From 0fd671007562d804d9fd330696f0cf692092f414 Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Tue, 15 Oct 2019 16:21:37 +0000 Subject: [PATCH 1/5] Try to fix paths in coverage report generation - Add the local path to coverage config - Fix the paths before generating an HTML report --- .gitlab-ci.yml | 1 + setup.cfg | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 27014d827..f46fd351e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -68,6 +68,7 @@ pages: image: openlp/debian script: - python3-coverage combine linux.coverage macos.coverage + - fixpaths .coverage - python3-coverage html - mv htmlcov public - python3-coverage report diff --git a/setup.cfg b/setup.cfg index 27af3897d..f992ea9b3 100644 --- a/setup.cfg +++ b/setup.cfg @@ -23,3 +23,10 @@ max-line-length = 120 # W503 line break before binary operator # W504 line break after binary operator ignore = E402,W503,W504 + +# Coverage configuration +[paths] +source = + ./ + /builds/openlp/openlp/ + /Users/raoul/Library/Application Support/gitlab-runner/builds/*/*/openlp/openlp/ From d98b84bd26304721793a5059b2ef6dbdc8be29ab Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Wed, 16 Oct 2019 20:10:33 -0700 Subject: [PATCH 2/5] Set up a GitLab runner on a Windows 7 VM, and configure the pipeline to use it. --- .gitignore | 2 ++ .gitlab-ci.yml | 16 +++++++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 54ec9c9ac..39e4703d3 100644 --- a/.gitignore +++ b/.gitignore @@ -16,10 +16,12 @@ .cache .coverage .directory +.eggs .idea .kdev4 .komodotools .pytest_cache +.venv .vscode OpenLP.egg-info \#*\# diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index f46fd351e..5edb7301e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -63,11 +63,24 @@ test-macos: only: - master@openlp/openlp +test-windows: + stage: test + tags: + - windows + script: + - C:\Users\raoul\GitLab-Runner\venv\Scripts\pytest.exe --color=no --disable-warnings --cov openlp + - mv .coverage windows.coverage + artifacts: + paths: + - windows.coverage + only: + - master@openlp/openlp + pages: stage: deploy image: openlp/debian script: - - python3-coverage combine linux.coverage macos.coverage + - python3-coverage combine linux.coverage macos.coverage windows.coverage - fixpaths .coverage - python3-coverage html - mv htmlcov public @@ -79,5 +92,6 @@ pages: dependencies: - test-debian - test-macos + - test-windows only: - master@openlp/openlp From 6eb81e2f610253bbbbccb010d7f53b6d62cbb87e Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Fri, 18 Oct 2019 06:12:09 +0000 Subject: [PATCH 3/5] Create a Theme Preview dialog, plus some theme background fixes. - Update .gitignore to ignore virtualenvs and eggs directory - Create a dialog to generate theme previews/thumbnails - Use theme preview generation dialog all over - Create a test for the new method - Update existing theme manager tests - Skip Bible HTTP tests when in GitLab CI - Make theme backgrounds scale and centred --- .gitignore | 2 + openlp/core/display/html/display.css | 2 + openlp/core/display/render.py | 18 ++++- openlp/core/display/window.py | 14 ++-- openlp/core/ui/thememanager.py | 48 +++++++----- openlp/core/ui/themeprogressdialog.py | 75 +++++++++++++++++++ openlp/core/ui/themeprogressform.py | 64 ++++++++++++++++ resources/forms/themeprogressdialog.ui | 54 +++++++++++++ .../openlp_core/ui/test_thememanager.py | 35 +++++++-- .../openlp_core/ui/test_thememanager.py | 3 +- .../openlp_plugins/bibles/test_lib_http.py | 2 +- 11 files changed, 281 insertions(+), 36 deletions(-) create mode 100644 openlp/core/ui/themeprogressdialog.py create mode 100644 openlp/core/ui/themeprogressform.py create mode 100644 resources/forms/themeprogressdialog.ui 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): From 57dd0897ae89a254382d24ecff579918b6204f3b Mon Sep 17 00:00:00 2001 From: Tim Date: Fri, 1 Nov 2019 15:56:34 +0000 Subject: [PATCH 4/5] Head --- .gitlab-ci.yml | 5 +- openlp/core/display/html/display.js | 74 ++++++++- openlp/core/display/html/reveal.js | 9 -- openlp/core/display/window.py | 14 +- openlp/core/lib/json/theme.json | 2 + openlp/core/lib/theme.py | 89 ++++++++++- openlp/core/ui/icons.py | 1 + openlp/core/ui/mainwindow.py | 8 +- openlp/core/ui/shortcutlistform.py | 27 ++-- openlp/core/ui/themeform.py | 47 ++++-- openlp/core/ui/thememanager.py | 6 +- openlp/core/ui/themeprogressform.py | 3 + openlp/core/ui/themewizard.py | 37 ++++- openlp/core/widgets/edits.py | 2 +- resources/forms/themewizard.ui | 63 +++++++- scripts/reveal-js.patch | 25 --- .../openlp_core/api/http/test_init.py | 17 +++ .../openlp_core/display/test_window.py | 76 ++++++++++ .../functional/openlp_core/lib/test_theme.py | 76 +++++++++- .../openlp_core/ui/test_slidecontroller.py | 19 ++- .../openlp_core/ui/test_splashscreen.py | 46 ++++++ .../openlp_core/ui/test_themeform.py | 4 +- .../openlp_core/ui/test_thememanager.py | 8 +- tests/js/test_display.js | 115 ++++++++++++-- .../openlp_core/ui/test_themeprogressform.py | 142 ++++++++++++++++++ 25 files changed, 797 insertions(+), 118 deletions(-) delete mode 100644 scripts/reveal-js.patch create mode 100644 tests/functional/openlp_core/display/test_window.py create mode 100644 tests/functional/openlp_core/ui/test_splashscreen.py create mode 100644 tests/openlp_core/ui/test_themeprogressform.py diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 58d647ebf..73294d97f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,17 +1,16 @@ stages: - - lint - test - deploy lint-python: - stage: lint + stage: test image: openlp/debian script: - sh scripts/generate_resources.sh - flake8 lint-javascript: - stage: lint + stage: test image: openlp/angular script: - yarn install diff --git a/openlp/core/display/html/display.js b/openlp/core/display/html/display.js index 1a55d8507..0eda27cfa 100644 --- a/openlp/core/display/html/display.js +++ b/openlp/core/display/html/display.js @@ -43,6 +43,26 @@ var VerticalAlign = { Bottom: 2 }; +/** + * Transition type enumeration + */ +var TransitionType = { + Fade: 0, + Slide: 1, + Convex: 2, + Concave: 3, + Zoom: 4 +}; + +/** + * Transition speed enumeration + */ +var TransitionSpeed = { + Normal: 0, + Fast: 1, + Slow: 2 +}; + /** * Audio state enumeration */ @@ -329,6 +349,7 @@ var Display = { _alertState: AlertState.NotDisplaying, _transitionState: TransitionState.NoTransition, _animationState: AnimationState.NoAnimation, + _doTransitions: false, _revealConfig: { margin: 0.0, minScale: 1.0, @@ -348,21 +369,25 @@ var Display = { /** * Start up reveal and do any other initialisation */ - init: function () { + init: function (doTransitions=false) { + Display._doTransitions = doTransitions; Reveal.initialize(Display._revealConfig); }, /** * Reinitialise Reveal */ reinit: function () { - Reveal.reinitialize(); + Reveal.sync(); + // Python expects to be on first page after reinit + Reveal.slide(0); }, /** * Set the transition type * @param {string} transitionType - Can be one of "none", "fade", "slide", "convex", "concave", "zoom" + * @param {string} transitionSpeed - Can be one of "default", "fast", "slow" */ - setTransition: function (transitionType) { - Reveal.configure({"transition": transitionType}); + setTransition: function (transitionType, transitionSpeed) { + Reveal.configure({"transition": transitionType, "transitionSpeed": transitionSpeed}); }, /** * Clear the current list of slides @@ -639,7 +664,6 @@ var Display = { Display.addTextSlide(slide.verse, slide.text, slide.footer, false); }); Display.reinit(); - Display.goToSlide(0); }, /** * Create the
that will contain text slides (vertical slides in react) @@ -869,6 +893,44 @@ var Display = { }, setTheme: function (theme) { Display._theme = theme; + // Set slide transitions + var new_transition_type = "none", + new_transition_speed = "default"; + if (!!theme.display_slide_transition && Display._doTransitions) { + switch (theme.display_slide_transition_type) { + case TransitionType.Fade: + new_transition_type = "fade"; + break; + case TransitionType.Slide: + new_transition_type = "slide"; + break; + case TransitionType.Convex: + new_transition_type = "convex"; + break; + case TransitionType.Concave: + new_transition_type = "concave"; + break; + case TransitionType.Zoom: + new_transition_type = "zoom"; + break; + default: + new_transition_type = "fade"; + } + switch (theme.display_slide_transition_speed) { + case TransitionSpeed.Normal: + new_transition_speed = "default"; + break; + case TransitionSpeed.Fast: + new_transition_speed = "fast"; + break; + case TransitionSpeed.Slow: + new_transition_speed = "slow"; + break; + default: + new_transition_speed = "default"; + } + } + Display.setTransition(new_transition_type, new_transition_speed); // Set the background var globalBackground = $("#global-background")[0]; var backgroundStyle = {}; @@ -986,7 +1048,7 @@ var Display = { default: mainStyle["justify-content"] = "center"; } - if (theme.hasOwnProperty('font_main_shadow_size')) { + if (theme.hasOwnProperty('font_main_shadow_size') && !!theme.font_main_shadow) { mainStyle["text-shadow"] = theme.font_main_shadow_color + " " + theme.font_main_shadow_size + "pt " + theme.font_main_shadow_size + "pt"; } diff --git a/openlp/core/display/html/reveal.js b/openlp/core/display/html/reveal.js index 4ca322832..5c026db71 100644 --- a/openlp/core/display/html/reveal.js +++ b/openlp/core/display/html/reveal.js @@ -441,14 +441,6 @@ } - /** - * Restarts up the presentation if the client is capable. - */ - function reinitialize() { - initialized = false; - initialize(config); - } - /** * Inspect the client to see what it's capable of, this * should only happens once per runtime. @@ -5814,7 +5806,6 @@ VERSION: VERSION, initialize: initialize, - reinitialize: reinitialize, configure: configure, sync: sync, diff --git a/openlp/core/display/window.py b/openlp/core/display/window.py index f5a53b57d..52c1a8fc3 100644 --- a/openlp/core/display/window.py +++ b/openlp/core/display/window.py @@ -109,13 +109,17 @@ class DisplayWindow(QtWidgets.QWidget): Create the display window """ super(DisplayWindow, self).__init__(parent) + # Gather all flags for the display window + flags = QtCore.Qt.FramelessWindowHint | QtCore.Qt.Tool | QtCore.Qt.WindowStaysOnTopHint + if Settings().value('advanced/x11 bypass wm'): + flags |= QtCore.Qt.X11BypassWindowManagerHint # 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) + self.setWindowFlags(flags) self.setAttribute(QtCore.Qt.WA_TranslucentBackground) self.setAutoFillBackground(True) self.setAttribute(QtCore.Qt.WA_DeleteOnClose) @@ -198,13 +202,13 @@ class DisplayWindow(QtWidgets.QWidget): """ Add stuff after page initialisation """ - self.run_javascript('Display.init();') + js_is_display = str(self.is_display).lower() + self.run_javascript('Display.init({do_transitions});'.format(do_transitions=js_is_display)) self._is_initialised = True - 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) + if self._can_show_startup_screen: + self.set_startup_screen() def run_javascript(self, script, is_sync=False): """ diff --git a/openlp/core/lib/json/theme.json b/openlp/core/lib/json/theme.json index b23593c6b..1a6018535 100644 --- a/openlp/core/lib/json/theme.json +++ b/openlp/core/lib/json/theme.json @@ -11,6 +11,8 @@ "display" :{ "horizontal_align": 0, "slide_transition": false, + "slide_transition_type": 0, + "slide_transition_speed": 0, "vertical_align": 0 }, "font": { diff --git a/openlp/core/lib/theme.py b/openlp/core/lib/theme.py index bc3cbce5c..3c3ec1e24 100644 --- a/openlp/core/lib/theme.py +++ b/openlp/core/lib/theme.py @@ -127,6 +127,82 @@ class BackgroundGradientType(object): return BackgroundGradientType.LeftBottom +class TransitionType(object): + """ + Type enumeration for transition types. + """ + Fade = 0 + Slide = 1 + Convex = 2 + Concave = 3 + Zoom = 4 + + @staticmethod + def to_string(transition_type): + """ + Return a string representation of a transition type. + """ + if transition_type == TransitionType.Fade: + return 'fade' + elif transition_type == TransitionType.Slide: + return 'slide' + elif transition_type == TransitionType.Convex: + return 'convex' + elif transition_type == TransitionType.Concave: + return 'concave' + elif transition_type == TransitionType.Zoom: + return 'zoom' + + @staticmethod + def from_string(type_string): + """ + Return a transition type for the given string. + """ + if type_string == 'fade': + return TransitionType.Fade + elif type_string == 'slide': + return TransitionType.Slide + elif type_string == 'convex': + return TransitionType.Convex + elif type_string == 'concave': + return TransitionType.Concave + elif type_string == 'zoom': + return TransitionType.Zoom + + +class TransitionSpeed(object): + """ + Type enumeration for transition types. + """ + Normal = 0 + Fast = 1 + Slow = 2 + + @staticmethod + def to_string(transition_speed): + """ + Return a string representation of a transition type. + """ + if transition_speed == TransitionSpeed.Normal: + return 'normal' + elif transition_speed == TransitionSpeed.Fast: + return 'fast' + elif transition_speed == TransitionSpeed.Slow: + return 'slow' + + @staticmethod + def from_string(type_string): + """ + Return a transition type for the given string. + """ + if type_string == 'normal': + return TransitionSpeed.Normal + if type_string == 'fast': + return TransitionSpeed.Fast + elif type_string == 'slow': + return TransitionSpeed.Slow + + class HorizontalType(object): """ Type enumeration for horizontal alignment. @@ -153,7 +229,7 @@ class VerticalType(object): BOOLEAN_LIST = ['bold', 'italics', 'override', 'outline', 'shadow', 'slide_transition'] INTEGER_LIST = ['size', 'line_adjustment', 'x', 'height', 'y', 'width', 'shadow_size', 'outline_size', - 'horizontal_align', 'vertical_align', 'wrap_style'] + 'horizontal_align', 'vertical_align', 'wrap_style', 'slide_transition_type', 'slide_transition_speed'] class Theme(object): @@ -204,12 +280,21 @@ class Theme(object): Set the header and footer size into the current primary screen. 10 px on each side is removed to allow for a border. """ + self.set_default_header() + self.set_default_footer() + + def set_default_header(self): current_screen_geometry = ScreenList().current.display_geometry + self.font_main_x = 10 self.font_main_y = 0 self.font_main_width = current_screen_geometry.width() - 20 self.font_main_height = current_screen_geometry.height() * 9 / 10 - self.font_footer_width = current_screen_geometry.width() - 20 + + def set_default_footer(self): + current_screen_geometry = ScreenList().current.display_geometry + self.font_footer_x = 10 self.font_footer_y = current_screen_geometry.height() * 9 / 10 + self.font_footer_width = current_screen_geometry.width() - 20 self.font_footer_height = current_screen_geometry.height() / 10 def load_theme(self, theme, theme_path=None): diff --git a/openlp/core/ui/icons.py b/openlp/core/ui/icons.py index bd84dab8f..6f8b3e4bd 100644 --- a/openlp/core/ui/icons.py +++ b/openlp/core/ui/icons.py @@ -62,6 +62,7 @@ class UiIcons(metaclass=Singleton): 'authentication': {'icon': 'fa.exclamation-triangle', 'attr': 'red'}, 'address': {'icon': 'fa.book'}, 'back': {'icon': 'fa.step-backward'}, + 'backspace': {'icon': 'mdi.backspace-outline'}, 'bible': {'icon': 'fa.book'}, 'blank': {'icon': 'fa.times-circle'}, 'blank_theme': {'icon': 'fa.file-image-o'}, diff --git a/openlp/core/ui/mainwindow.py b/openlp/core/ui/mainwindow.py index 81830f287..16ca79072 100644 --- a/openlp/core/ui/mainwindow.py +++ b/openlp/core/ui/mainwindow.py @@ -670,6 +670,10 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, LogMixin, RegistryPropert self.application.process_events() plugin.first_time() self.application.process_events() + # Load the themes from files + self.theme_manager_contents.load_first_time_themes() + # Update the theme widget + self.theme_manager_contents.load_themes() temp_path = Path(gettempdir(), 'openlp') shutil.rmtree(temp_path, True) @@ -714,10 +718,6 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, LogMixin, RegistryPropert self.active_plugin.toggle_status(PluginStatus.Inactive) # Set global theme and Registry().execute('theme_update_global') - # Load the themes from files - self.theme_manager_contents.load_first_time_themes() - # Update the theme widget - self.theme_manager_contents.load_themes() # Check if any Bibles downloaded. If there are, they will be processed. Registry().execute('bibles_load_list') self.application.set_normal_cursor() diff --git a/openlp/core/ui/shortcutlistform.py b/openlp/core/ui/shortcutlistform.py index 9b00e459b..168d135ed 100644 --- a/openlp/core/ui/shortcutlistform.py +++ b/openlp/core/ui/shortcutlistform.py @@ -323,16 +323,23 @@ class ShortcutListForm(QtWidgets.QDialog, Ui_ShortcutListDialog, RegistryPropert if not toggled: return action = self._current_item_action() - shortcuts = self._action_shortcuts(action) - self.refresh_shortcut_list() - primary_button_text = '' - alternate_button_text = '' - if shortcuts: - primary_button_text = self.get_shortcut_string(shortcuts[0], for_display=True) - if len(shortcuts) == 2: - alternate_button_text = self.get_shortcut_string(shortcuts[1], for_display=True) - self.primary_push_button.setText(primary_button_text) - self.alternate_push_button.setText(alternate_button_text) + if action is None: + QtWidgets.QMessageBox.information(self, translate('OpenLP.ShortcutListForm', 'Select an Action'), + translate('OpenLP.ShortcutListForm', 'Select an action and click one ' + 'of the buttons below to start ' + 'capturing a new primary or alternate shortcut, respectively.')) + + else: + shortcuts = self._action_shortcuts(action) + self.refresh_shortcut_list() + primary_button_text = '' + alternate_button_text = '' + if shortcuts: + primary_button_text = self.get_shortcut_string(shortcuts[0], for_display=True) + if len(shortcuts) == 2: + alternate_button_text = self.get_shortcut_string(shortcuts[1], for_display=True) + self.primary_push_button.setText(primary_button_text) + self.alternate_push_button.setText(alternate_button_text) def save(self): """ diff --git a/openlp/core/ui/themeform.py b/openlp/core/ui/themeform.py index 040aad980..c769c64d6 100644 --- a/openlp/core/ui/themeform.py +++ b/openlp/core/ui/themeform.py @@ -98,6 +98,7 @@ class ThemeForm(QtWidgets.QWizard, Ui_ThemeWizard, RegistryProperties): self.main_font_combo_box.activated.connect(self.calculate_lines) self.footer_font_combo_box.activated.connect(self.update_theme) self.footer_size_spin_box.valueChanged.connect(self.update_theme) + self.transitions_check_box.stateChanged.connect(self.on_transitions_check_box_state_changed) def set_defaults(self): """ @@ -145,6 +146,8 @@ class ThemeForm(QtWidgets.QWizard, Ui_ThemeWizard, RegistryProperties): self.background_page.registerField('horizontal', self.horizontal_combo_box) self.background_page.registerField('vertical', self.vertical_combo_box) self.background_page.registerField('slide_transition', self.transitions_check_box) + self.background_page.registerField('slide_transition_type', self.transition_combo_box) + self.background_page.registerField('slide_transition_speed', self.transition_speed_combo_box) self.background_page.registerField('name', self.theme_name_edit) def calculate_lines(self): @@ -251,10 +254,7 @@ class ThemeForm(QtWidgets.QWizard, Ui_ThemeWizard, RegistryProperties): Change state as Shadow check box changed """ if self.update_theme_allowed: - if state == QtCore.Qt.Checked: - self.theme.font_main_shadow = True - else: - self.theme.font_main_shadow = False + self.theme.font_main_shadow = state == QtCore.Qt.Checked self.shadow_color_button.setEnabled(self.theme.font_main_shadow) self.shadow_size_spin_box.setEnabled(self.theme.font_main_shadow) self.calculate_lines() @@ -275,6 +275,16 @@ class ThemeForm(QtWidgets.QWizard, Ui_ThemeWizard, RegistryProperties): if self.update_theme_allowed: self.theme.font_footer_override = (value != QtCore.Qt.Checked) + def on_transitions_check_box_state_changed(self, state): + """ + Change state as Transitions check box is changed + """ + if self.update_theme_allowed: + self.theme.display_slide_transition = state == QtCore.Qt.Checked + self.transition_combo_box.setEnabled(self.theme.display_slide_transition) + self.transition_speed_combo_box.setEnabled(self.theme.display_slide_transition) + self.calculate_lines() + def exec(self, edit=False): """ Run the wizard. @@ -395,6 +405,8 @@ class ThemeForm(QtWidgets.QWizard, Ui_ThemeWizard, RegistryProperties): self.setField('horizontal', self.theme.display_horizontal_align) self.setField('vertical', self.theme.display_vertical_align) self.setField('slide_transition', self.theme.display_slide_transition) + self.setField('slide_transition_type', self.theme.display_slide_transition_type) + self.setField('slide_transition_speed', self.theme.display_slide_transition_speed) def set_preview_page_values(self): """ @@ -525,19 +537,28 @@ class ThemeForm(QtWidgets.QWizard, Ui_ThemeWizard, RegistryProperties): # footer page self.theme.font_footer_name = self.footer_font_combo_box.currentFont().family() self.theme.font_footer_size = self.field('footer_size_spin_box') - # position page - self.theme.font_main_x = self.field('main_position_x') - self.theme.font_main_y = self.field('main_position_y') - self.theme.font_main_height = self.field('main_position_height') - self.theme.font_main_width = self.field('main_position_width') - self.theme.font_footer_x = self.field('footer_position_x') - self.theme.font_footer_y = self.field('footer_position_y') - self.theme.font_footer_height = self.field('footer_position_height') - self.theme.font_footer_width = self.field('footer_position_width') + # position page (main) + if self.theme.font_main_override: + self.theme.font_main_x = self.field('main_position_x') + self.theme.font_main_y = self.field('main_position_y') + self.theme.font_main_height = self.field('main_position_height') + self.theme.font_main_width = self.field('main_position_width') + else: + self.theme.set_default_header() + # position page (footer) + if self.theme.font_footer_override: + self.theme.font_footer_x = self.field('footer_position_x') + self.theme.font_footer_y = self.field('footer_position_y') + self.theme.font_footer_height = self.field('footer_position_height') + self.theme.font_footer_width = self.field('footer_position_width') + else: + self.theme.set_default_footer() # position page self.theme.display_horizontal_align = self.horizontal_combo_box.currentIndex() self.theme.display_vertical_align = self.vertical_combo_box.currentIndex() self.theme.display_slide_transition = self.field('slide_transition') + self.theme.display_slide_transition_type = self.field('slide_transition_type') + self.theme.display_slide_transition_speed = self.field('slide_transition_speed') def accept(self): """ diff --git a/openlp/core/ui/thememanager.py b/openlp/core/ui/thememanager.py index 2a447b7a9..8539f1eab 100644 --- a/openlp/core/ui/thememanager.py +++ b/openlp/core/ui/thememanager.py @@ -149,16 +149,15 @@ 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() self.upgrade_themes() # TODO: Can be removed when upgrade path from OpenLP 2.4 no longer needed def bootstrap_post_set_up(self): """ process the bootstrap post setup request """ + self.progress_form = ThemeProgressForm(self) self.theme_form = ThemeForm(self) self.theme_form.path = self.theme_path self.file_rename_form = FileRenameForm() @@ -481,7 +480,8 @@ class ThemeManager(QtWidgets.QWidget, RegistryBase, Ui_ThemeManager, LogMixin, R 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) + if new_themes: + self.update_preview_images(new_themes) self.application.set_normal_cursor() def load_themes(self): diff --git a/openlp/core/ui/themeprogressform.py b/openlp/core/ui/themeprogressform.py index 4d61357a3..c636dd834 100644 --- a/openlp/core/ui/themeprogressform.py +++ b/openlp/core/ui/themeprogressform.py @@ -36,6 +36,8 @@ class ThemeProgressForm(QtWidgets.QDialog, UiThemeProgressDialog, RegistryProper super().__init__(parent) self.setup_ui(self) self._theme_list = [] + + def show(self): self.progress_bar.setMinimum(0) self.progress_bar.setMaximum(0) self.progress_bar.setValue(0) @@ -45,6 +47,7 @@ class ThemeProgressForm(QtWidgets.QDialog, UiThemeProgressDialog, RegistryProper except ZeroDivisionError: self.ratio = 16 / 9 self.theme_preview_layout.aspect_ratio = self.ratio + return super().show() def get_preview(self, theme_name, theme_data): self.label.setText(theme_name) diff --git a/openlp/core/ui/themewizard.py b/openlp/core/ui/themewizard.py index 4fb8f3883..5738a70c5 100644 --- a/openlp/core/ui/themewizard.py +++ b/openlp/core/ui/themewizard.py @@ -25,7 +25,13 @@ from PyQt5 import QtCore, QtGui, QtWidgets from openlp.core.common import is_macosx from openlp.core.common.i18n import UiStrings, translate -from openlp.core.lib.theme import BackgroundGradientType, BackgroundType, HorizontalType +from openlp.core.lib.theme import ( + BackgroundGradientType, + BackgroundType, + HorizontalType, + TransitionType, + TransitionSpeed +) from openlp.core.lib.ui import add_welcome_page, create_valign_selection_widgets from openlp.core.ui.icons import UiIcons from openlp.core.widgets.buttons import ColorButton @@ -271,12 +277,22 @@ class Ui_ThemeWizard(object): self.vertical_label.setObjectName('vertical_label') self.vertical_combo_box.setObjectName('vertical_combo_box') self.alignment_layout.addRow(self.vertical_label, self.vertical_combo_box) - self.transitions_label = QtWidgets.QLabel(self.alignment_page) - self.transitions_label.setObjectName('transitions_label') self.transitions_check_box = QtWidgets.QCheckBox(self.alignment_page) self.transitions_check_box.setObjectName('transitions_check_box') - self.alignment_layout.addRow(self.transitions_label, self.transitions_check_box) - self.alignment_layout.setItem(3, QtWidgets.QFormLayout.LabelRole, self.spacer) + self.transition_layout = QtWidgets.QHBoxLayout() + self.transition_layout.setObjectName("transition_layout") + self.transition_combo_box = QtWidgets.QComboBox(self.alignment_page) + self.transition_combo_box.setObjectName("transition_combo_box") + self.transition_combo_box.addItems(['', '', '', '', '']) + self.transition_layout.addWidget(self.transition_combo_box) + self.transition_speed_label = QtWidgets.QLabel(self.alignment_page) + self.transition_speed_label.setObjectName("transition_speed_label") + self.transition_layout.addWidget(self.transition_speed_label) + self.transition_speed_combo_box = QtWidgets.QComboBox(self.alignment_page) + self.transition_speed_combo_box.setObjectName("transition_speed_combo_box") + self.transition_speed_combo_box.addItems(['', '', '']) + self.transition_layout.addWidget(self.transition_speed_combo_box) + self.alignment_layout.addRow(self.transitions_check_box, self.transition_layout) theme_wizard.addPage(self.alignment_page) # Area Position Page self.area_position_page = QtWidgets.QWizardPage() @@ -460,7 +476,16 @@ class Ui_ThemeWizard(object): self.horizontal_combo_box.setItemText(HorizontalType.Right, translate('OpenLP.ThemeWizard', 'Right')) self.horizontal_combo_box.setItemText(HorizontalType.Center, translate('OpenLP.ThemeWizard', 'Center')) self.horizontal_combo_box.setItemText(HorizontalType.Justify, translate('OpenLP.ThemeWizard', 'Justify')) - self.transitions_label.setText(translate('OpenLP.ThemeWizard', 'Transitions:')) + self.transitions_check_box.setText(translate('OpenLP.ThemeWizard', 'Transitions:')) + self.transition_combo_box.setItemText(TransitionType.Fade, translate('OpenLP.ThemeWizard', 'Fade')) + self.transition_combo_box.setItemText(TransitionType.Slide, translate('OpenLP.ThemeWizard', 'Slide')) + self.transition_combo_box.setItemText(TransitionType.Concave, translate('OpenLP.ThemeWizard', 'Concave')) + self.transition_combo_box.setItemText(TransitionType.Convex, translate('OpenLP.ThemeWizard', 'Convex')) + self.transition_combo_box.setItemText(TransitionType.Zoom, translate('OpenLP.ThemeWizard', 'Zoom')) + self.transition_speed_label.setText(translate('OpenLP.ThemeWizard', 'Speed:')) + self.transition_speed_combo_box.setItemText(TransitionSpeed.Normal, translate('OpenLP.ThemeWizard', 'Normal')) + self.transition_speed_combo_box.setItemText(TransitionSpeed.Fast, translate('OpenLP.ThemeWizard', 'Fast')) + self.transition_speed_combo_box.setItemText(TransitionSpeed.Slow, translate('OpenLP.ThemeWizard', 'Slow')) self.area_position_page.setTitle(translate('OpenLP.ThemeWizard', 'Output Area Locations')) self.area_position_page.setSubTitle(translate('OpenLP.ThemeWizard', 'Allows you to change and move the' ' Main and Footer areas.')) diff --git a/openlp/core/widgets/edits.py b/openlp/core/widgets/edits.py index 2efae2e1d..022ab6d53 100644 --- a/openlp/core/widgets/edits.py +++ b/openlp/core/widgets/edits.py @@ -64,7 +64,7 @@ class SearchEdit(QtWidgets.QLineEdit): self.settings_section = settings_section self._current_search_type = -1 self.clear_button = QtWidgets.QToolButton(self) - self.clear_button.setIcon(UiIcons().shortcuts) + self.clear_button.setIcon(UiIcons().backspace) self.clear_button.setCursor(QtCore.Qt.ArrowCursor) self.clear_button.setStyleSheet('QToolButton { border: none; padding: 0px; }') self.clear_button.resize(18, 18) diff --git a/resources/forms/themewizard.ui b/resources/forms/themewizard.ui index 9e1f8f9e8..41faeadca 100644 --- a/resources/forms/themewizard.ui +++ b/resources/forms/themewizard.ui @@ -765,13 +765,72 @@ p, li { white-space: pre-wrap; } - + - Transitions + Transitions: + + + + + + + Fade + + + + + Slide + + + + + Convex + + + + + Concave + + + + + Zoom + + + + + + + + Speed: + + + + + + + + Normal + + + + + Fast + + + + + Slow + + + + + + diff --git a/scripts/reveal-js.patch b/scripts/reveal-js.patch deleted file mode 100644 index 70306926a..000000000 --- a/scripts/reveal-js.patch +++ /dev/null @@ -1,25 +0,0 @@ ---- reveal.js.orig 2018-08-01 10:37:51.000000000 +0200 -+++ reveal.js 2019-02-11 21:25:39.396198927 +0100 -@@ -383,6 +383,14 @@ - } - - /** -+ * Restarts up the presentation if the client is capable. -+ */ -+ function reinitialize() { -+ initialized = false; -+ initialize(config); -+ } -+ -+ /** - * Inspect the client to see what it's capable of, this - * should only happens once per runtime. - */ -@@ -5372,6 +5380,7 @@ - VERSION: VERSION, - - initialize: initialize, -+ reinitialize: reinitialize, - configure: configure, - - sync: sync, diff --git a/tests/functional/openlp_core/api/http/test_init.py b/tests/functional/openlp_core/api/http/test_init.py index 770599a30..166ec6ef4 100644 --- a/tests/functional/openlp_core/api/http/test_init.py +++ b/tests/functional/openlp_core/api/http/test_init.py @@ -144,6 +144,23 @@ class TestInit(TestCase, TestMixin): # THEN: the result will be as expected - try again assert str(value) == 'called' + def test_requires_auth_missing_credentials(self): + """ + Test the requires_auth wrapper with enabled security and authorization taken place and and error + :return: + """ + # GIVEN: An enabled security and a known user + Settings().setValue('api/authentication enabled', True) + Settings().setValue('api/user id', 'superfly') + Settings().setValue('api/password', 'lamas') + + # WHEN: I call the function with no password + wrapped_function = requires_auth(func) + value = wrapped_function(0) + + # THEN: the result will be as expected (unauthorized) + assert str(value) == str(authenticate()) + def func(field=None): return 'called' diff --git a/tests/functional/openlp_core/display/test_window.py b/tests/functional/openlp_core/display/test_window.py new file mode 100644 index 000000000..df1b8aded --- /dev/null +++ b/tests/functional/openlp_core/display/test_window.py @@ -0,0 +1,76 @@ +# -*- 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 . # +########################################################################## +""" +Package to test the openlp.core.display.window package. +""" +import sys + +from unittest import TestCase +from unittest.mock import MagicMock, patch + +from PyQt5 import QtCore + +# Mock QtWebEngineWidgets +sys.modules['PyQt5.QtWebEngineWidgets'] = MagicMock() + +from openlp.core.display.window import DisplayWindow +from tests.helpers.testmixin import TestMixin + + +@patch('PyQt5.QtWidgets.QVBoxLayout') +@patch('openlp.core.display.webengine.WebEngineView') +@patch('openlp.core.display.window.Settings') +class TestDisplayWindow(TestCase, TestMixin): + """ + A test suite to test the functions in DisplayWindow + """ + + def test_x11_override_on(self, MockSettings, mocked_webengine, mocked_addWidget): + """ + Test that the x11 override option bit is set + """ + # GIVEN: x11 bypass is on + mocked_settings = MagicMock() + mocked_settings.value.return_value = True + MockSettings.return_value = mocked_settings + + # WHEN: A DisplayWindow is generated + display_window = DisplayWindow() + + # THEN: The x11 override flag should be set + x11_bit = display_window.windowFlags() & QtCore.Qt.X11BypassWindowManagerHint + assert x11_bit == QtCore.Qt.X11BypassWindowManagerHint + + def test_x11_override_off(self, MockSettings, mocked_webengine, mocked_addWidget): + """ + Test that the x11 override option bit is not set when setting if off + """ + # GIVEN: x11 bypass is off + mocked_settings = MagicMock() + mocked_settings.value.return_value = False + MockSettings.return_value = mocked_settings + + # WHEN: A DisplayWindow is generated + display_window = DisplayWindow() + + # THEN: The x11 override flag should not be set + x11_bit = display_window.windowFlags() & QtCore.Qt.X11BypassWindowManagerHint + assert x11_bit != QtCore.Qt.X11BypassWindowManagerHint diff --git a/tests/functional/openlp_core/lib/test_theme.py b/tests/functional/openlp_core/lib/test_theme.py index 48b3e5367..b738abaa0 100644 --- a/tests/functional/openlp_core/lib/test_theme.py +++ b/tests/functional/openlp_core/lib/test_theme.py @@ -23,6 +23,7 @@ Package to test the openlp.core.lib.theme package. """ from pathlib import Path from unittest import TestCase +from unittest.mock import MagicMock, patch from openlp.core.lib.theme import BackgroundType, Theme @@ -175,10 +176,83 @@ class TestTheme(TestCase): lt.load_theme(save_theme_json) self.check_theme(lt) + @patch('openlp.core.display.screens.ScreenList.current') + def test_set_default_footer(self, mock_geometry): + """ + Test the set_default_footer function sets the footer back to default + (reletive to the screen) + """ + # GIVEN: A screen geometry object and a Theme footer with a strange area + mock_geometry.display_geometry = MagicMock() + mock_geometry.display_geometry.height.return_value = 600 + mock_geometry.display_geometry.width.return_value = 400 + theme = Theme() + theme.font_main_x = 20 + theme.font_footer_x = 207 + theme.font_footer_y = 25 + theme.font_footer_width = 4253 + theme.font_footer_height = 5423 + + # WHEN: set_default_footer is called + theme.set_default_footer() + + # THEN: footer should be set, header should not have changed + assert theme.font_main_x == 20, 'header should not have been changed' + assert theme.font_footer_x == 10, 'x pos should be reset to default of 10' + assert theme.font_footer_y == 540, 'y pos should be reset to (screen_size_height * 9 / 10)' + assert theme.font_footer_width == 380, 'width should have been reset to (screen_size_width - 20)' + assert theme.font_footer_height == 60, 'height should have been reset to (screen_size_height / 10)' + + @patch('openlp.core.display.screens.ScreenList.current') + def test_set_default_header(self, mock_geometry): + """ + Test the set_default_header function sets the header back to default + (reletive to the screen) + """ + # GIVEN: A screen geometry object and a Theme header with a strange area + mock_geometry.display_geometry = MagicMock() + mock_geometry.display_geometry.height.return_value = 600 + mock_geometry.display_geometry.width.return_value = 400 + theme = Theme() + theme.font_footer_x = 200 + theme.font_main_x = 687 + theme.font_main_y = 546 + theme.font_main_width = 345 + theme.font_main_height = 653 + + # WHEN: set_default_header is called + theme.set_default_header() + + # THEN: footer should be set, header should not have changed + assert theme.font_footer_x == 200, 'footer should not have been changed' + assert theme.font_main_x == 10, 'x pos should be reset to default of 10' + assert theme.font_main_y == 0, 'y pos should be reset to 0' + assert theme.font_main_width == 380, 'width should have been reset to (screen_size_width - 20)' + assert theme.font_main_height == 540, 'height should have been reset to (screen_size_height * 9 / 10)' + + @patch('openlp.core.display.screens.ScreenList.current') + def test_set_default_header_footer(self, mock_geometry): + """ + Test the set_default_header_footer function sets the header and footer back to default + (reletive to the screen) + """ + # GIVEN: A screen geometry object and a Theme header with a strange area + mock_geometry.display_geometry = MagicMock() + theme = Theme() + theme.font_footer_x = 200 + theme.font_main_x = 687 + + # WHEN: set_default_header is called + theme.set_default_header_footer() + + # THEN: footer should be set, header should not have changed + assert theme.font_footer_x == 10, 'footer x pos should be reset to default of 10' + assert theme.font_main_x == 10, 'header x pos should be reset to default of 10' + def check_theme(self, theme): assert '#000000' == theme.background_border_color, 'background_border_color should be "#000000"' assert 'solid' == theme.background_type, 'background_type should be "solid"' assert 0 == theme.display_vertical_align, 'display_vertical_align should be 0' assert theme.font_footer_bold is False, 'font_footer_bold should be False' assert 'Arial' == theme.font_main_name, 'font_main_name should be "Arial"' - assert 49 == len(theme.__dict__), 'The theme should have 49 attributes' + assert 51 == len(theme.__dict__), 'The theme should have 51 attributes' diff --git a/tests/functional/openlp_core/ui/test_slidecontroller.py b/tests/functional/openlp_core/ui/test_slidecontroller.py index e09ba70f1..2f26b95cd 100644 --- a/tests/functional/openlp_core/ui/test_slidecontroller.py +++ b/tests/functional/openlp_core/ui/test_slidecontroller.py @@ -941,10 +941,8 @@ class TestInfoLabel(TestCase): """ Test the paintEvent method when text fits the label """ - font = QtGui.QFont() - metrics = QtGui.QFontMetrics(font) - with patch('openlp.core.ui.slidecontroller.QtWidgets.QLabel'), \ + patch('openlp.core.ui.slidecontroller.QtGui.QFontMetrics') as MockFontMetrics, \ patch('openlp.core.ui.slidecontroller.QtGui.QPainter') as mocked_qpainter: # GIVEN: An instance of InfoLabel, with mocked text return, width and rect methods @@ -957,9 +955,11 @@ class TestInfoLabel(TestCase): info_label.rect = mocked_rect info_label.text = mocked_text info_label.width = mocked_width + mocked_font_metrics = MagicMock() + mocked_font_metrics.elidedText.return_value = test_string + MockFontMetrics.return_value = mocked_font_metrics # WHEN: The instance is wider than its text, and the paintEvent method is called - info_label.width.return_value = metrics.boundingRect(test_string).width() + 10 info_label.paintEvent(MagicMock()) # THEN: The text should be drawn centered with the complete test_string @@ -969,15 +969,14 @@ class TestInfoLabel(TestCase): """ Test the paintEvent method when text fits the label """ - font = QtGui.QFont() - metrics = QtGui.QFontMetrics(font) - with patch('openlp.core.ui.slidecontroller.QtWidgets.QLabel'), \ + patch('openlp.core.ui.slidecontroller.QtGui.QFontMetrics') as MockFontMetrics, \ patch('openlp.core.ui.slidecontroller.QtGui.QPainter') as mocked_qpainter: # GIVEN: An instance of InfoLabel, with mocked text return, width and rect methods info_label = InfoLabel() test_string = 'Label Text' + elided_test_string = test_string[0:5] + '...' mocked_rect = MagicMock() mocked_text = MagicMock() mocked_width = MagicMock() @@ -985,14 +984,14 @@ class TestInfoLabel(TestCase): info_label.rect = mocked_rect info_label.text = mocked_text info_label.width = mocked_width + mocked_font_metrics = MagicMock() + mocked_font_metrics.elidedText.return_value = elided_test_string + MockFontMetrics.return_value = mocked_font_metrics # WHEN: The instance is narrower than its text, and the paintEvent method is called - label_width = metrics.boundingRect(test_string).width() - 10 - info_label.width.return_value = label_width info_label.paintEvent(MagicMock()) # THEN: The text should be drawn aligned left with an elided test_string - elided_test_string = metrics.elidedText(test_string, QtCore.Qt.ElideRight, label_width) mocked_qpainter().drawText.assert_called_once_with(mocked_rect(), QtCore.Qt.AlignLeft, elided_test_string) @patch('builtins.super') diff --git a/tests/functional/openlp_core/ui/test_splashscreen.py b/tests/functional/openlp_core/ui/test_splashscreen.py new file mode 100644 index 000000000..e9d8bcca5 --- /dev/null +++ b/tests/functional/openlp_core/ui/test_splashscreen.py @@ -0,0 +1,46 @@ +# -*- 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 . # +########################################################################## +""" +Package to test the openlp.core.ui package. +""" +from PyQt5 import QtCore + +from openlp.core.ui.splashscreen import SplashScreen + + +class TestSplashScreen(): + """ + This will test the SplachScreen.py file + """ + def test_SplashScreen(self): + """ + Test that the SpashScreen is created correctly + """ + # GIVEN: the SplashScreen class + # WHEN: An object is created + + ss = SplashScreen() + # THEN: Nothing should go wrong and the instance should have the correct values + assert ss.objectName() == 'splashScreen', 'The ObjectName should have be ' \ + 'splashScreen' + assert ss.frameSize() == QtCore.QSize(370, 370), 'The frameSize should be (370, 370)' + assert ss.contextMenuPolicy() == QtCore.Qt.PreventContextMenu, 'The ContextMenuPolicy ' \ + 'should have been QtCore.Qt.PreventContextMenu or 4' diff --git a/tests/functional/openlp_core/ui/test_themeform.py b/tests/functional/openlp_core/ui/test_themeform.py index c05b9a2b7..bbb205f0a 100644 --- a/tests/functional/openlp_core/ui/test_themeform.py +++ b/tests/functional/openlp_core/ui/test_themeform.py @@ -28,9 +28,9 @@ from unittest.mock import MagicMock, patch from openlp.core.ui.themeform import ThemeForm -class TestThemeManager(TestCase): +class TestThemeForm(TestCase): """ - Test the functions in the ThemeManager Class + Test the functions in the ThemeForm Class """ def setUp(self): with patch('openlp.core.ui.themeform.ThemeForm._setup'): diff --git a/tests/interfaces/openlp_core/ui/test_thememanager.py b/tests/interfaces/openlp_core/ui/test_thememanager.py index a5b5a88e5..df47d1d81 100644 --- a/tests/interfaces/openlp_core/ui/test_thememanager.py +++ b/tests/interfaces/openlp_core/ui/test_thememanager.py @@ -58,19 +58,16 @@ class TestThemeManager(TestCase, TestMixin): # GIVEN: A new a call to initialise self.theme_manager.setup_ui = MagicMock() self.theme_manager.build_theme_path = MagicMock() - self.theme_manager.load_first_time_themes = MagicMock() self.theme_manager.upgrade_themes = MagicMock() Settings().setValue('themes/global theme', 'my_theme') # WHEN: the initialisation is run - with patch('openlp.core.ui.thememanager.ThemeProgressForm'): - self.theme_manager.bootstrap_initialise() + self.theme_manager.bootstrap_initialise() # THEN: self.theme_manager.setup_ui.assert_called_once_with(self.theme_manager) assert self.theme_manager.global_theme == 'my_theme' self.theme_manager.build_theme_path.assert_called_once_with() - self.theme_manager.load_first_time_themes.assert_called_once_with() self.theme_manager.upgrade_themes.assert_called_once_with() @patch('openlp.core.ui.thememanager.create_paths') @@ -117,7 +114,8 @@ class TestThemeManager(TestCase, TestMixin): self.theme_manager.theme_path = MagicMock() # WHEN: - self.theme_manager.bootstrap_post_set_up() + with patch('openlp.core.ui.thememanager.ThemeProgressForm'): + self.theme_manager.bootstrap_post_set_up() # THEN: assert 1 == self.theme_manager.load_themes.call_count, "load_themes should have been called once" diff --git a/tests/js/test_display.js b/tests/js/test_display.js index a8b50dfa1..857e78507 100644 --- a/tests/js/test_display.js +++ b/tests/js/test_display.js @@ -113,22 +113,18 @@ describe("The Display object", function () { expect(Display.reinit).toBeDefined(); }); - it("should re-initialise Reveal when reinit is called", function () { - spyOn(Reveal, "reinitialize"); + it("should sync Reveal and set to first slide when reinit is called", function () { + spyOn(Reveal, "sync"); + spyOn(Reveal, "slide"); Display.reinit(); - expect(Reveal.reinitialize).toHaveBeenCalled(); + expect(Reveal.sync).toHaveBeenCalled(); + expect(Reveal.slide).toHaveBeenCalledWith(0); }); it("should have a setTransition() method", function () { expect(Display.setTransition).toBeDefined(); }); - it("should have a correctly functioning setTransition() method", function () { - spyOn(Reveal, "configure"); - Display.setTransition("fade"); - expect(Reveal.configure).toHaveBeenCalledWith({"transition": "fade"}); - }); - it("should have a correctly functioning clearSlides() method", function () { expect(Display.clearSlides).toBeDefined(); @@ -156,6 +152,55 @@ describe("The Display object", function () { }); +describe("Transitions", function () { + beforeEach(function() { + document.body.innerHTML = ""; + _createDiv({"class": "slides"}); + _createDiv({"class": "footer"}); + _createDiv({"id": "global-background"}); + Display._slides = {}; + }); + afterEach(function() { + // Reset theme + Display._theme = null; + }); + + it("should have a correctly functioning setTransition() method", function () { + spyOn(Reveal, "configure"); + Display.setTransition("fade", "slow"); + expect(Reveal.configure).toHaveBeenCalledWith({"transition": "fade", "transitionSpeed": "slow"}); + }); + + it("should have enabled transitions when _doTransitions is true and setTheme is run", function () { + spyOn(Display, "setTransition"); + Display._doTransitions = true; + var theme = { + "display_slide_transition": true, + "display_slide_transition_type": TransitionType.Slide, + "display_slide_transition_speed": TransitionSpeed.Fast + } + + Display.setTheme(theme); + + expect(Display.setTransition).toHaveBeenCalledWith("slide", "fast"); + }); + + it("should have not enabled transitions when init() with no transitions and setTheme is run", function () { + spyOn(Display, "setTransition"); + Display._doTransitions = false; + var theme = { + "display_slide_transition": true, + "display_slide_transition_type": TransitionType.Slide, + "display_slide_transition_speed": TransitionSpeed.Fast, + } + + Display.setTheme(theme); + + expect(Display.setTransition).toHaveBeenCalledWith("none", "default"); + }); + +}); + describe("Display.alert", function () { var alertContainer, alertBackground, alertText, settings, text; @@ -519,7 +564,6 @@ describe("Display.setTextSlides", function () { ]; spyOn(Display, "clearSlides"); spyOn(Display, "reinit"); - spyOn(Reveal, "slide"); Display.setTextSlides(slides); @@ -528,7 +572,6 @@ describe("Display.setTextSlides", function () { expect(Display._slides["v2"]).toEqual(1); expect($(".slides > section > section").length).toEqual(2); expect(Display.reinit).toHaveBeenCalledTimes(1); - expect(Reveal.slide).toHaveBeenCalledWith(0, 0); }); it("should correctly set outline width", function () { @@ -583,6 +626,56 @@ describe("Display.setTextSlides", function () { expect(slidesDiv.style['justify-content']).toEqual('center'); }) + it("should enable shadows", function () { + const slides = [ + { + "verse": "v1", + "text": "Amazing grace, how sweet the sound\nThat saved a wretch like me\n" + + "I once was lost, but now I'm found\nWas blind but now I see", + "footer": "Public Domain" + } + ]; + // + const theme = { + 'font_main_shadow': true, + 'font_main_shadow_color': "#000", + 'font_main_shadow_size': 5 + }; + spyOn(Display, "reinit"); + spyOn(Reveal, "slide"); + + Display.setTheme(theme); + Display.setTextSlides(slides); + + const slidesDiv = $(".text-slides")[0]; + expect(slidesDiv.style['text-shadow']).not.toEqual(''); + }) + + it("should not enable shadows", function () { + const slides = [ + { + "verse": "v1", + "text": "Amazing grace, how sweet the sound\nThat saved a wretch like me\n" + + "I once was lost, but now I'm found\nWas blind but now I see", + "footer": "Public Domain" + } + ]; + // + const theme = { + 'font_main_shadow': false, + 'font_main_shadow_color': "#000", + 'font_main_shadow_size': 5 + }; + spyOn(Display, "reinit"); + spyOn(Reveal, "slide"); + + Display.setTheme(theme); + Display.setTextSlides(slides); + + const slidesDiv = $(".text-slides")[0]; + expect(slidesDiv.style['text-shadow']).toEqual(''); + }) + it("should correctly set slide size position to theme size when adding a text slide", function () { const slides = [ { diff --git a/tests/openlp_core/ui/test_themeprogressform.py b/tests/openlp_core/ui/test_themeprogressform.py new file mode 100644 index 000000000..dfb91f496 --- /dev/null +++ b/tests/openlp_core/ui/test_themeprogressform.py @@ -0,0 +1,142 @@ +# -*- 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 . # +########################################################################## +""" +Package to test the openlp.core.ui.themeform package. +""" +# from pathlib import Path +from unittest import TestCase +from unittest.mock import MagicMock, patch + +from openlp.core.ui.themeprogressform import ThemeProgressForm +from tests.helpers.testmixin import TestMixin + + +class TestThemeProgressForm(TestCase, TestMixin): + """ + Test the functions in the ThemeProgressForm class + """ + def setUp(self): + self.setup_application() + + def tearDown(self): + del self.app + + def _get_theme_progress_form(self): + """Common code used to create the ThemeProgressForm object""" + with patch('openlp.core.ui.themeprogressdialog.ThemePreviewRenderer'), \ + patch('openlp.core.ui.themeprogressdialog.UiThemeProgressDialog.setup_ui'): + form = ThemeProgressForm() + return form + + def test_init(self): + """Test that the ThemeProgressForm is created without problems""" + # GIVEN: ThemeProgressForm class + # WHEN: An object is instatiated + # THEN: There is no problem + self._get_theme_progress_form() + + @patch('openlp.core.ui.themeprogressform.ScreenList') + @patch('openlp.core.ui.themeprogressform.QtWidgets.QDialog.show') + def test_show(self, mocked_show, MockScreenList): + """Test that the ThemeProgressForm is created without problems""" + # GIVEN: ThemeProgressForm object + form = self._get_theme_progress_form() + mocked_screen_list = MagicMock() + mocked_screen_list.current.display_geometry.width.return_value = 1920 + mocked_screen_list.current.display_geometry.height.return_value = 1080 + MockScreenList.return_value = mocked_screen_list + form.progress_bar = MagicMock() + form.theme_preview_layout = MagicMock() + + # WHEN: The show() method is called + form.show() + + # THEN: The correct display ratio is calculated and the form is shown + expected_ratio = 16 / 9 + form.progress_bar.setMinimum.assert_called_once_with(0) + form.progress_bar.setMaximum.assert_called_once_with(0) + form.progress_bar.setValue.assert_called_once_with(0) + assert form.ratio == expected_ratio + assert form.theme_preview_layout.aspect_ratio == expected_ratio + mocked_show.assert_called_once() + + @patch('openlp.core.ui.themeprogressform.ScreenList') + @patch('openlp.core.ui.themeprogressform.QtWidgets.QDialog.show') + def test_show_divide_by_zero(self, mocked_show, MockScreenList): + """Test that the ThemeProgressForm is created without problems even if there's a divide by zero exception""" + # GIVEN: ThemeProgressForm object + form = self._get_theme_progress_form() + mocked_screen_list = MagicMock() + mocked_screen_list.current.display_geometry.width.return_value = 1920 + mocked_screen_list.current.display_geometry.height.return_value = 0 + MockScreenList.return_value = mocked_screen_list + form.progress_bar = MagicMock() + form.theme_preview_layout = MagicMock() + + # WHEN: The show() method is called + form.show() + + # THEN: The correct display ratio is calculated and the form is shown + expected_ratio = 16 / 9 + form.progress_bar.setMinimum.assert_called_once_with(0) + form.progress_bar.setMaximum.assert_called_once_with(0) + form.progress_bar.setValue.assert_called_once_with(0) + assert form.ratio == expected_ratio + assert form.theme_preview_layout.aspect_ratio == expected_ratio + mocked_show.assert_called_once() + + def test_get_preview(self): + """Test that the get_preview() method returns a preview image""" + # GIVEN: ThemeProgressForm object + test_theme_name = 'Test Theme' + test_theme_data = {'name': test_theme_name} + form = self._get_theme_progress_form() + form.progress_bar = MagicMock(**{'value.return_value': 0}) + form.label = MagicMock() + form.theme_display = MagicMock(**{'width.return_value': 192, 'generate_preview.return_value': 'preview'}) + form.renderer.width = MagicMock(return_value=1920) + + # WHEN: get_preview() is called + preview = form.get_preview(test_theme_name, test_theme_data) + + # THEN: All the correct methods should be called and the correct results should be returned + form.label.setText.assert_called_once_with(test_theme_name) + form.progress_bar.value.assert_called_once() + form.progress_bar.setValue.assert_called_once_with(1) + form.theme_display.width.assert_called_once() + form.renderer.width.assert_called_once() + form.theme_display.set_scale.assert_called_once_with(0.1) + form.theme_display.generate_preview.assert_called_once_with(test_theme_data, generate_screenshot=True) + assert preview == 'preview' + + def test_theme_list(self): + # GIVEN: ThemeProgressForm object and theme list + test_theme_list = ['Theme 1', 'Theme 2'] + form = self._get_theme_progress_form() + form.progress_bar = MagicMock() + + # WHEN: theme_list is set and get'ed + form.theme_list = test_theme_list + theme_list = form.theme_list + + # THEN: The theme list should be correct + form.progress_bar.setMaximum.assert_called_once_with(2) + assert theme_list == test_theme_list From a9d4fb99ddc1d6e60bcc536b026a99b740699174 Mon Sep 17 00:00:00 2001 From: Tim Date: Tue, 24 Dec 2019 06:50:18 +0000 Subject: [PATCH 5/5] Fix file attributes --- openlp/plugins/planningcenter/forms/selectplanform.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 openlp/plugins/planningcenter/forms/selectplanform.py diff --git a/openlp/plugins/planningcenter/forms/selectplanform.py b/openlp/plugins/planningcenter/forms/selectplanform.py old mode 100755 new mode 100644