diff --git a/openlp/core/lib/theme.py b/openlp/core/lib/theme.py index 3c3ec1e24..a840a4a3c 100644 --- a/openlp/core/lib/theme.py +++ b/openlp/core/lib/theme.py @@ -214,6 +214,20 @@ class HorizontalType(object): Names = ['left', 'right', 'center', 'justify'] + @staticmethod + def to_string(align): + """ + Return a string representation of the alignment + """ + return HorizontalType.Names[align] + + @staticmethod + def from_string(align): + """ + Return an alignment for a given string + """ + return HorizontalType.Names.index(align) + class VerticalType(object): """ @@ -225,6 +239,20 @@ class VerticalType(object): Names = ['top', 'middle', 'bottom'] + @staticmethod + def to_string(align): + """ + Return a string representation of the alignment + """ + return VerticalType.Names[align] + + @staticmethod + def from_string(align): + """ + Return an alignment for a given string + """ + return VerticalType.Names.index(align) + BOOLEAN_LIST = ['bold', 'italics', 'override', 'outline', 'shadow', 'slide_transition'] diff --git a/openlp/core/lib/ui.py b/openlp/core/lib/ui.py index bcf3300ac..3190fe76e 100644 --- a/openlp/core/lib/ui.py +++ b/openlp/core/lib/ui.py @@ -31,6 +31,7 @@ from openlp.core.common.i18n import UiStrings, translate from openlp.core.common.registry import Registry from openlp.core.lib import build_icon from openlp.core.ui.icons import UiIcons +from openlp.core.widgets.labels import FormLabel log = logging.getLogger(__name__) @@ -306,7 +307,7 @@ def create_valign_selection_widgets(parent): :param parent: The parent object. This should be a ``QWidget`` descendant. """ - label = QtWidgets.QLabel(parent) + label = FormLabel(parent) label.setText(translate('OpenLP.Ui', '&Vertical Align:')) combo_box = QtWidgets.QComboBox(parent) combo_box.addItems([UiStrings().Top, UiStrings().Middle, UiStrings().Bottom]) diff --git a/openlp/core/pages/__init__.py b/openlp/core/pages/__init__.py new file mode 100644 index 000000000..f282672cc --- /dev/null +++ b/openlp/core/pages/__init__.py @@ -0,0 +1,61 @@ +# -*- 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 :mod:`~openlp.core.pages` module contains wizard pages used in OpenLP +""" +from PyQt5 import QtWidgets + + +class GridLayoutPage(QtWidgets.QWizardPage): + """ + A class that has a QGridLayout for its layout which automatically ensure all columns are equal width + """ + def __init__(self, parent=None): + super().__init__(parent) + self._column_width = 0 + self.layout = QtWidgets.QGridLayout(self) + self.setup_ui() + self.retranslate_ui() + self.resize_columns() + + def resizeEvent(self, event): + """ + Override inherited resize method + """ + super().resizeEvent(event) + self.resize_columns() + + def resize_columns(self): + """ + Resize all the column widths + """ + width = self.layout.contentsRect().width() + spacing = self.layout.horizontalSpacing() + column_count = self.layout.columnCount() + self._column_width = (width - (spacing * (column_count - 1))) // column_count + for column_number in range(column_count): + self.layout.setColumnMinimumWidth(column_number, self._column_width) + + def setup_ui(self): + raise NotImplementedError('Descendant pages need to implement setup_ui') + + def retranslate_ui(self): + raise NotImplementedError('Descendant pages need to implement retranslate_ui') diff --git a/openlp/core/pages/alignment.py b/openlp/core/pages/alignment.py new file mode 100644 index 000000000..b25a4e305 --- /dev/null +++ b/openlp/core/pages/alignment.py @@ -0,0 +1,169 @@ +# -*- 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 :mod:`~openlp.core.pages.alignment` module contains the alignment page used in the theme wizard +""" +from PyQt5 import QtWidgets + +from openlp.core.common.i18n import translate +from openlp.core.lib.theme import HorizontalType, VerticalType, TransitionType, TransitionSpeed +from openlp.core.lib.ui import create_valign_selection_widgets +from openlp.core.pages import GridLayoutPage +from openlp.core.widgets.labels import FormLabel + + +class AlignmentTransitionsPage(GridLayoutPage): + """ + A widget containing the alignment and transitions options + """ + def setup_ui(self): + """ + Set up the UI + """ + # Alignment + self.horizontal_label = FormLabel(self) + self.horizontal_label.setObjectName('horizontal_label') + self.layout.addWidget(self.horizontal_label, 0, 0) + self.horizontal_combo_box = QtWidgets.QComboBox(self) + self.horizontal_combo_box.addItems(['', '', '', '']) + self.horizontal_combo_box.setObjectName('horizontal_combo_box') + self.layout.addWidget(self.horizontal_combo_box, 0, 1, 1, 3) + self.vertical_label, self.vertical_combo_box = create_valign_selection_widgets(self) + self.vertical_label.setObjectName('vertical_label') + self.layout.addWidget(self.vertical_label, 1, 0) + self.vertical_combo_box.setObjectName('vertical_combo_box') + self.layout.addWidget(self.vertical_combo_box, 1, 1, 1, 3) + # Line + self.line = QtWidgets.QFrame(self) + self.line.setFrameShape(QtWidgets.QFrame.HLine) + self.line.setObjectName('line') + self.layout.addWidget(self.line, 2, 0, 1, 4) + # Transitions + self.transitions_enabled_check_box = QtWidgets.QCheckBox(self) + self.transitions_enabled_check_box.setObjectName('transitions_enabled_check_box') + self.layout.addWidget(self.transitions_enabled_check_box, 3, 1) + self.transition_effect_label = FormLabel(self) + self.transition_effect_label.setObjectName('transition_effect_label') + self.layout.addWidget(self.transition_effect_label, 4, 0) + self.transition_effect_combo_box = QtWidgets.QComboBox(self) + self.transition_effect_combo_box.setObjectName('transition_effect_combo_box') + self.transition_effect_combo_box.addItems(['', '', '', '', '']) + self.layout.addWidget(self.transition_effect_combo_box, 4, 1) + self.transition_speed_label = FormLabel(self) + self.transition_speed_label.setObjectName('transition_speed_label') + self.layout.addWidget(self.transition_speed_label, 4, 2) + self.transition_speed_combo_box = QtWidgets.QComboBox(self) + self.transition_speed_combo_box.setObjectName('transition_speed_combo_box') + self.transition_speed_combo_box.addItems(['', '', '']) + self.layout.addWidget(self.transition_speed_combo_box, 4, 3) + # Connect slots + self.transitions_enabled_check_box.stateChanged.connect(self._on_transition_enabled_changed) + + def retranslate_ui(self): + """ + Translate the widgets + """ + self.horizontal_label.setText(translate('OpenLP.ThemeWizard', 'Horizontal Align:')) + self.horizontal_combo_box.setItemText(HorizontalType.Left, translate('OpenLP.ThemeWizard', 'Left')) + 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_enabled_check_box.setText(translate('OpenLP.ThemeWizard', 'Enable transitions')) + self.transition_effect_label.setText(translate('OpenLP.ThemeWizard', 'Effect:')) + self.transition_effect_combo_box.setItemText(TransitionType.Fade, translate('OpenLP.ThemeWizard', 'Fade')) + self.transition_effect_combo_box.setItemText(TransitionType.Slide, translate('OpenLP.ThemeWizard', 'Slide')) + self.transition_effect_combo_box.setItemText(TransitionType.Concave, translate('OpenLP.ThemeWizard', 'Concave')) + self.transition_effect_combo_box.setItemText(TransitionType.Convex, translate('OpenLP.ThemeWizard', 'Convex')) + self.transition_effect_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')) + + def _on_transition_enabled_changed(self, value): + """ + Update the UI widgets when the transition is enabled or disabled + """ + self.transition_effect_label.setEnabled(value) + self.transition_effect_combo_box.setEnabled(value) + self.transition_speed_label.setEnabled(value) + self.transition_speed_combo_box.setEnabled(value) + + @property + def horizontal_align(self): + return self.horizontal_combo_box.currentIndex() + + @horizontal_align.setter + def horizontal_align(self, value): + if isinstance(value, str): + self.horizontal_combo_box.setCurrentIndex(HorizontalType.from_string(value)) + elif isinstance(value, int): + self.horizontal_combo_box.setCurrentIndex(value) + else: + raise TypeError('horizontal_align must either be a string or an int') + + @property + def vertical_align(self): + return self.vertical_combo_box.currentIndex() + + @vertical_align.setter + def vertical_align(self, value): + if isinstance(value, str): + self.vertical_combo_box.setCurrentIndex(VerticalType.from_string(value)) + elif isinstance(value, int): + self.vertical_combo_box.setCurrentIndex(value) + else: + raise TypeError('vertical_align must either be a string or an int') + + @property + def is_transition_enabled(self): + return self.transitions_enabled_check_box.isChecked() + + @is_transition_enabled.setter + def is_transition_enabled(self, value): + self.transitions_enabled_check_box.setChecked(value) + self._on_transition_enabled_changed(value) + + @property + def transition_type(self): + return self.transition_effect_combo_box.currentIndex() + + @transition_type.setter + def transition_type(self, value): + if isinstance(value, str): + self.transition_effect_combo_box.setCurrentIndex(TransitionType.from_string(value)) + elif isinstance(value, int): + self.transition_effect_combo_box.setCurrentIndex(value) + else: + raise TypeError('transition_type must either be a string or an int') + + @property + def transition_speed(self): + return self.transition_speed_combo_box.currentIndex() + + @transition_speed.setter + def transition_speed(self, value): + if isinstance(value, str): + self.transition_speed_combo_box.setCurrentIndex(TransitionSpeed.from_string(value)) + elif isinstance(value, int): + self.transition_speed_combo_box.setCurrentIndex(value) + else: + raise TypeError('transition_speed must either be a string or an int') diff --git a/openlp/core/pages/areaposition.py b/openlp/core/pages/areaposition.py new file mode 100644 index 000000000..37715e6fc --- /dev/null +++ b/openlp/core/pages/areaposition.py @@ -0,0 +1,226 @@ +# -*- 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 :mod:`~openlp.core.pages.areaposition` module contains the area position page used in the theme wizard +""" +from PyQt5 import QtWidgets + +from openlp.core.common.i18n import translate +from openlp.core.pages import GridLayoutPage +from openlp.core.widgets.labels import FormLabel + + +class AreaPositionPage(GridLayoutPage): + """ + A wizard page for the area positioning widgets in the theme wizard + """ + def setup_ui(self): + """ + Set up the UI + """ + # Main area position + self.main_position_group_box = QtWidgets.QGroupBox(self) + self.main_position_group_box.setObjectName('main_position_group_box') + self.main_position_layout = QtWidgets.QGridLayout(self.main_position_group_box) + self.main_position_layout.setObjectName('main_position_layout') + self.main_position_check_box = QtWidgets.QCheckBox(self.main_position_group_box) + self.main_position_check_box.setObjectName('main_position_check_box') + self.main_position_layout.addWidget(self.main_position_check_box, 0, 0, 1, 2) + self.main_x_label = FormLabel(self.main_position_group_box) + self.main_x_label.setObjectName('main_x_label') + self.main_position_layout.addWidget(self.main_x_label, 1, 0) + self.main_x_spin_box = QtWidgets.QSpinBox(self.main_position_group_box) + self.main_x_spin_box.setMaximum(9999) + self.main_x_spin_box.setObjectName('main_x_spin_box') + self.main_position_layout.addWidget(self.main_x_spin_box, 1, 1) + self.main_y_label = FormLabel(self.main_position_group_box) + self.main_y_label.setObjectName('main_y_label') + self.main_position_layout.addWidget(self.main_y_label, 2, 0) + self.main_y_spin_box = QtWidgets.QSpinBox(self.main_position_group_box) + self.main_y_spin_box.setMaximum(9999) + self.main_y_spin_box.setObjectName('main_y_spin_box') + self.main_position_layout.addWidget(self.main_y_spin_box, 2, 1) + self.main_width_label = FormLabel(self.main_position_group_box) + self.main_width_label.setObjectName('main_width_label') + self.main_width_spin_box = QtWidgets.QSpinBox(self.main_position_group_box) + self.main_width_spin_box.setMaximum(9999) + self.main_width_spin_box.setObjectName('main_width_spin_box') + self.main_position_layout.addWidget(self.main_width_label, 3, 0) + self.main_position_layout.addWidget(self.main_width_spin_box, 3, 1) + self.main_height_label = FormLabel(self.main_position_group_box) + self.main_height_label.setObjectName('main_height_label') + self.main_height_spin_box = QtWidgets.QSpinBox(self.main_position_group_box) + self.main_height_spin_box.setMaximum(9999) + self.main_height_spin_box.setObjectName('main_height_spin_box') + self.main_position_layout.addWidget(self.main_height_label, 4, 0) + self.main_position_layout.addWidget(self.main_height_spin_box, 4, 1) + self.layout.addWidget(self.main_position_group_box, 0, 0) + # Footer area position + self.footer_position_group_box = QtWidgets.QGroupBox(self) + self.footer_position_group_box.setObjectName('footer_position_group_box') + self.footer_position_layout = QtWidgets.QGridLayout(self.footer_position_group_box) + self.footer_position_layout.setObjectName('footer_position_layout') + self.footer_position_check_box = QtWidgets.QCheckBox(self.footer_position_group_box) + self.footer_position_check_box.setObjectName('footer_position_check_box') + self.footer_position_layout.addWidget(self.footer_position_check_box, 0, 0, 1, 2) + self.footer_x_label = FormLabel(self.footer_position_group_box) + self.footer_x_label.setObjectName('footer_x_label') + self.footer_x_spin_box = QtWidgets.QSpinBox(self.footer_position_group_box) + self.footer_x_spin_box.setMaximum(9999) + self.footer_x_spin_box.setObjectName('footer_x_spin_box') + self.footer_position_layout.addWidget(self.footer_x_label, 1, 0) + self.footer_position_layout.addWidget(self.footer_x_spin_box, 1, 1) + self.footer_y_label = FormLabel(self.footer_position_group_box) + self.footer_y_label.setObjectName('footer_y_label') + self.footer_y_spin_box = QtWidgets.QSpinBox(self.footer_position_group_box) + self.footer_y_spin_box.setMaximum(9999) + self.footer_y_spin_box.setObjectName('footer_y_spin_box') + self.footer_position_layout.addWidget(self.footer_y_label, 2, 0) + self.footer_position_layout.addWidget(self.footer_y_spin_box, 2, 1) + self.footer_width_label = FormLabel(self.footer_position_group_box) + self.footer_width_label.setObjectName('footer_width_label') + self.footer_width_spin_box = QtWidgets.QSpinBox(self.footer_position_group_box) + self.footer_width_spin_box.setMaximum(9999) + self.footer_width_spin_box.setObjectName('footer_width_spin_box') + self.footer_position_layout.addWidget(self.footer_width_label, 3, 0) + self.footer_position_layout.addWidget(self.footer_width_spin_box, 3, 1) + self.footer_height_label = FormLabel(self.footer_position_group_box) + self.footer_height_label.setObjectName('footer_height_label') + self.footer_height_spin_box = QtWidgets.QSpinBox(self.footer_position_group_box) + self.footer_height_spin_box.setMaximum(9999) + self.footer_height_spin_box.setObjectName('footer_height_spin_box') + self.footer_position_layout.addWidget(self.footer_height_label, 4, 0) + self.footer_position_layout.addWidget(self.footer_height_spin_box, 4, 1) + self.layout.addWidget(self.footer_position_group_box, 0, 1) + # Connect signals to slots + self.main_position_check_box.toggled.connect(self.main_x_spin_box.setDisabled) + self.main_position_check_box.toggled.connect(self.main_y_spin_box.setDisabled) + self.main_position_check_box.toggled.connect(self.main_width_spin_box.setDisabled) + self.main_position_check_box.toggled.connect(self.main_height_spin_box.setDisabled) + self.footer_position_check_box.toggled.connect(self.footer_x_spin_box.setDisabled) + self.footer_position_check_box.toggled.connect(self.footer_y_spin_box.setDisabled) + self.footer_position_check_box.toggled.connect(self.footer_width_spin_box.setDisabled) + self.footer_position_check_box.toggled.connect(self.footer_height_spin_box.setDisabled) + + def retranslate_ui(self): + """ + Translate the UI + """ + self.main_position_group_box.setTitle(translate('OpenLP.ThemeWizard', '&Main Area')) + self.main_position_check_box.setText(translate('OpenLP.ThemeWizard', '&Use default location')) + self.main_x_label.setText(translate('OpenLP.ThemeWizard', 'X position:')) + self.main_x_spin_box.setSuffix(translate('OpenLP.ThemeWizard', 'px')) + self.main_y_spin_box.setSuffix(translate('OpenLP.ThemeWizard', 'px')) + self.main_y_label.setText(translate('OpenLP.ThemeWizard', 'Y position:')) + self.main_width_spin_box.setSuffix(translate('OpenLP.ThemeWizard', 'px')) + self.main_width_label.setText(translate('OpenLP.ThemeWizard', 'Width:')) + self.main_height_spin_box.setSuffix(translate('OpenLP.ThemeWizard', 'px')) + self.main_height_label.setText(translate('OpenLP.ThemeWizard', 'Height:')) + self.footer_position_group_box.setTitle(translate('OpenLP.ThemeWizard', '&Footer Area')) + self.footer_x_label.setText(translate('OpenLP.ThemeWizard', 'X position:')) + self.footer_x_spin_box.setSuffix(translate('OpenLP.ThemeWizard', 'px')) + self.footer_y_label.setText(translate('OpenLP.ThemeWizard', 'Y position:')) + self.footer_y_spin_box.setSuffix(translate('OpenLP.ThemeWizard', 'px')) + self.footer_width_label.setText(translate('OpenLP.ThemeWizard', 'Width:')) + self.footer_width_spin_box.setSuffix(translate('OpenLP.ThemeWizard', 'px')) + self.footer_height_label.setText(translate('OpenLP.ThemeWizard', 'Height:')) + self.footer_height_spin_box.setSuffix(translate('OpenLP.ThemeWizard', 'px')) + self.footer_position_check_box.setText(translate('OpenLP.ThemeWizard', 'Use default location')) + + @property + def use_main_default_location(self): + return self.main_position_check_box.isChecked() + + @use_main_default_location.setter + def use_main_default_location(self, value): + self.main_position_check_box.setChecked(value) + + @property + def main_x(self): + return self.main_x_spin_box.value() + + @main_x.setter + def main_x(self, value): + self.main_x_spin_box.setValue(value) + + @property + def main_y(self): + return self.main_y_spin_box.value() + + @main_y.setter + def main_y(self, value): + self.main_y_spin_box.setValue(value) + + @property + def main_width(self): + return self.main_width_spin_box.value() + + @main_width.setter + def main_width(self, value): + self.main_width_spin_box.setValue(value) + + @property + def main_height(self): + return self.main_height_spin_box.value() + + @main_height.setter + def main_height(self, value): + self.main_height_spin_box.setValue(value) + + @property + def use_footer_default_location(self): + return self.footer_position_check_box.isChecked() + + @use_footer_default_location.setter + def use_footer_default_location(self, value): + self.footer_position_check_box.setChecked(value) + + @property + def footer_x(self): + return self.footer_x_spin_box.value() + + @footer_x.setter + def footer_x(self, value): + self.footer_x_spin_box.setValue(value) + + @property + def footer_y(self): + return self.footer_y_spin_box.value() + + @footer_y.setter + def footer_y(self, value): + self.footer_y_spin_box.setValue(value) + + @property + def footer_width(self): + return self.footer_width_spin_box.value() + + @footer_width.setter + def footer_width(self, value): + self.footer_width_spin_box.setValue(value) + + @property + def footer_height(self): + return self.footer_height_spin_box.value() + + @footer_height.setter + def footer_height(self, value): + self.footer_height_spin_box.setValue(value) diff --git a/openlp/core/pages/background.py b/openlp/core/pages/background.py new file mode 100644 index 000000000..1814630af --- /dev/null +++ b/openlp/core/pages/background.py @@ -0,0 +1,253 @@ +# -*- 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 :mod:`~openlp.core.pages.background` module contains the background page used in the theme wizard +""" +from PyQt5 import QtWidgets + +from openlp.core.common import get_images_filter +from openlp.core.common.i18n import UiStrings, translate +from openlp.core.lib.theme import BackgroundGradientType, BackgroundType +from openlp.core.pages import GridLayoutPage +from openlp.core.ui.media import VIDEO_EXT +from openlp.core.widgets.buttons import ColorButton +from openlp.core.widgets.edits import PathEdit +from openlp.core.widgets.labels import FormLabel + + +class BackgroundPage(GridLayoutPage): + """ + A background selection widget + """ + Color = 'color' + Gradient = 'gradient' + Image = 'image' + Video = 'video' + + def setup_ui(self): + """ + Set up the ui + """ + # background type + self.background_label = FormLabel(self) + self.background_label.setObjectName('background_label') + self.layout.addWidget(self.background_label, 0, 0) + self.background_combo_box = QtWidgets.QComboBox(self) + self.background_combo_box.addItems(['', '', '', '']) + self.background_combo_box.setObjectName('background_combo_box') + self.layout.addWidget(self.background_combo_box, 0, 1, 1, 3) + # color + self.color_label = FormLabel(self) + self.color_label.setObjectName('color_label') + self.layout.addWidget(self.color_label, 1, 0) + self.color_button = ColorButton(self) + self.color_button.setObjectName('color_button') + self.layout.addWidget(self.color_button, 1, 1) + self.color_widgets = [self.color_label, self.color_button] + # gradient + self.gradient_type_label = FormLabel(self) + self.gradient_type_label.setObjectName('gradient_type_label') + self.layout.addWidget(self.gradient_type_label, 2, 0) + self.gradient_combo_box = QtWidgets.QComboBox(self) + self.gradient_combo_box.setObjectName('gradient_combo_box') + self.gradient_combo_box.addItems(['', '', '', '', '']) + self.layout.addWidget(self.gradient_combo_box, 2, 1, 1, 3) + self.gradient_start_label = FormLabel(self) + self.gradient_start_label.setObjectName('gradient_start_label') + self.layout.addWidget(self.gradient_start_label, 3, 0) + self.gradient_start_button = ColorButton(self) + self.gradient_start_button.setObjectName('gradient_start_button') + self.layout.addWidget(self.gradient_start_button, 3, 1) + self.gradient_end_label = FormLabel(self) + self.gradient_end_label.setObjectName('gradient_end_label') + self.layout.addWidget(self.gradient_end_label, 3, 2) + self.gradient_end_button = ColorButton(self) + self.gradient_end_button.setObjectName('gradient_end_button') + self.layout.addWidget(self.gradient_end_button, 3, 3) + self.gradient_widgets = [self.gradient_type_label, self.gradient_combo_box, self.gradient_start_label, + self.gradient_start_button, self.gradient_end_label, self.gradient_end_button] + # image + self.image_label = FormLabel(self) + self.image_label.setObjectName('image_label') + self.layout.addWidget(self.image_label, 4, 0) + self.image_path_edit = PathEdit(self, dialog_caption=translate('OpenLP.ThemeWizard', 'Select Image'), + show_revert=False) + self.layout.addWidget(self.image_path_edit, 4, 1, 1, 3) + self.image_color_label = FormLabel(self) + self.image_color_label.setObjectName('image_color_label') + self.layout.addWidget(self.image_color_label, 5, 0) + self.image_color_button = ColorButton(self) + self.image_color_button.color = '#000000' + self.image_color_button.setObjectName('image_color_button') + self.layout.addWidget(self.image_color_button, 5, 1) + self.image_widgets = [self.image_label, self.image_path_edit, self.image_color_label, self.image_color_button] + # video + self.video_label = FormLabel(self) + self.video_label.setObjectName('video_label') + self.layout.addWidget(self.video_label, 6, 0) + self.video_path_edit = PathEdit(self, dialog_caption=translate('OpenLP.ThemeWizard', 'Select Video'), + show_revert=False) + self.layout.addWidget(self.video_path_edit, 6, 1, 1, 3) + self.video_color_label = FormLabel(self) + self.video_color_label.setObjectName('video_color_label') + self.layout.addWidget(self.video_color_label, 7, 0) + self.video_color_button = ColorButton(self) + self.video_color_button.color = '#000000' + self.video_color_button.setObjectName('video_color_button') + self.layout.addWidget(self.video_color_button, 7, 1) + self.video_widgets = [self.video_label, self.video_path_edit, self.video_color_label, self.video_color_button] + # Force everything up + self.layout_spacer = QtWidgets.QSpacerItem(1, 1) + self.layout.addItem(self.layout_spacer, 8, 0, 1, 4) + # Connect slots + self.background_combo_box.currentIndexChanged.connect(self._on_background_type_index_changed) + # Force the first set of widgets to show + self._on_background_type_index_changed(0) + + def retranslate_ui(self): + """ + Translate the text elements of the widget + """ + self.background_label.setText(translate('OpenLP.ThemeWizard', 'Background type:')) + self.background_combo_box.setItemText(BackgroundType.Solid, translate('OpenLP.ThemeWizard', 'Solid color')) + self.background_combo_box.setItemText(BackgroundType.Gradient, translate('OpenLP.ThemeWizard', 'Gradient')) + self.background_combo_box.setItemText(BackgroundType.Image, UiStrings().Image) + self.background_combo_box.setItemText(BackgroundType.Transparent, + translate('OpenLP.ThemeWizard', 'Transparent')) + self.color_label.setText(translate('OpenLP.ThemeWizard', 'Color:')) + self.gradient_start_label.setText(translate('OpenLP.ThemeWizard', 'Starting color:')) + self.gradient_end_label.setText(translate('OpenLP.ThemeWizard', 'Ending color:')) + self.gradient_type_label.setText(translate('OpenLP.ThemeWizard', 'Gradient:')) + self.gradient_combo_box.setItemText(BackgroundGradientType.Horizontal, + translate('OpenLP.ThemeWizard', 'Horizontal')) + self.gradient_combo_box.setItemText(BackgroundGradientType.Vertical, + translate('OpenLP.ThemeWizard', 'Vertical')) + self.gradient_combo_box.setItemText(BackgroundGradientType.Circular, + translate('OpenLP.ThemeWizard', 'Circular')) + self.gradient_combo_box.setItemText(BackgroundGradientType.LeftTop, + translate('OpenLP.ThemeWizard', 'Top Left - Bottom Right')) + self.gradient_combo_box.setItemText(BackgroundGradientType.LeftBottom, + translate('OpenLP.ThemeWizard', 'Bottom Left - Top Right')) + self.image_color_label.setText(translate('OpenLP.ThemeWizard', 'Background color:')) + self.image_label.setText('{text}:'.format(text=UiStrings().Image)) + self.video_color_label.setText(translate('OpenLP.ThemeWizard', 'Background color:')) + self.video_label.setText('{text}:'.format(text=UiStrings().Video)) + self.image_path_edit.filters = \ + '{name};;{text} (*)'.format(name=get_images_filter(), text=UiStrings().AllFiles) + visible_formats = '(*.{name})'.format(name='; *.'.join(VIDEO_EXT)) + actual_formats = '(*.{name})'.format(name=' *.'.join(VIDEO_EXT)) + video_filter = '{trans} {visible} {actual}'.format(trans=translate('OpenLP', 'Video Files'), + visible=visible_formats, actual=actual_formats) + self.video_path_edit.filters = '{video};;{ui} (*)'.format(video=video_filter, ui=UiStrings().AllFiles) + + def _on_background_type_index_changed(self, index): + """ + Hide and show widgets based on index + """ + widget_sets = [self.color_widgets, self.gradient_widgets, self.image_widgets, [], self.video_widgets] + for widgets in widget_sets: + for widget in widgets: + widget.hide() + if index < len(widget_sets): + for widget in widget_sets[index]: + widget.show() + + @property + def background_type(self): + return BackgroundType.to_string(self.background_combo_box.currentIndex()) + + @background_type.setter + def background_type(self, value): + if isinstance(value, str): + self.background_combo_box.setCurrentIndex(BackgroundType.from_string(value)) + elif isinstance(value, int): + self.background_combo_box.setCurrentIndex(value) + else: + raise TypeError('background_type must either be a string or an int') + + @property + def color(self): + return self.color_button.color + + @color.setter + def color(self, value): + self.color_button.color = value + + @property + def gradient_type(self): + return BackgroundGradientType.to_string(self.gradient_combo_box.currentIndex()) + + @gradient_type.setter + def gradient_type(self, value): + if isinstance(value, str): + self.gradient_combo_box.setCurrentIndex(BackgroundGradientType.from_string(value)) + elif isinstance(value, int): + self.gradient_combo_box.setCurrentIndex(value) + else: + raise TypeError('gradient_type must either be a string or an int') + + @property + def gradient_start(self): + return self.gradient_start_button.color + + @gradient_start.setter + def gradient_start(self, value): + self.gradient_start_button.color = value + + @property + def gradient_end(self): + return self.gradient_end_button.color + + @gradient_end.setter + def gradient_end(self, value): + self.gradient_end_button.color = value + + @property + def image_color(self): + return self.image_color_button.color + + @image_color.setter + def image_color(self, value): + self.image_color_button.color = value + + @property + def image_path(self): + return self.image_path_edit.path + + @image_path.setter + def image_path(self, value): + self.image_path_edit.path = value + + @property + def video_color(self): + return self.video_color_button.color + + @video_color.setter + def video_color(self, value): + self.video_color_button.color = value + + @property + def video_path(self): + return self.video_path_edit.path + + @video_path.setter + def video_path(self, value): + self.video_path_edit.path = value diff --git a/openlp/core/pages/fontselect.py b/openlp/core/pages/fontselect.py new file mode 100644 index 000000000..8242acb89 --- /dev/null +++ b/openlp/core/pages/fontselect.py @@ -0,0 +1,345 @@ +# -*- 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 :mod:`~openlp.core.pages.fontselect` module contains the font selection page used in the theme wizard +""" +from PyQt5 import QtCore, QtGui, QtWidgets + +from openlp.core.common.i18n import UiStrings, translate +from openlp.core.pages import GridLayoutPage +from openlp.core.ui.icons import UiIcons +from openlp.core.widgets.buttons import ColorButton +from openlp.core.widgets.labels import FormLabel + + +class FontSelectPage(GridLayoutPage): + """ + A font selection widget + """ + Outline = 'outline' + Shadow = 'shadow' + LineSpacing = 'line_spacing' + + font_name_changed = QtCore.pyqtSignal(str) + font_size_changed = QtCore.pyqtSignal(int) + font_color_changed = QtCore.pyqtSignal(str) + is_bold_changed = QtCore.pyqtSignal(bool) + is_italic_changed = QtCore.pyqtSignal(bool) + line_spacing_changed = QtCore.pyqtSignal(int) + is_outline_enabled_changed = QtCore.pyqtSignal(bool) + outline_color_changed = QtCore.pyqtSignal(str) + outline_size_changed = QtCore.pyqtSignal(int) + is_shadow_enabled_changed = QtCore.pyqtSignal(bool) + shadow_color_changed = QtCore.pyqtSignal(str) + shadow_size_changed = QtCore.pyqtSignal(int) + + def __init__(self, parent=None): + super().__init__(parent) + self.feature_widgets = { + FontSelectPage.Outline: [self.outline_groupbox], + FontSelectPage.Shadow: [self.shadow_groupbox], + FontSelectPage.LineSpacing: [self.line_spacing_label, self.line_spacing_spinbox] + } + + def setup_ui(self): + # Font name + self.font_name_label = FormLabel(self) + self.font_name_label.setObjectName('font_name_label') + self.layout.addWidget(self.font_name_label, 0, 0) + self.font_name_combobox = QtWidgets.QFontComboBox(self) + self.font_name_combobox.setObjectName('font_name_combobox') + self.layout.addWidget(self.font_name_combobox, 0, 1, 1, 3) + # Font color + self.font_color_label = FormLabel(self) + self.font_color_label.setObjectName('font_color_label') + self.layout.addWidget(self.font_color_label, 1, 0) + self.font_color_button = ColorButton(self) + self.font_color_button.setObjectName('font_color_button') + self.layout.addWidget(self.font_color_button, 1, 1) + # Font style + self.font_style_label = FormLabel(self) + self.font_style_label.setObjectName('font_style_label') + self.layout.addWidget(self.font_style_label, 1, 2) + self.style_layout = QtWidgets.QHBoxLayout() + self.style_bold_button = QtWidgets.QToolButton(self) + self.style_bold_button.setCheckable(True) + self.style_bold_button.setIcon(UiIcons().bold) + self.style_bold_button.setShortcut(QtGui.QKeySequence(QtGui.QKeySequence.Bold)) + self.style_bold_button.setObjectName('style_bold_button') + self.style_layout.addWidget(self.style_bold_button) + self.style_italic_button = QtWidgets.QToolButton(self) + self.style_italic_button.setCheckable(True) + self.style_italic_button.setIcon(UiIcons().italic) + self.style_italic_button.setShortcut(QtGui.QKeySequence(QtGui.QKeySequence.Italic)) + self.style_italic_button.setObjectName('style_italic_button') + self.style_layout.addWidget(self.style_italic_button) + self.style_layout.addStretch(1) + self.layout.addLayout(self.style_layout, 1, 3) + # Font size + self.font_size_label = FormLabel(self) + self.font_size_label.setObjectName('font_size_label') + self.layout.addWidget(self.font_size_label, 2, 0) + self.font_size_spinbox = QtWidgets.QSpinBox(self) + self.font_size_spinbox.setMaximum(999) + self.font_size_spinbox.setValue(16) + self.font_size_spinbox.setObjectName('font_size_spinbox') + self.layout.addWidget(self.font_size_spinbox, 2, 1) + # Line spacing + self.line_spacing_label = FormLabel(self) + self.line_spacing_label.setObjectName('line_spacing_label') + self.layout.addWidget(self.line_spacing_label, 2, 2) + self.line_spacing_spinbox = QtWidgets.QSpinBox(self) + self.line_spacing_spinbox.setMinimum(-250) + self.line_spacing_spinbox.setMaximum(250) + self.line_spacing_spinbox.setObjectName('line_spacing_spinbox') + self.layout.addWidget(self.line_spacing_spinbox, 2, 3) + # Outline + self.outline_groupbox = QtWidgets.QGroupBox(self) + self.outline_groupbox.setCheckable(True) + self.outline_groupbox.setChecked(False) + self.outline_groupbox.setObjectName('outline_groupbox') + self.outline_layout = QtWidgets.QGridLayout(self.outline_groupbox) + self.layout.addWidget(self.outline_groupbox, 3, 0, 1, 2) + # Outline colour + self.outline_color_label = FormLabel(self.outline_groupbox) + self.outline_color_label.setObjectName('outline_color_label') + self.outline_layout.addWidget(self.outline_color_label, 0, 0) + self.outline_color_button = ColorButton(self.outline_groupbox) + self.outline_color_button.setObjectName('outline_color_button') + self.outline_layout.addWidget(self.outline_color_button, 0, 1) + # Outline size + self.outline_size_label = FormLabel(self.outline_groupbox) + self.outline_size_label.setObjectName('outline_size_label') + self.outline_layout.addWidget(self.outline_size_label, 1, 0) + self.outline_size_spinbox = QtWidgets.QSpinBox(self.outline_groupbox) + self.outline_size_spinbox.setMaximum(9999) + self.outline_size_spinbox.setObjectName('outline_size_spinbox') + self.outline_layout.addWidget(self.outline_size_spinbox, 1, 1) + # Shadow + self.shadow_groupbox = QtWidgets.QGroupBox(self) + self.shadow_groupbox.setCheckable(True) + self.shadow_groupbox.setChecked(False) + self.shadow_groupbox.setObjectName('shadow_groupbox') + self.shadow_layout = QtWidgets.QGridLayout(self.shadow_groupbox) + self.layout.addWidget(self.shadow_groupbox, 3, 2, 1, 2) + # Shadow color + self.shadow_color_label = FormLabel(self.shadow_groupbox) + self.shadow_color_label.setObjectName('shadow_color_label') + self.shadow_layout.addWidget(self.shadow_color_label, 0, 0) + self.shadow_color_button = ColorButton(self.shadow_groupbox) + self.shadow_color_button.setObjectName('shadow_color_button') + self.shadow_layout.addWidget(self.shadow_color_button, 0, 1) + # Shadow size + self.shadow_size_label = FormLabel(self.shadow_groupbox) + self.shadow_size_label.setObjectName('shadow_size_label') + self.shadow_layout.addWidget(self.shadow_size_label, 1, 0) + self.shadow_size_spinbox = QtWidgets.QSpinBox(self.shadow_groupbox) + self.shadow_size_spinbox.setMaximum(9999) + self.shadow_size_spinbox.setObjectName('shadow_size_spinbox') + self.shadow_layout.addWidget(self.shadow_size_spinbox, 1, 1) + # Connect all the signals + self.font_name_combobox.activated.connect(self._on_font_name_changed) + self.font_color_button.colorChanged.connect(self._on_font_color_changed) + self.style_bold_button.toggled.connect(self._on_style_bold_toggled) + self.style_italic_button.toggled.connect(self._on_style_italic_toggled) + self.font_size_spinbox.valueChanged.connect(self._on_font_size_changed) + self.line_spacing_spinbox.valueChanged.connect(self._on_line_spacing_changed) + self.outline_groupbox.toggled.connect(self._on_outline_toggled) + self.outline_color_button.colorChanged.connect(self._on_outline_color_changed) + self.outline_size_spinbox.valueChanged.connect(self._on_outline_size_changed) + self.shadow_groupbox.toggled.connect(self._on_shadow_toggled) + self.shadow_color_button.colorChanged.connect(self._on_shadow_color_changed) + self.shadow_size_spinbox.valueChanged.connect(self._on_shadow_size_changed) + + def retranslate_ui(self): + self.font_name_label.setText(translate('OpenLP.FontSelectWidget', 'Font:')) + self.font_color_label.setText(translate('OpenLP.FontSelectWidget', 'Color:')) + self.font_style_label.setText(translate('OpenLP.FontSelectWidget', 'Style:')) + self.style_bold_button.setToolTip('{name} ({shortcut})'.format( + name=translate('OpenLP.FontSelectWidget', 'Bold'), + shortcut=QtGui.QKeySequence(QtGui.QKeySequence.Bold).toString() + )) + self.style_italic_button.setToolTip('{name} ({shortcut})'.format( + name=translate('OpenLP.FontSelectWidget', 'Italic'), + shortcut=QtGui.QKeySequence(QtGui.QKeySequence.Italic).toString() + )) + self.font_size_label.setText(translate('OpenLP.FontSelectWidget', 'Size:')) + self.font_size_spinbox.setSuffix(' {unit}'.format(unit=UiStrings().FontSizePtUnit)) + self.line_spacing_label.setText(translate('OpenLP.FontSelectWidget', 'Line Spacing:')) + self.outline_groupbox.setTitle(translate('OpenLP.FontSelectWidget', 'Outline')) + self.outline_color_label.setText(translate('OpenLP.FontSelectWidget', 'Color:')) + self.outline_size_label.setText(translate('OpenLP.FontSelectWidget', 'Size:')) + self.shadow_groupbox.setTitle(translate('OpenLP.FontSelectWidget', 'Shadow')) + self.shadow_color_label.setText(translate('OpenLP.FontSelectWidget', 'Color:')) + self.shadow_size_label.setText(translate('OpenLP.FontSelectWidget', 'Size:')) + + def _on_font_name_changed(self, name): + if isinstance(name, str): + self.font_name_changed.emit(name) + + def _on_font_color_changed(self, color): + self.font_color_changed.emit(color) + + def _on_style_bold_toggled(self, is_bold): + self.is_bold_changed.emit(is_bold) + + def _on_style_italic_toggled(self, is_italic): + self.is_italic_changed.emit(is_italic) + + def _on_font_size_changed(self, size): + self.font_size_changed.emit(size) + + def _on_line_spacing_changed(self, spacing): + self.line_spacing_changed.emit(spacing) + + def _on_outline_toggled(self, is_enabled): + self.is_outline_enabled_changed.emit(is_enabled) + + def _on_outline_color_changed(self, color): + self.outline_color_changed.emit(color) + + def _on_outline_size_changed(self, size): + self.outline_size_changed.emit(size) + + def _on_shadow_toggled(self, is_enabled): + self.is_shadow_enabled_changed.emit(is_enabled) + + def _on_shadow_color_changed(self, color): + self.shadow_color_changed.emit(color) + + def _on_shadow_size_changed(self, size): + self.shadow_size_changed.emit(size) + + def enable_features(self, *features): + """ + Enable a feature + """ + for feature_name in features: + if feature_name not in self.feature_widgets.keys(): + raise KeyError('No such feature: {feature_name}'.format(feature_name=feature_name)) + for widget in self.feature_widgets[feature_name]: + widget.show() + + def disable_features(self, *features): + """ + Disable a feature + """ + for feature_name in features: + if feature_name not in self.feature_widgets.keys(): + raise KeyError('No such feature: {feature_name}'.format(feature_name=feature_name)) + for widget in self.feature_widgets[feature_name]: + widget.hide() + + @property + def font_name(self): + return self.font_name_combobox.currentFont().family() + + @font_name.setter + def font_name(self, font): + self.font_name_combobox.setCurrentFont(QtGui.QFont(font)) + + @property + def font_color(self): + return self.font_color_button.color + + @font_color.setter + def font_color(self, color): + self.font_color_button.color = color + + @property + def is_bold(self): + return self.style_bold_button.isChecked() + + @is_bold.setter + def is_bold(self, is_bold): + self.style_bold_button.setChecked(is_bold) + + @property + def is_italic(self): + return self.style_italic_button.isChecked() + + @is_italic.setter + def is_italic(self, is_italic): + self.style_italic_button.setChecked(is_italic) + + @property + def font_size(self): + return self.font_size_spinbox.value() + + @font_size.setter + def font_size(self, size): + self.font_size_spinbox.setValue(size) + + @property + def line_spacing(self): + return self.line_spacing_spinbox.value() + + @line_spacing.setter + def line_spacing(self, line_spacing): + self.line_spacing_spinbox.setValue(line_spacing) + + @property + def is_outline_enabled(self): + return self.outline_groupbox.isChecked() + + @is_outline_enabled.setter + def is_outline_enabled(self, is_enabled): + self.outline_groupbox.setChecked(is_enabled) + + @property + def outline_color(self): + return self.outline_color_button.color + + @outline_color.setter + def outline_color(self, color): + self.outline_color_button.color = color + + @property + def outline_size(self): + return self.outline_size_spinbox.value() + + @outline_size.setter + def outline_size(self, size): + self.outline_size_spinbox.setValue(size) + + @property + def is_shadow_enabled(self): + return self.shadow_groupbox.isChecked() + + @is_shadow_enabled.setter + def is_shadow_enabled(self, is_enabled): + self.shadow_groupbox.setChecked(is_enabled) + + @property + def shadow_color(self): + return self.shadow_color_button.color + + @shadow_color.setter + def shadow_color(self, color): + self.shadow_color_button.color = color + + @property + def shadow_size(self): + return self.shadow_size_spinbox.value() + + @shadow_size.setter + def shadow_size(self, size): + self.shadow_size_spinbox.setValue(size) diff --git a/openlp/core/ui/themeform.py b/openlp/core/ui/themeform.py index edb7e5359..5f253851f 100644 --- a/openlp/core/ui/themeform.py +++ b/openlp/core/ui/themeform.py @@ -25,13 +25,12 @@ import logging from PyQt5 import QtCore, QtGui, QtWidgets -from openlp.core.common import get_images_filter, is_not_image_file +from openlp.core.common import is_not_image_file from openlp.core.common.i18n import UiStrings, translate from openlp.core.common.mixins import RegistryProperties from openlp.core.common.registry import Registry -from openlp.core.lib.theme import BackgroundGradientType, BackgroundType +from openlp.core.lib.theme import BackgroundType from openlp.core.lib.ui import critical_error_message_box -from openlp.core.ui.media import VIDEO_EXT from openlp.core.ui.themelayoutform import ThemeLayoutForm from openlp.core.ui.themewizard import Ui_ThemeWizard @@ -60,42 +59,21 @@ class ThemeForm(QtWidgets.QWizard, Ui_ThemeWizard, RegistryProperties): Set up the class. This method is mocked out by the tests. """ self.setup_ui(self) - self.registerFields() - self.update_theme_allowed = True + self.can_update_theme = True self.temp_background_filename = None self.theme_layout_form = ThemeLayoutForm(self) - self.background_combo_box.currentIndexChanged.connect(self.on_background_combo_box_current_index_changed) - self.gradient_combo_box.currentIndexChanged.connect(self.on_gradient_combo_box_current_index_changed) - self.color_button.colorChanged.connect(self.on_color_changed) - self.image_color_button.colorChanged.connect(self.on_image_color_changed) - self.video_color_button.colorChanged.connect(self.on_video_color_changed) - self.gradient_start_button.colorChanged.connect(self.on_gradient_start_color_changed) - self.gradient_end_button.colorChanged.connect(self.on_gradient_end_color_changed) - self.image_path_edit.filters = \ - '{name};;{text} (*)'.format(name=get_images_filter(), text=UiStrings().AllFiles) - self.image_path_edit.pathChanged.connect(self.on_image_path_edit_path_changed) - visible_formats = '(*.{name})'.format(name='; *.'.join(VIDEO_EXT)) - actual_formats = '(*.{name})'.format(name=' *.'.join(VIDEO_EXT)) - video_filter = '{trans} {visible} {actual}'.format(trans=translate('OpenLP', 'Video Files'), - visible=visible_formats, actual=actual_formats) - self.video_path_edit.filters = '{video};;{ui} (*)'.format(video=video_filter, ui=UiStrings().AllFiles) - self.video_path_edit.pathChanged.connect(self.on_video_path_edit_path_changed) - self.footer_color_button.colorChanged.connect(self.on_footer_color_changed) self.customButtonClicked.connect(self.on_custom_1_button_clicked) - self.main_position_check_box.stateChanged.connect(self.on_main_position_check_box_state_changed) - self.footer_position_check_box.stateChanged.connect(self.on_footer_position_check_box_state_changed) self.currentIdChanged.connect(self.on_current_id_changed) Registry().register_function('theme_line_count', self.update_lines_text) - self.main_font.font_name_changed.connect(self.calculate_lines) - self.main_font.font_size_changed.connect(self.calculate_lines) - self.main_font.line_spacing_changed.connect(self.calculate_lines) - self.main_font.is_outline_enabled_changed.connect(self.on_outline_toggled) - self.main_font.outline_size_changed.connect(self.calculate_lines) - self.main_font.is_shadow_enabled_changed.connect(self.on_shadow_toggled) - self.main_font.shadow_size_changed.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) + self.main_area_page.font_name_changed.connect(self.calculate_lines) + self.main_area_page.font_size_changed.connect(self.calculate_lines) + self.main_area_page.line_spacing_changed.connect(self.calculate_lines) + self.main_area_page.is_outline_enabled_changed.connect(self.on_outline_toggled) + self.main_area_page.outline_size_changed.connect(self.calculate_lines) + self.main_area_page.is_shadow_enabled_changed.connect(self.on_shadow_toggled) + self.main_area_page.shadow_size_changed.connect(self.calculate_lines) + self.footer_area_page.font_name_changed.connect(self.update_theme) + self.footer_area_page.font_size_changed.connect(self.update_theme) def set_defaults(self): """ @@ -109,33 +87,6 @@ class ThemeForm(QtWidgets.QWizard, Ui_ThemeWizard, RegistryProperties): self.set_position_page_values() self.set_preview_page_values() - def registerFields(self): - """ - Map field names to screen names, - """ - self.background_page.registerField('background_type', self.background_combo_box) - self.background_page.registerField('color', self.color_button) - self.background_page.registerField('gradient_start', self.gradient_start_button) - self.background_page.registerField('gradient_end', self.gradient_end_button) - self.background_page.registerField('background_image', self.image_path_edit, - 'path', self.image_path_edit.pathChanged) - self.background_page.registerField('gradient', self.gradient_combo_box) - self.main_area_page.registerField('footer_size_spin_box', self.footer_size_spin_box) - self.area_position_page.registerField('main_position_x', self.main_x_spin_box) - self.area_position_page.registerField('main_position_y', self.main_y_spin_box) - self.area_position_page.registerField('main_position_width', self.main_width_spin_box) - self.area_position_page.registerField('main_position_height', self.main_height_spin_box) - self.area_position_page.registerField('footer_position_x', self.footer_x_spin_box) - self.area_position_page.registerField('footer_position_y', self.footer_y_spin_box) - self.area_position_page.registerField('footer_position_width', self.footer_width_spin_box) - self.area_position_page.registerField('footer_position_height', self.footer_height_spin_box) - 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, *args): """ Calculate the number of lines on a page by rendering text @@ -175,7 +126,8 @@ class ThemeForm(QtWidgets.QWizard, Ui_ThemeWizard, RegistryProperties): """ background_image = BackgroundType.to_string(BackgroundType.Image) if self.page(self.currentId()) == self.background_page and \ - self.theme.background_type == background_image and is_not_image_file(self.theme.background_filename): + self.background_page.background_type == background_image and \ + is_not_image_file(self.background_page.image_path): QtWidgets.QMessageBox.critical(self, translate('OpenLP.ThemeWizard', 'Background Image Empty'), translate('OpenLP.ThemeWizard', 'You have not selected a ' 'background image. Please select one before continuing.')) @@ -229,7 +181,7 @@ class ThemeForm(QtWidgets.QWizard, Ui_ThemeWizard, RegistryProperties): """ Change state as Outline check box changed """ - if self.update_theme_allowed: + if self.can_update_theme: self.theme.font_main_outline = is_enabled self.calculate_lines() @@ -237,45 +189,19 @@ class ThemeForm(QtWidgets.QWizard, Ui_ThemeWizard, RegistryProperties): """ Change state as Shadow check box changed """ - if self.update_theme_allowed: + if self.can_update_theme: self.theme.font_main_shadow = is_enabled self.calculate_lines() - def on_main_position_check_box_state_changed(self, value): - """ - Change state as Main Area _position check box changed - NOTE the font_main_override is the inverse of the check box value - """ - if self.update_theme_allowed: - self.theme.font_main_override = (value != QtCore.Qt.Checked) - - def on_footer_position_check_box_state_changed(self, value): - """ - Change state as Footer Area _position check box changed - NOTE the font_footer_override is the inverse of the check box value - """ - 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. """ log.debug('Editing theme {name}'.format(name=self.theme.theme_name)) self.temp_background_filename = self.theme.background_source - self.update_theme_allowed = False + self.can_update_theme = False self.set_defaults() - self.update_theme_allowed = True + self.can_update_theme = True self.theme_name_label.setVisible(not edit) self.theme_name_edit.setVisible(not edit) self.edit_mode = edit @@ -308,228 +234,150 @@ class ThemeForm(QtWidgets.QWizard, Ui_ThemeWizard, RegistryProperties): """ Handle the display and state of the Background page. """ + self.background_page.background_type = self.theme.background_type if self.theme.background_type == BackgroundType.to_string(BackgroundType.Solid): - self.color_button.color = self.theme.background_color - self.setField('background_type', 0) + self.background_page.color = self.theme.background_color elif self.theme.background_type == BackgroundType.to_string(BackgroundType.Gradient): - self.gradient_start_button.color = self.theme.background_start_color - self.gradient_end_button.color = self.theme.background_end_color - self.setField('background_type', 1) + self.background_page.gradient_start = self.theme.background_start_color + self.background_page.gradient_end = self.theme.background_end_color + self.background_page.gradient_type = self.theme.background_direction elif self.theme.background_type == BackgroundType.to_string(BackgroundType.Image): - self.image_color_button.color = self.theme.background_border_color - self.image_path_edit.path = self.theme.background_source - self.setField('background_type', 2) + self.background_page.image_color = self.theme.background_border_color + if self.theme.background_source and self.theme.background_source.exists(): + self.background_page.image_path = self.theme.background_source + else: + self.background_page.image_path = self.theme.background_filename elif self.theme.background_type == BackgroundType.to_string(BackgroundType.Video): - self.video_color_button.color = self.theme.background_border_color - self.video_path_edit.path = self.theme.background_source - self.setField('background_type', 4) - elif self.theme.background_type == BackgroundType.to_string(BackgroundType.Stream): - self.setField('background_type', 5) - elif self.theme.background_type == BackgroundType.to_string(BackgroundType.Transparent): - self.setField('background_type', 3) - if self.theme.background_direction == BackgroundGradientType.to_string(BackgroundGradientType.Horizontal): - self.setField('gradient', 0) - elif self.theme.background_direction == BackgroundGradientType.to_string(BackgroundGradientType.Vertical): - self.setField('gradient', 1) - elif self.theme.background_direction == BackgroundGradientType.to_string(BackgroundGradientType.Circular): - self.setField('gradient', 2) - elif self.theme.background_direction == BackgroundGradientType.to_string(BackgroundGradientType.LeftTop): - self.setField('gradient', 3) - else: - self.setField('gradient', 4) + self.background_page.video_color = self.theme.background_border_color + if self.theme.background_source and self.theme.background_source.exists(): + self.background_page.video_path = self.theme.background_source + else: + self.background_page.video_path = self.theme.background_filename def set_main_area_page_values(self): """ Handle the display and state of the Main Area page. """ - self.main_font.font_name = self.theme.font_main_name - self.main_font.font_color = self.theme.font_main_color - self.main_font.font_size = self.theme.font_main_size - self.main_font.line_spacing = self.theme.font_main_line_adjustment - self.main_font.is_outline_enabled = self.theme.font_main_outline - self.main_font.outline_color = self.theme.font_main_outline_color - self.main_font.outline_size = self.theme.font_main_outline_size - self.main_font.is_shadow_enabled = self.theme.font_main_shadow - self.main_font.shadow_color = self.theme.font_main_shadow_color - self.main_font.shadow_size = self.theme.font_main_shadow_size - self.main_font.is_bold = self.theme.font_main_bold - self.main_font.is_italic = self.theme.font_main_italics + self.main_area_page.font_name = self.theme.font_main_name + self.main_area_page.font_color = self.theme.font_main_color + self.main_area_page.font_size = self.theme.font_main_size + self.main_area_page.line_spacing = self.theme.font_main_line_adjustment + self.main_area_page.is_outline_enabled = self.theme.font_main_outline + self.main_area_page.outline_color = self.theme.font_main_outline_color + self.main_area_page.outline_size = self.theme.font_main_outline_size + self.main_area_page.is_shadow_enabled = self.theme.font_main_shadow + self.main_area_page.shadow_color = self.theme.font_main_shadow_color + self.main_area_page.shadow_size = self.theme.font_main_shadow_size + self.main_area_page.is_bold = self.theme.font_main_bold + self.main_area_page.is_italic = self.theme.font_main_italics def set_footer_area_page_values(self): """ Handle the display and state of the Footer Area page. """ - self.footer_font_combo_box.setCurrentFont(QtGui.QFont(self.theme.font_footer_name)) - self.footer_color_button.color = self.theme.font_footer_color - self.setField('footer_size_spin_box', self.theme.font_footer_size) + self.footer_area_page.font_name = self.theme.font_footer_name + self.footer_area_page.font_color = self.theme.font_footer_color def set_position_page_values(self): """ Handle the display and state of the _position page. """ # Main Area - self.main_position_check_box.setChecked(not self.theme.font_main_override) - self.setField('main_position_x', self.theme.font_main_x) - self.setField('main_position_y', self.theme.font_main_y) - self.setField('main_position_height', self.theme.font_main_height) - self.setField('main_position_width', self.theme.font_main_width) + self.area_position_page.use_main_default_location = not self.theme.font_main_override + self.area_position_page.main_x = self.theme.font_main_x + self.area_position_page.main_y = self.theme.font_main_y + self.area_position_page.main_height = self.theme.font_main_height + self.area_position_page.main_width = self.theme.font_main_width # Footer - self.footer_position_check_box.setChecked(not self.theme.font_footer_override) - self.setField('footer_position_x', self.theme.font_footer_x) - self.setField('footer_position_y', self.theme.font_footer_y) - self.setField('footer_position_height', self.theme.font_footer_height) - self.setField('footer_position_width', self.theme.font_footer_width) + self.area_position_page.use_footer_default_location = not self.theme.font_footer_override + self.area_position_page.footer_x = self.theme.font_footer_x + self.area_position_page.footer_y = self.theme.font_footer_y + self.area_position_page.footer_height = self.theme.font_footer_height + self.area_position_page.footer_width = self.theme.font_footer_width def set_alignment_page_values(self): """ Handle the display and state of the Alignments page. """ - 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) + self.alignment_page.horizontal_align = self.theme.display_horizontal_align + self.alignment_page.vertical_align = self.theme.display_vertical_align + self.alignment_page.is_transition_enabled = self.theme.display_slide_transition + self.alignment_page.transition_type = self.theme.display_slide_transition_type + self.alignment_page.transition_speed = self.theme.display_slide_transition_speed def set_preview_page_values(self): """ Handle the display and state of the Preview page. """ - self.setField('name', self.theme.theme_name) + self.theme_name_edit.setText(self.theme.theme_name) self.preview_box.set_theme(self.theme) - def on_background_combo_box_current_index_changed(self, index): - """ - Background style Combo box has changed. - """ - # do not allow updates when screen is building for the first time. - if self.update_theme_allowed: - self.theme.background_type = BackgroundType.to_string(index) - if self.theme.background_type != BackgroundType.to_string(BackgroundType.Image) and \ - self.theme.background_type != BackgroundType.to_string(BackgroundType.Video) and \ - self.temp_background_filename is None: - self.temp_background_filename = self.theme.background_filename - self.theme.background_filename = None - if (self.theme.background_type == BackgroundType.to_string(BackgroundType.Image) or - self.theme.background_type != BackgroundType.to_string(BackgroundType.Video)) and \ - self.temp_background_filename is not None: - self.theme.background_filename = self.temp_background_filename - self.temp_background_filename = None - self.set_background_page_values() - - def on_gradient_combo_box_current_index_changed(self, index): - """ - Background gradient Combo box has changed. - """ - if self.update_theme_allowed: - self.theme.background_direction = BackgroundGradientType.to_string(index) - self.set_background_page_values() - - def on_color_changed(self, color): - """ - Background / Gradient 1 _color button pushed. - """ - self.theme.background_color = color - - def on_image_color_changed(self, color): - """ - Background / Gradient 1 _color button pushed. - """ - self.theme.background_border_color = color - - def on_video_color_changed(self, color): - """ - Background / Gradient 1 _color button pushed. - """ - self.theme.background_border_color = color - - def on_gradient_start_color_changed(self, color): - """ - Gradient 2 _color button pushed. - """ - self.theme.background_start_color = color - - def on_gradient_end_color_changed(self, color): - """ - Gradient 2 _color button pushed. - """ - self.theme.background_end_color = color - - def on_image_path_edit_path_changed(self, new_path): - """ - Handle the `pathEditChanged` signal from image_path_edit - - :param pathlib.Path new_path: Path to the new image - :rtype: None - """ - self.theme.background_source = new_path - self.theme.background_filename = new_path - self.set_background_page_values() - - def on_video_path_edit_path_changed(self, new_path): - """ - Handle the `pathEditChanged` signal from video_path_edit - - :param pathlib.Path new_path: Path to the new video - :rtype: None - """ - self.theme.background_source = new_path - self.theme.background_filename = new_path - self.set_background_page_values() - - def on_footer_color_changed(self, color): - """ - Set the footer colour value - """ - self.theme.font_footer_color = color - def update_theme(self): """ Update the theme object from the UI for fields not already updated when the are changed. """ - if not self.update_theme_allowed: + if not self.can_update_theme: return log.debug('update_theme') + # background page + self.theme.background_type = self.background_page.background_type + if self.theme.background_type == BackgroundType.to_string(BackgroundType.Solid): + self.theme.background_color = self.background_page.color + elif self.theme.background_type == BackgroundType.to_string(BackgroundType.Gradient): + self.theme.background_direction = self.background_page.gradient_type + self.theme.background_start_color = self.background_page.gradient_start + self.theme.background_end_color = self.background_page.gradient_end + elif self.theme.background_type == BackgroundType.to_string(BackgroundType.Image): + self.theme.background_border_color = self.background_page.image_color + self.theme.background_source = self.background_page.image_path + self.theme.background_filename = self.background_page.image_path + elif self.theme.background_type == BackgroundType.to_string(BackgroundType.Video): + self.theme.background_border_color = self.background_page.video_color + self.theme.background_source = self.background_page.video_path + self.theme.background_filename = self.background_page.video_path # main page - self.theme.font_main_name = self.main_font.font_name - self.theme.font_main_size = self.main_font.font_size - self.theme.font_main_line_adjustment = self.main_font.line_spacing - self.theme.font_main_outline_size = self.main_font.outline_size - self.theme.font_main_shadow_size = self.main_font.shadow_size - self.theme.font_main_bold = self.main_font.is_bold - self.theme.font_main_italics = self.main_font.is_italic + self.theme.font_main_name = self.main_area_page.font_name + self.theme.font_main_size = self.main_area_page.font_size + self.theme.font_main_line_adjustment = self.main_area_page.line_spacing + self.theme.font_main_outline_size = self.main_area_page.outline_size + self.theme.font_main_shadow_size = self.main_area_page.shadow_size + self.theme.font_main_bold = self.main_area_page.is_bold + self.theme.font_main_italics = self.main_area_page.is_italic # 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') + self.theme.font_footer_name = self.footer_area_page.font_name + self.theme.font_footer_size = self.footer_area_page.font_size # position page (main) + self.theme.font_main_override = not self.area_position_page.use_main_default_location 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') + self.theme.font_main_x = self.area_position_page.main_x + self.theme.font_main_y = self.area_position_page.main_y + self.theme.font_main_height = self.area_position_page.main_height + self.theme.font_main_width = self.area_position_page.main_width else: self.theme.set_default_header() # position page (footer) + self.theme.font_footer_override = not self.area_position_page.use_footer_default_location 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') + self.theme.font_footer_x = self.area_position_page.footer_x + self.theme.font_footer_y = self.area_position_page.footer_y + self.theme.font_footer_height = self.area_position_page.footer_height + self.theme.font_footer_width = self.area_position_page.footer_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') + # alignment page + self.theme.display_horizontal_align = self.alignment_page.horizontal_align + self.theme.display_vertical_align = self.alignment_page.vertical_align + self.theme.display_slide_transition = self.alignment_page.is_transition_enabled + self.theme.display_slide_transition_type = self.alignment_page.transition_type + self.theme.display_slide_transition_speed = self.alignment_page.transition_speed def accept(self): """ Lets save the theme as Finish has been triggered """ # Save the theme name - self.theme.theme_name = self.field('name') + self.theme.theme_name = self.theme_name_edit.text() if not self.theme.theme_name: critical_error_message_box( translate('OpenLP.ThemeWizard', 'Theme Name Missing'), @@ -542,7 +390,7 @@ class ThemeForm(QtWidgets.QWizard, Ui_ThemeWizard, RegistryProperties): return destination_path = None if self.theme.background_type == BackgroundType.to_string(BackgroundType.Image) or \ - self.theme.background_type == BackgroundType.to_string(BackgroundType.Video): + self.theme.background_type == BackgroundType.to_string(BackgroundType.Video): file_name = self.theme.background_filename.name destination_path = self.path / self.theme.theme_name / file_name if not self.edit_mode and not self.theme_manager.check_if_theme_exists(self.theme.theme_name): diff --git a/openlp/core/ui/themewizard.py b/openlp/core/ui/themewizard.py index 9b902b7fc..c47084fcf 100644 --- a/openlp/core/ui/themewizard.py +++ b/openlp/core/ui/themewizard.py @@ -24,16 +24,15 @@ The Create/Edit theme wizard from PyQt5 import QtCore, QtGui, QtWidgets from openlp.core.common import is_macosx -from openlp.core.common.i18n import UiStrings, translate +from openlp.core.common.i18n import translate from openlp.core.display.render import ThemePreviewRenderer -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.lib.ui import add_welcome_page +from openlp.core.pages.alignment import AlignmentTransitionsPage +from openlp.core.pages.areaposition import AreaPositionPage +from openlp.core.pages.background import BackgroundPage +from openlp.core.pages.fontselect import FontSelectPage from openlp.core.ui.icons import UiIcons -from openlp.core.widgets.buttons import ColorButton -from openlp.core.widgets.edits import PathEdit from openlp.core.widgets.layouts import AspectRatioLayout -from openlp.core.widgets.widgets import FontSelectWidget class Ui_ThemeWizard(object): @@ -58,234 +57,26 @@ class Ui_ThemeWizard(object): # Welcome Page add_welcome_page(theme_wizard, ':/wizards/wizard_createtheme.bmp') # Background Page - self.background_page = QtWidgets.QWizardPage() + self.background_page = BackgroundPage() self.background_page.setObjectName('background_page') - self.background_layout = QtWidgets.QVBoxLayout(self.background_page) - self.background_layout.setObjectName('background_layout') - self.background_type_layout = QtWidgets.QFormLayout() - self.background_type_layout.setObjectName('background_type_layout') - self.background_label = QtWidgets.QLabel(self.background_page) - self.background_label.setObjectName('background_label') - self.background_combo_box = QtWidgets.QComboBox(self.background_page) - self.background_combo_box.addItems(['', '', '', '']) - self.background_combo_box.setObjectName('background_combo_box') - self.background_type_layout.addRow(self.background_label, self.background_combo_box) - self.background_type_layout.setItem(1, QtWidgets.QFormLayout.LabelRole, self.spacer) - self.background_layout.addLayout(self.background_type_layout) - self.background_stack = QtWidgets.QStackedLayout() - self.background_stack.setObjectName('background_stack') - self.color_widget = QtWidgets.QWidget(self.background_page) - self.color_widget.setObjectName('color_widget') - self.color_layout = QtWidgets.QFormLayout(self.color_widget) - self.color_layout.setContentsMargins(0, 0, 0, 0) - self.color_layout.setObjectName('color_layout') - self.color_label = QtWidgets.QLabel(self.color_widget) - self.color_label.setObjectName('color_label') - self.color_button = ColorButton(self.color_widget) - self.color_button.setObjectName('color_button') - self.color_layout.addRow(self.color_label, self.color_button) - self.color_layout.setItem(1, QtWidgets.QFormLayout.LabelRole, self.spacer) - self.background_stack.addWidget(self.color_widget) - self.gradient_widget = QtWidgets.QWidget(self.background_page) - self.gradient_widget.setObjectName('Gradient_widget') - self.gradient_layout = QtWidgets.QFormLayout(self.gradient_widget) - self.gradient_layout.setContentsMargins(0, 0, 0, 0) - self.gradient_layout.setObjectName('gradient_layout') - self.gradient_start_label = QtWidgets.QLabel(self.gradient_widget) - self.gradient_start_label.setObjectName('gradient_start_label') - self.gradient_start_button = ColorButton(self.gradient_widget) - self.gradient_start_button.setObjectName('gradient_start_button') - self.gradient_layout.addRow(self.gradient_start_label, self.gradient_start_button) - self.gradient_end_label = QtWidgets.QLabel(self.gradient_widget) - self.gradient_end_label.setObjectName('gradient_end_label') - self.gradient_end_button = ColorButton(self.gradient_widget) - self.gradient_end_button.setObjectName('gradient_end_button') - self.gradient_layout.addRow(self.gradient_end_label, self.gradient_end_button) - self.gradient_type_label = QtWidgets.QLabel(self.gradient_widget) - self.gradient_type_label.setObjectName('Gradient_type_label') - self.gradient_combo_box = QtWidgets.QComboBox(self.gradient_widget) - self.gradient_combo_box.setObjectName('gradient_combo_box') - self.gradient_combo_box.addItems(['', '', '', '', '']) - self.gradient_layout.addRow(self.gradient_type_label, self.gradient_combo_box) - self.gradient_layout.setItem(3, QtWidgets.QFormLayout.LabelRole, self.spacer) - self.background_stack.addWidget(self.gradient_widget) - self.image_widget = QtWidgets.QWidget(self.background_page) - self.image_widget.setObjectName('image_widget') - self.image_layout = QtWidgets.QFormLayout(self.image_widget) - self.image_layout.setContentsMargins(0, 0, 0, 0) - self.image_layout.setObjectName('image_layout') - self.image_color_label = QtWidgets.QLabel(self.color_widget) - self.image_color_label.setObjectName('image_color_label') - self.image_color_button = ColorButton(self.color_widget) - self.image_color_button.setObjectName('image_color_button') - self.image_layout.addRow(self.image_color_label, self.image_color_button) - self.image_label = QtWidgets.QLabel(self.image_widget) - self.image_label.setObjectName('image_label') - self.image_path_edit = PathEdit(self.image_widget, - dialog_caption=translate('OpenLP.ThemeWizard', 'Select Image'), - show_revert=False) - self.image_layout.addRow(self.image_label, self.image_path_edit) - self.image_layout.setItem(2, QtWidgets.QFormLayout.LabelRole, self.spacer) - self.background_stack.addWidget(self.image_widget) - self.transparent_widget = QtWidgets.QWidget(self.background_page) - self.transparent_widget.setObjectName('TransparentWidget') - self.transparent_layout = QtWidgets.QFormLayout(self.transparent_widget) - self.transparent_layout.setContentsMargins(0, 0, 0, 0) - self.transparent_layout.setObjectName('Transparent_layout') - self.background_stack.addWidget(self.transparent_widget) - self.background_layout.addLayout(self.background_stack) - self.video_widget = QtWidgets.QWidget(self.background_page) - self.video_widget.setObjectName('video_widget') - self.video_layout = QtWidgets.QFormLayout(self.video_widget) - self.video_layout.setContentsMargins(0, 0, 0, 0) - self.video_layout.setObjectName('video_layout') - self.video_color_label = QtWidgets.QLabel(self.color_widget) - self.video_color_label.setObjectName('video_color_label') - self.video_color_button = ColorButton(self.color_widget) - self.video_color_button.setObjectName('video_color_button') - self.video_layout.addRow(self.video_color_label, self.video_color_button) - self.video_label = QtWidgets.QLabel(self.video_widget) - self.video_label.setObjectName('video_label') - self.video_path_edit = PathEdit(self.video_widget, - dialog_caption=translate('OpenLP.ThemeWizard', 'Select Video'), - show_revert=False) - self.video_layout.addRow(self.video_label, self.video_path_edit) - self.video_layout.setItem(2, QtWidgets.QFormLayout.LabelRole, self.spacer) - self.background_stack.addWidget(self.video_widget) theme_wizard.addPage(self.background_page) # Main Area Page - self.main_area_page = QtWidgets.QWizardPage() + self.main_area_page = FontSelectPage() self.main_area_page.setObjectName('main_area_page') - self.main_area_layout = QtWidgets.QVBoxLayout(self.main_area_page) - self.main_area_layout.setObjectName('main_area_layout') - self.main_font = FontSelectWidget(self.main_area_page) - self.main_area_layout.addWidget(self.main_font) theme_wizard.addPage(self.main_area_page) # Footer Area Page - self.footer_area_page = QtWidgets.QWizardPage() + self.footer_area_page = FontSelectPage() self.footer_area_page.setObjectName('footer_area_page') - self.footer_area_layout = QtWidgets.QFormLayout(self.footer_area_page) - self.footer_area_layout.setObjectName('footer_area_layout') - self.footer_font_label = QtWidgets.QLabel(self.footer_area_page) - self.footer_font_label.setObjectName('FooterFontLabel') - self.footer_font_combo_box = QtWidgets.QFontComboBox(self.footer_area_page) - self.footer_font_combo_box.setObjectName('footer_font_combo_box') - self.footer_area_layout.addRow(self.footer_font_label, self.footer_font_combo_box) - self.footer_color_label = QtWidgets.QLabel(self.footer_area_page) - self.footer_color_label.setObjectName('footer_color_label') - self.footer_color_button = ColorButton(self.footer_area_page) - self.footer_color_button.setObjectName('footer_color_button') - self.footer_area_layout.addRow(self.footer_color_label, self.footer_color_button) - self.footer_size_label = QtWidgets.QLabel(self.footer_area_page) - self.footer_size_label.setObjectName('footer_size_label') - self.footer_size_spin_box = QtWidgets.QSpinBox(self.footer_area_page) - self.footer_size_spin_box.setMaximum(999) - self.footer_size_spin_box.setValue(10) - self.footer_size_spin_box.setObjectName('FooterSizeSpinBox') - self.footer_area_layout.addRow(self.footer_size_label, self.footer_size_spin_box) - self.footer_area_layout.setItem(3, QtWidgets.QFormLayout.LabelRole, self.spacer) + self.footer_area_page.disable_features(FontSelectPage.Outline, FontSelectPage.Shadow, + FontSelectPage.LineSpacing) theme_wizard.addPage(self.footer_area_page) # Alignment Page - self.alignment_page = QtWidgets.QWizardPage() + self.alignment_page = AlignmentTransitionsPage() self.alignment_page.setObjectName('alignment_page') - self.alignment_layout = QtWidgets.QFormLayout(self.alignment_page) - self.alignment_layout.setObjectName('alignment_layout') - self.horizontal_label = QtWidgets.QLabel(self.alignment_page) - self.horizontal_label.setObjectName('horizontal_label') - self.horizontal_combo_box = QtWidgets.QComboBox(self.alignment_page) - self.horizontal_combo_box.addItems(['', '', '', '']) - self.horizontal_combo_box.setObjectName('horizontal_combo_box') - self.alignment_layout.addRow(self.horizontal_label, self.horizontal_combo_box) - self.vertical_label, self.vertical_combo_box = create_valign_selection_widgets(self.alignment_page) - 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_check_box = QtWidgets.QCheckBox(self.alignment_page) - self.transitions_check_box.setObjectName('transitions_check_box') - 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() + self.area_position_page = AreaPositionPage() self.area_position_page.setObjectName('area_position_page') - self.area_position_layout = QtWidgets.QHBoxLayout(self.area_position_page) - self.area_position_layout.setObjectName('area_position_layout') - self.main_position_group_box = QtWidgets.QGroupBox(self.area_position_page) - self.main_position_group_box.setObjectName('main_position_group_box') - self.main_position_layout = QtWidgets.QFormLayout(self.main_position_group_box) - self.main_position_layout.setObjectName('main_position_layout') - self.main_position_check_box = QtWidgets.QCheckBox(self.main_position_group_box) - self.main_position_check_box.setObjectName('main_position_check_box') - self.main_position_layout.addRow(self.main_position_check_box) - self.main_x_label = QtWidgets.QLabel(self.main_position_group_box) - self.main_x_label.setObjectName('main_x_label') - self.main_x_spin_box = QtWidgets.QSpinBox(self.main_position_group_box) - self.main_x_spin_box.setMaximum(9999) - self.main_x_spin_box.setObjectName('main_x_spin_box') - self.main_position_layout.addRow(self.main_x_label, self.main_x_spin_box) - self.main_y_label = QtWidgets.QLabel(self.main_position_group_box) - self.main_y_label.setObjectName('main_y_label') - self.main_y_spin_box = QtWidgets.QSpinBox(self.main_position_group_box) - self.main_y_spin_box.setMaximum(9999) - self.main_y_spin_box.setObjectName('main_y_spin_box') - self.main_position_layout.addRow(self.main_y_label, self.main_y_spin_box) - self.main_width_label = QtWidgets.QLabel(self.main_position_group_box) - self.main_width_label.setObjectName('main_width_label') - self.main_width_spin_box = QtWidgets.QSpinBox(self.main_position_group_box) - self.main_width_spin_box.setMaximum(9999) - self.main_width_spin_box.setObjectName('main_width_spin_box') - self.main_position_layout.addRow(self.main_width_label, self.main_width_spin_box) - self.main_height_label = QtWidgets.QLabel(self.main_position_group_box) - self.main_height_label.setObjectName('main_height_label') - self.main_height_spin_box = QtWidgets.QSpinBox(self.main_position_group_box) - self.main_height_spin_box.setMaximum(9999) - self.main_height_spin_box.setObjectName('main_height_spin_box') - self.main_position_layout.addRow(self.main_height_label, self.main_height_spin_box) - self.area_position_layout.addWidget(self.main_position_group_box) - self.footer_position_group_box = QtWidgets.QGroupBox(self.area_position_page) - self.footer_position_group_box.setObjectName('footer_position_group_box') - self.footer_position_layout = QtWidgets.QFormLayout(self.footer_position_group_box) - self.footer_position_layout.setObjectName('footer_position_layout') - self.footer_position_check_box = QtWidgets.QCheckBox(self.footer_position_group_box) - self.footer_position_check_box.setObjectName('footer_position_check_box') - self.footer_position_layout.addRow(self.footer_position_check_box) - self.footer_x_label = QtWidgets.QLabel(self.footer_position_group_box) - self.footer_x_label.setObjectName('footer_x_label') - self.footer_x_spin_box = QtWidgets.QSpinBox(self.footer_position_group_box) - self.footer_x_spin_box.setMaximum(9999) - self.footer_x_spin_box.setObjectName('footer_x_spin_box') - self.footer_position_layout.addRow(self.footer_x_label, self.footer_x_spin_box) - self.footer_y_label = QtWidgets.QLabel(self.footer_position_group_box) - self.footer_y_label.setObjectName('footer_y_label') - self.footer_y_spin_box = QtWidgets.QSpinBox(self.footer_position_group_box) - self.footer_y_spin_box.setMaximum(9999) - self.footer_y_spin_box.setObjectName('footer_y_spin_box') - self.footer_position_layout.addRow(self.footer_y_label, self.footer_y_spin_box) - self.footer_width_label = QtWidgets.QLabel(self.footer_position_group_box) - self.footer_width_label.setObjectName('footer_width_label') - self.footer_width_spin_box = QtWidgets.QSpinBox(self.footer_position_group_box) - self.footer_width_spin_box.setMaximum(9999) - self.footer_width_spin_box.setObjectName('footer_width_spin_box') - self.footer_position_layout.addRow(self.footer_width_label, self.footer_width_spin_box) - self.footer_height_label = QtWidgets.QLabel(self.footer_position_group_box) - self.footer_height_label.setObjectName('footer_height_label') - self.footer_height_spin_box = QtWidgets.QSpinBox(self.footer_position_group_box) - self.footer_height_spin_box.setMaximum(9999) - self.footer_height_spin_box.setObjectName('footer_height_spin_box') - self.footer_position_layout.addRow(self.footer_height_label, self.footer_height_spin_box) - self.area_position_layout.addWidget(self.footer_position_group_box) theme_wizard.addPage(self.area_position_page) # Preview Page self.preview_page = QtWidgets.QWizardPage() @@ -313,15 +104,6 @@ class Ui_ThemeWizard(object): self.preview_layout.addWidget(self.preview_area) theme_wizard.addPage(self.preview_page) self.retranslate_ui(theme_wizard) - self.background_combo_box.currentIndexChanged.connect(self.background_stack.setCurrentIndex) - self.main_position_check_box.toggled.connect(self.main_x_spin_box.setDisabled) - self.main_position_check_box.toggled.connect(self.main_y_spin_box.setDisabled) - self.main_position_check_box.toggled.connect(self.main_width_spin_box.setDisabled) - self.main_position_check_box.toggled.connect(self.main_height_spin_box.setDisabled) - self.footer_position_check_box.toggled.connect(self.footer_x_spin_box.setDisabled) - self.footer_position_check_box.toggled.connect(self.footer_y_spin_box.setDisabled) - self.footer_position_check_box.toggled.connect(self.footer_width_spin_box.setDisabled) - self.footer_position_check_box.toggled.connect(self.footer_height_spin_box.setDisabled) def retranslate_ui(self, theme_wizard): """ @@ -336,87 +118,20 @@ class Ui_ThemeWizard(object): self.background_page.setTitle(translate('OpenLP.ThemeWizard', 'Set Up Background')) self.background_page.setSubTitle(translate('OpenLP.ThemeWizard', 'Set up your theme\'s background ' 'according to the parameters below.')) - self.background_label.setText(translate('OpenLP.ThemeWizard', 'Background type:')) - self.background_combo_box.setItemText(BackgroundType.Solid, translate('OpenLP.ThemeWizard', 'Solid color')) - self.background_combo_box.setItemText(BackgroundType.Gradient, translate('OpenLP.ThemeWizard', 'Gradient')) - self.background_combo_box.setItemText(BackgroundType.Image, UiStrings().Image) - self.background_combo_box.setItemText(BackgroundType.Transparent, - translate('OpenLP.ThemeWizard', 'Transparent')) - self.color_label.setText(translate('OpenLP.ThemeWizard', 'color:')) - self.gradient_start_label.setText(translate('OpenLP.ThemeWizard', 'Starting color:')) - self.gradient_end_label.setText(translate('OpenLP.ThemeWizard', 'Ending color:')) - self.gradient_type_label.setText(translate('OpenLP.ThemeWizard', 'Gradient:')) - self.gradient_combo_box.setItemText(BackgroundGradientType.Horizontal, - translate('OpenLP.ThemeWizard', 'Horizontal')) - self.gradient_combo_box.setItemText(BackgroundGradientType.Vertical, - translate('OpenLP.ThemeWizard', 'Vertical')) - self.gradient_combo_box.setItemText(BackgroundGradientType.Circular, - translate('OpenLP.ThemeWizard', 'Circular')) - self.gradient_combo_box.setItemText(BackgroundGradientType.LeftTop, - translate('OpenLP.ThemeWizard', 'Top Left - Bottom Right')) - self.gradient_combo_box.setItemText(BackgroundGradientType.LeftBottom, - translate('OpenLP.ThemeWizard', 'Bottom Left - Top Right')) - self.image_color_label.setText(translate('OpenLP.ThemeWizard', 'Background color:')) - self.image_label.setText('{text}:'.format(text=UiStrings().Image)) - self.video_color_label.setText(translate('OpenLP.ThemeWizard', 'Background color:')) - self.video_label.setText('{text}:'.format(text=UiStrings().Video)) self.main_area_page.setTitle(translate('OpenLP.ThemeWizard', 'Main Area Font Details')) self.main_area_page.setSubTitle(translate('OpenLP.ThemeWizard', 'Define the font and display ' 'characteristics for the Display text')) self.footer_area_page.setTitle(translate('OpenLP.ThemeWizard', 'Footer Area Font Details')) self.footer_area_page.setSubTitle(translate('OpenLP.ThemeWizard', 'Define the font and display ' 'characteristics for the Footer text')) - self.footer_font_label.setText(translate('OpenLP.ThemeWizard', 'Font:')) - self.footer_color_label.setText(translate('OpenLP.ThemeWizard', 'color:')) - self.footer_size_label.setText(translate('OpenLP.ThemeWizard', 'Size:')) - self.footer_size_spin_box.setSuffix(' {unit}'.format(unit=UiStrings().FontSizePtUnit)) self.alignment_page.setTitle(translate('OpenLP.ThemeWizard', 'Text Formatting Details')) self.alignment_page.setSubTitle(translate('OpenLP.ThemeWizard', 'Allows additional display ' 'formatting information to be defined')) - self.horizontal_label.setText(translate('OpenLP.ThemeWizard', 'Horizontal Align:')) - self.horizontal_combo_box.setItemText(HorizontalType.Left, translate('OpenLP.ThemeWizard', 'Left')) - 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_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.')) - self.main_position_group_box.setTitle(translate('OpenLP.ThemeWizard', '&Main Area')) - self.main_position_check_box.setText(translate('OpenLP.ThemeWizard', '&Use default location')) - self.main_x_label.setText(translate('OpenLP.ThemeWizard', 'X position:')) - self.main_x_spin_box.setSuffix(translate('OpenLP.ThemeWizard', 'px')) - self.main_y_spin_box.setSuffix(translate('OpenLP.ThemeWizard', 'px')) - self.main_y_label.setText(translate('OpenLP.ThemeWizard', 'Y position:')) - self.main_width_spin_box.setSuffix(translate('OpenLP.ThemeWizard', 'px')) - self.main_width_label.setText(translate('OpenLP.ThemeWizard', 'Width:')) - self.main_height_spin_box.setSuffix(translate('OpenLP.ThemeWizard', 'px')) - self.main_height_label.setText(translate('OpenLP.ThemeWizard', 'Height:')) - self.footer_position_group_box.setTitle(translate('OpenLP.ThemeWizard', '&Footer Area')) - self.footer_x_label.setText(translate('OpenLP.ThemeWizard', 'X position:')) - self.footer_x_spin_box.setSuffix(translate('OpenLP.ThemeWizard', 'px')) - self.footer_y_label.setText(translate('OpenLP.ThemeWizard', 'Y position:')) - self.footer_y_spin_box.setSuffix(translate('OpenLP.ThemeWizard', 'px')) - self.footer_width_label.setText(translate('OpenLP.ThemeWizard', 'Width:')) - self.footer_width_spin_box.setSuffix(translate('OpenLP.ThemeWizard', 'px')) - self.footer_height_label.setText(translate('OpenLP.ThemeWizard', 'Height:')) - self.footer_height_spin_box.setSuffix(translate('OpenLP.ThemeWizard', 'px')) - self.footer_position_check_box.setText(translate('OpenLP.ThemeWizard', 'Use default location')) theme_wizard.setOption(QtWidgets.QWizard.HaveCustomButton1, False) theme_wizard.setButtonText(QtWidgets.QWizard.CustomButton1, translate('OpenLP.ThemeWizard', 'Layout Preview')) self.preview_page.setTitle(translate('OpenLP.ThemeWizard', 'Preview and Save')) self.preview_page.setSubTitle(translate('OpenLP.ThemeWizard', 'Preview the theme and save it.')) self.theme_name_label.setText(translate('OpenLP.ThemeWizard', 'Theme name:')) - # Align all QFormLayouts towards each other. - label_width = max(self.background_label.minimumSizeHint().width(), - self.horizontal_label.minimumSizeHint().width()) - self.spacer.changeSize(label_width, 0, QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) diff --git a/openlp/core/widgets/dialogs.py b/openlp/core/widgets/dialogs.py old mode 100755 new mode 100644 diff --git a/tests/functional/openlp_core/ui/test_themeform.py b/openlp/core/widgets/labels.py similarity index 54% rename from tests/functional/openlp_core/ui/test_themeform.py rename to openlp/core/widgets/labels.py index bbb205f0a..65cfdaef6 100644 --- a/tests/functional/openlp_core/ui/test_themeform.py +++ b/openlp/core/widgets/labels.py @@ -19,34 +19,15 @@ # along with this program. If not, see . # ########################################################################## """ -Package to test the openlp.core.ui.themeform package. +The :mod:`~openlp.core.widgets.labels` module contains specialised labels """ -from pathlib import Path -from unittest import TestCase -from unittest.mock import MagicMock, patch - -from openlp.core.ui.themeform import ThemeForm +from PyQt5 import QtCore, QtWidgets -class TestThemeForm(TestCase): +class FormLabel(QtWidgets.QLabel): """ - Test the functions in the ThemeForm Class + A label that is prepped for forms, right aligned and vertically centered """ - def setUp(self): - with patch('openlp.core.ui.themeform.ThemeForm._setup'): - self.instance = ThemeForm(None) - - def test_on_image_path_edit_path_changed(self): - """ - Test the `image_path_edit.pathChanged` handler - """ - # GIVEN: An instance of Theme Form - with patch.object(self.instance, 'set_background_page_values') as mocked_set_background_page_values: - self.instance.theme = MagicMock() - - # WHEN: `on_image_path_edit_path_changed` is clicked - self.instance.on_image_path_edit_path_changed(Path('/', 'new', 'pat.h')) - - # THEN: The theme background file should be set and `set_background_page_values` should have been called - assert self.instance.theme.background_filename == Path('/', 'new', 'pat.h') - mocked_set_background_page_values.assert_called_once_with() + def __init__(self, parent=None): + super().__init__(parent) + self.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) diff --git a/openlp/core/widgets/widgets.py b/openlp/core/widgets/widgets.py index b3ef54ea8..acb280aae 100644 --- a/openlp/core/widgets/widgets.py +++ b/openlp/core/widgets/widgets.py @@ -21,13 +21,11 @@ """ The :mod:`~openlp.core.widgets.widgets` module contains custom widgets used in OpenLP """ -from PyQt5 import QtCore, QtGui, QtWidgets +from PyQt5 import QtCore, QtWidgets -from openlp.core.common.i18n import UiStrings, translate +from openlp.core.common.i18n import translate from openlp.core.common.settings import ProxyMode, Settings from openlp.core.lib.ui import critical_error_message_box -from openlp.core.ui.icons import UiIcons -from openlp.core.widgets.buttons import ColorButton SCREENS_LAYOUT_STYLE = """ @@ -443,350 +441,3 @@ class ScreenSelectionWidget(QtWidgets.QWidget): self._setup_spin_box(self.height_spin_box, 0, screen.display_geometry.height(), screen.display_geometry.height()) self.current_screen = screen - - -class FontSelectWidget(QtWidgets.QWidget): - """ - A font selection widget - """ - Outline = 'outline' - Shadow = 'shadow' - LineSpacing = 'line_spacing' - - font_name_changed = QtCore.pyqtSignal(str) - font_size_changed = QtCore.pyqtSignal(int) - font_color_changed = QtCore.pyqtSignal(str) - is_bold_changed = QtCore.pyqtSignal(bool) - is_italic_changed = QtCore.pyqtSignal(bool) - line_spacing_changed = QtCore.pyqtSignal(int) - is_outline_enabled_changed = QtCore.pyqtSignal(bool) - outline_color_changed = QtCore.pyqtSignal(str) - outline_size_changed = QtCore.pyqtSignal(int) - is_shadow_enabled_changed = QtCore.pyqtSignal(bool) - shadow_color_changed = QtCore.pyqtSignal(str) - shadow_size_changed = QtCore.pyqtSignal(int) - - def __init__(self, parent=None): - super().__init__(parent) - self._column_width = 0 - self.setup_ui() - self.feature_widgets = { - FontSelectWidget.Outline: [self.outline_groupbox], - FontSelectWidget.Shadow: [self.shadow_groupbox], - FontSelectWidget.LineSpacing: [self.line_spacing_label, self.line_spacing_spinbox] - } - - def setup_ui(self): - self.layout = QtWidgets.QGridLayout(self) - # Font name - self.font_name_label = QtWidgets.QLabel(self) - self.font_name_label.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) - self.font_name_label.setObjectName('font_name_label') - self.layout.addWidget(self.font_name_label, 0, 0) - self.font_name_combobox = QtWidgets.QFontComboBox(self) - self.font_name_combobox.setObjectName('font_name_combobox') - self.layout.addWidget(self.font_name_combobox, 0, 1, 1, 3) - # Font color - self.font_color_label = QtWidgets.QLabel(self) - self.font_color_label.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) - self.font_color_label.setObjectName('font_color_label') - self.layout.addWidget(self.font_color_label, 1, 0) - self.font_color_button = ColorButton(self) - self.font_color_button.setObjectName('font_color_button') - self.layout.addWidget(self.font_color_button, 1, 1) - # Font style - self.font_style_label = QtWidgets.QLabel(self) - self.font_style_label.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) - self.font_style_label.setObjectName('font_style_label') - self.layout.addWidget(self.font_style_label, 1, 2) - self.style_layout = QtWidgets.QHBoxLayout() - self.style_bold_button = QtWidgets.QToolButton(self) - self.style_bold_button.setIcon(UiIcons().bold) - self.style_bold_button.setShortcut(QtGui.QKeySequence(QtGui.QKeySequence.Bold)) - self.style_bold_button.setObjectName('style_bold_button') - self.style_layout.addWidget(self.style_bold_button) - self.style_italic_button = QtWidgets.QToolButton(self) - self.style_italic_button.setIcon(UiIcons().italic) - self.style_italic_button.setShortcut(QtGui.QKeySequence(QtGui.QKeySequence.Italic)) - self.style_italic_button.setObjectName('style_italic_button') - self.style_layout.addWidget(self.style_italic_button) - self.style_layout.addStretch(1) - self.layout.addLayout(self.style_layout, 1, 3) - # Font size - self.font_size_label = QtWidgets.QLabel(self) - self.font_size_label.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) - self.font_size_label.setObjectName('font_size_label') - self.layout.addWidget(self.font_size_label, 2, 0) - self.font_size_spinbox = QtWidgets.QSpinBox(self) - self.font_size_spinbox.setMaximum(999) - self.font_size_spinbox.setValue(16) - self.font_size_spinbox.setObjectName('font_size_spinbox') - self.layout.addWidget(self.font_size_spinbox, 2, 1) - # Line spacing - self.line_spacing_label = QtWidgets.QLabel(self) - self.line_spacing_label.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) - self.line_spacing_label.setObjectName('line_spacing_label') - self.layout.addWidget(self.line_spacing_label, 2, 2) - self.line_spacing_spinbox = QtWidgets.QSpinBox(self) - self.line_spacing_spinbox.setMinimum(-250) - self.line_spacing_spinbox.setMaximum(250) - self.line_spacing_spinbox.setObjectName('line_spacing_spinbox') - self.layout.addWidget(self.line_spacing_spinbox, 2, 3) - # Outline - self.outline_groupbox = QtWidgets.QGroupBox(self) - self.outline_groupbox.setCheckable(True) - self.outline_groupbox.setChecked(False) - self.outline_groupbox.setObjectName('outline_groupbox') - self.outline_layout = QtWidgets.QGridLayout(self.outline_groupbox) - self.layout.addWidget(self.outline_groupbox, 3, 0, 1, 2) - # Outline colour - self.outline_color_label = QtWidgets.QLabel(self.outline_groupbox) - self.outline_color_label.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) - self.outline_color_label.setObjectName('outline_color_label') - self.outline_layout.addWidget(self.outline_color_label, 0, 0) - self.outline_color_button = ColorButton(self.outline_groupbox) - self.outline_color_button.setObjectName('outline_color_button') - self.outline_layout.addWidget(self.outline_color_button, 0, 1) - # Outline size - self.outline_size_label = QtWidgets.QLabel(self.outline_groupbox) - self.outline_size_label.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) - self.outline_size_label.setObjectName('outline_size_label') - self.outline_layout.addWidget(self.outline_size_label, 1, 0) - self.outline_size_spinbox = QtWidgets.QSpinBox(self.outline_groupbox) - self.outline_size_spinbox.setMaximum(9999) - self.outline_size_spinbox.setObjectName('outline_size_spinbox') - self.outline_layout.addWidget(self.outline_size_spinbox, 1, 1) - # Shadow - self.shadow_groupbox = QtWidgets.QGroupBox(self) - self.shadow_groupbox.setCheckable(True) - self.shadow_groupbox.setChecked(False) - self.shadow_groupbox.setObjectName('shadow_groupbox') - self.shadow_layout = QtWidgets.QGridLayout(self.shadow_groupbox) - self.layout.addWidget(self.shadow_groupbox, 3, 2, 1, 2) - # Shadow color - self.shadow_color_label = QtWidgets.QLabel(self.shadow_groupbox) - self.shadow_color_label.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) - self.shadow_color_label.setObjectName('shadow_color_label') - self.shadow_layout.addWidget(self.shadow_color_label, 0, 0) - self.shadow_color_button = ColorButton(self.shadow_groupbox) - self.shadow_color_button.setObjectName('shadow_color_button') - self.shadow_layout.addWidget(self.shadow_color_button, 0, 1) - # Shadow size - self.shadow_size_label = QtWidgets.QLabel(self.shadow_groupbox) - self.shadow_size_label.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) - self.shadow_size_label.setObjectName('shadow_size_label') - self.shadow_layout.addWidget(self.shadow_size_label, 1, 0) - self.shadow_size_spinbox = QtWidgets.QSpinBox(self.shadow_groupbox) - self.shadow_size_spinbox.setMaximum(9999) - self.shadow_size_spinbox.setObjectName('shadow_size_spinbox') - self.shadow_layout.addWidget(self.shadow_size_spinbox, 1, 1) - # Fix the size - self.resize_widgets() - # Connect all the signals - self.font_name_combobox.activated.connect(self._on_font_name_changed) - self.font_color_button.colorChanged.connect(self._on_font_color_changed) - self.style_bold_button.toggled.connect(self._on_style_bold_toggled) - self.style_italic_button.toggled.connect(self._on_style_italic_toggled) - self.font_size_spinbox.valueChanged.connect(self._on_font_size_changed) - self.line_spacing_spinbox.valueChanged.connect(self._on_line_spacing_changed) - self.outline_groupbox.toggled.connect(self._on_outline_toggled) - self.outline_color_button.colorChanged.connect(self._on_outline_color_changed) - self.outline_size_spinbox.valueChanged.connect(self._on_outline_size_changed) - self.shadow_groupbox.toggled.connect(self._on_shadow_toggled) - self.shadow_color_button.colorChanged.connect(self._on_shadow_color_changed) - self.shadow_size_spinbox.valueChanged.connect(self._on_shadow_size_changed) - # Translate everything - self.retranslate_ui() - - def retranslate_ui(self): - self.font_name_label.setText(translate('OpenLP.FontSelectWidget', 'Font:')) - self.font_color_label.setText(translate('OpenLP.FontSelectWidget', 'Color:')) - self.font_style_label.setText(translate('OpenLP.FontSelectWidget', 'Style:')) - self.style_bold_button.setToolTip('{name} ({shortcut})'.format( - name=translate('OpenLP.FontSelectWidget', 'Bold'), - shortcut=QtGui.QKeySequence(QtGui.QKeySequence.Bold).toString() - )) - self.style_italic_button.setToolTip('{name} ({shortcut})'.format( - name=translate('OpenLP.FontSelectWidget', 'Italic'), - shortcut=QtGui.QKeySequence(QtGui.QKeySequence.Italic).toString() - )) - self.font_size_label.setText(translate('OpenLP.FontSelectWidget', 'Size:')) - self.font_size_spinbox.setSuffix(' {unit}'.format(unit=UiStrings().FontSizePtUnit)) - self.line_spacing_label.setText(translate('OpenLP.FontSelectWidget', 'Line Spacing:')) - self.outline_groupbox.setTitle(translate('OpenLP.FontSelectWidget', 'Outline')) - self.outline_color_label.setText(translate('OpenLP.FontSelectWidget', 'Color:')) - self.outline_size_label.setText(translate('OpenLP.FontSelectWidget', 'Size:')) - self.shadow_groupbox.setTitle(translate('OpenLP.FontSelectWidget', 'Shadow')) - self.shadow_color_label.setText(translate('OpenLP.FontSelectWidget', 'Color:')) - self.shadow_size_label.setText(translate('OpenLP.FontSelectWidget', 'Size:')) - - def resizeEvent(self, event): - """ - Override inherited resize method - """ - super().resizeEvent(event) - self.resize_widgets() - - def _on_font_name_changed(self, name): - if isinstance(name, str): - self.font_name_changed.emit(name) - - def _on_font_color_changed(self, color): - self.font_color_changed.emit(color) - - def _on_style_bold_toggled(self, is_bold): - self.is_bold_changed.emit(is_bold) - - def _on_style_italic_toggled(self, is_italic): - self.is_italic_changed.emit(is_italic) - - def _on_font_size_changed(self, size): - self.font_size_changed.emit(size) - - def _on_line_spacing_changed(self, spacing): - self.line_spacing_changed.emit(spacing) - - def _on_outline_toggled(self, is_enabled): - self.is_outline_enabled_changed.emit(is_enabled) - - def _on_outline_color_changed(self, color): - self.outline_color_changed.emit(color) - - def _on_outline_size_changed(self, size): - self.outline_size_changed.emit(size) - - def _on_shadow_toggled(self, is_enabled): - self.is_shadow_enabled_changed.emit(is_enabled) - - def _on_shadow_color_changed(self, color): - self.shadow_color_changed.emit(color) - - def _on_shadow_size_changed(self, size): - self.shadow_size_changed.emit(size) - - def resize_widgets(self): - """ - Resize all the widgets and set the column widths - """ - width = self.geometry().width() - margins = self.layout.contentsMargins() - spacing = self.layout.horizontalSpacing() - self._column_width = (width - margins.left() - margins.right() - (spacing * 3)) // 4 - for column_number in range(4): - self.layout.setColumnMinimumWidth(column_number, self._column_width) - - def enable_features(self, *features): - """ - Enable a feature - """ - for feature_name in features: - if feature_name not in self.feature_widgets.keys(): - raise KeyError('No such feature: {feature_name}'.format(feature_name=feature_name)) - for widget in self.feature_widgets[feature_name]: - widget.show() - - def disable_features(self, *features): - """ - Disable a feature - """ - for feature_name in features: - if feature_name not in self.feature_widgets.keys(): - raise KeyError('No such feature: {feature_name}'.format(feature_name=feature_name)) - for widget in self.feature_widgets[feature_name]: - widget.hide() - - @property - def font_name(self): - return self.font_name_combobox.currentFont().family() - - @font_name.setter - def font_name(self, font): - self.font_name_combobox.setCurrentFont(QtGui.QFont(font)) - - @property - def font_color(self): - return self.font_color_button.color - - @font_color.setter - def font_color(self, color): - self.font_color_button.color = color - - @property - def is_bold(self): - return self.style_bold_button.isChecked() - - @is_bold.setter - def is_bold(self, is_bold): - self.style_bold_button.setChecked(is_bold) - - @property - def is_italic(self): - return self.style_italic_button.isChecked() - - @is_italic.setter - def is_italic(self, is_italic): - self.style_italic_button.setChecked(is_italic) - - @property - def font_size(self): - return self.font_size_spinbox.value() - - @font_size.setter - def font_size(self, size): - self.font_size_spinbox.setValue(size) - - @property - def line_spacing(self): - return self.line_spacing_spinbox.value() - - @line_spacing.setter - def line_spacing(self, line_spacing): - self.line_spacing_spinbox.setValue(line_spacing) - - @property - def is_outline_enabled(self): - return self.outline_groupbox.isChecked() - - @is_outline_enabled.setter - def is_outline_enabled(self, is_enabled): - self.outline_groupbox.setChecked(is_enabled) - - @property - def outline_color(self): - return self.outline_color_button.color - - @outline_color.setter - def outline_color(self, color): - self.outline_color_button.color = color - - @property - def outline_size(self): - return self.outline_size_spinbox.value() - - @outline_size.setter - def outline_size(self, size): - self.outline_size_spinbox.setValue(size) - - @property - def is_shadow_enabled(self): - return self.shadow_groupbox.isChecked() - - @is_shadow_enabled.setter - def is_shadow_enabled(self, is_enabled): - self.shadow_groupbox.setChecked(is_enabled) - - @property - def shadow_color(self): - return self.shadow_color_button.color - - @shadow_color.setter - def shadow_color(self, color): - self.shadow_color_button.color = color - - @property - def shadow_size(self): - return self.shadow_size_spinbox.value() - - @shadow_size.setter - def shadow_size(self, size): - self.shadow_size_spinbox.setValue(size) diff --git a/tests/openlp_core/pages/__init__.py b/tests/openlp_core/pages/__init__.py new file mode 100644 index 000000000..36a3e2274 --- /dev/null +++ b/tests/openlp_core/pages/__init__.py @@ -0,0 +1,23 @@ +# -*- 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.pages package. +""" diff --git a/tests/openlp_core/pages/test_alignment.py b/tests/openlp_core/pages/test_alignment.py new file mode 100644 index 000000000..2a317d2a0 --- /dev/null +++ b/tests/openlp_core/pages/test_alignment.py @@ -0,0 +1,305 @@ +# -*- 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.pages.alignment package. +""" +from unittest import TestCase +from unittest.mock import MagicMock + +import pytest + +from openlp.core.lib.theme import HorizontalType, VerticalType, TransitionType, TransitionSpeed +from openlp.core.pages.alignment import AlignmentTransitionsPage +from tests.helpers.testmixin import TestMixin + + +class TestAlignmentTransitionsPage(TestCase, TestMixin): + + def setUp(self): + """Test setup""" + self.setup_application() + self.build_settings() + + def tearDown(self): + """Tear down tests""" + del self.app + + def test_init_(self): + """ + Test the initialisation of AlignmentTransitionsPage + """ + # GIVEN: The AlignmentTransitionsPage class + # WHEN: Initialising AlignmentTransitionsPage + # THEN: We should have an instance of the widget with no errors + AlignmentTransitionsPage() + + def test_on_transition_enabled_changed(self): + """ + Test the _on_transition_enabled_changed() slot + """ + # GIVEN: And instance of AlignmentTransitionsPage and some mock widgets + page = AlignmentTransitionsPage() + + # WHEN: _on_transition_enabled_changed + page._on_transition_enabled_changed(True) + + # THEN: The correct widgets should be visible + assert page.transition_effect_label.isEnabled() + assert page.transition_effect_combo_box.isEnabled() + assert page.transition_speed_label.isEnabled() + assert page.transition_speed_combo_box.isEnabled() + + def test_get_horizontal_align(self): + """ + Test the horizontal_align getter + """ + # GIVEN: A AlignmentTransitionsPage instance with the combobox set to index 1 + page = AlignmentTransitionsPage() + page.horizontal_combo_box.setCurrentIndex(1) + + # WHEN: The property is accessed + result = page.horizontal_align + + # THEN: The result should be correct + assert result == HorizontalType.Right + + def test_set_horizontal_align_int(self): + """ + Test the horizontal_align setter with an int + """ + # GIVEN: A AlignmentTransitionsPage instance + page = AlignmentTransitionsPage() + + # WHEN: The property is set + page.horizontal_align = HorizontalType.Center + + # THEN: The combobox should be correct + assert page.horizontal_combo_box.currentIndex() == 2 + + def test_set_horizontal_align_str(self): + """ + Test the horizontal_align setter with a str + """ + # GIVEN: A AlignmentTransitionsPage instance + page = AlignmentTransitionsPage() + + # WHEN: The property is set + page.horizontal_align = HorizontalType.to_string(HorizontalType.Justify) + + # THEN: The combobox should be correct + assert page.horizontal_combo_box.currentIndex() == 3 + + def test_set_horizontal_align_exception(self): + """ + Test the horizontal_align setter with something other than a str or int + """ + # GIVEN: A AlignmentTransitionsPage instance + page = AlignmentTransitionsPage() + + # WHEN: The property is set + # THEN: An exception is raised + with pytest.raises(TypeError, match='horizontal_align must either be a string or an int'): + page.horizontal_align = [] + + def test_get_vertical_align(self): + """ + Test the vertical_align getter + """ + # GIVEN: A AlignmentTransitionsPage instance with the combobox set to index 1 + page = AlignmentTransitionsPage() + page.vertical_combo_box.setCurrentIndex(1) + + # WHEN: The property is accessed + result = page.vertical_align + + # THEN: The result should be correct + assert result == VerticalType.Middle + + def test_set_vertical_align_int(self): + """ + Test the vertical_align setter with an int + """ + # GIVEN: A AlignmentTransitionsPage instance + page = AlignmentTransitionsPage() + + # WHEN: The property is set + page.vertical_align = VerticalType.Bottom + + # THEN: The combobox should be correct + assert page.vertical_combo_box.currentIndex() == 2 + + def test_set_vertical_align_str(self): + """ + Test the vertical_align setter with a str + """ + # GIVEN: A AlignmentTransitionsPage instance + page = AlignmentTransitionsPage() + + # WHEN: The property is set + page.vertical_align = VerticalType.to_string(VerticalType.Top) + + # THEN: The combobox should be correct + assert page.vertical_combo_box.currentIndex() == 0 + + def test_set_vertical_align_exception(self): + """ + Test the vertical_align setter with something other than a str or int + """ + # GIVEN: A AlignmentTransitionsPage instance + page = AlignmentTransitionsPage() + + # WHEN: The property is set + # THEN: An exception is raised + with pytest.raises(TypeError, match='vertical_align must either be a string or an int'): + page.vertical_align = [] + + def test_get_is_transition_enabled(self): + """ + Test the is_transition_enabled getter + """ + # GIVEN: A AlignmentTransitionsPage instance with the transitions enabled + page = AlignmentTransitionsPage() + page.transitions_enabled_check_box.setChecked(False) + + # WHEN: The property is accessed + result = page.is_transition_enabled + + # THEN: The result should be correct + assert result is False + + def test_set_is_transition_enabled(self): + """ + Test the is_transition_enabled setter + """ + # GIVEN: A AlignmentTransitionsPage instance + page = AlignmentTransitionsPage() + page._on_transition_enabled_changed = MagicMock() + + # WHEN: The property is set + page.is_transition_enabled = True + + # THEN: The result should be correct + assert page.transitions_enabled_check_box.isChecked() is True + page._on_transition_enabled_changed.assert_called_once_with(True) + + def test_get_transition_type(self): + """ + Test the transition_type getter + """ + # GIVEN: A AlignmentTransitionsPage instance with the combobox set to index 1 + page = AlignmentTransitionsPage() + page.transition_effect_combo_box.setCurrentIndex(1) + + # WHEN: The property is accessed + result = page.transition_type + + # THEN: The result should be correct + assert result == TransitionType.Slide + + def test_set_transition_type_int(self): + """ + Test the transition_type setter with an int + """ + # GIVEN: A AlignmentTransitionsPage instance + page = AlignmentTransitionsPage() + + # WHEN: The property is set + page.transition_type = TransitionType.Concave + + # THEN: The combobox should be correct + assert page.transition_effect_combo_box.currentIndex() == 3 + + def test_set_transition_type_str(self): + """ + Test the transition_type setter with a str + """ + # GIVEN: A AlignmentTransitionsPage instance + page = AlignmentTransitionsPage() + + # WHEN: The property is set + page.transition_type = TransitionType.to_string(TransitionType.Convex) + + # THEN: The combobox should be correct + assert page.transition_effect_combo_box.currentIndex() == 2 + + def test_set_transition_type_exception(self): + """ + Test the transition_type setter with something other than a str or int + """ + # GIVEN: A AlignmentTransitionsPage instance + page = AlignmentTransitionsPage() + + # WHEN: The property is set + # THEN: An exception is raised + with pytest.raises(TypeError, match='transition_type must either be a string or an int'): + page.transition_type = [] + + def test_get_transition_speed(self): + """ + Test the transition_speed getter + """ + # GIVEN: A AlignmentTransitionsPage instance with the combobox set to index 1 + page = AlignmentTransitionsPage() + page.transition_speed_combo_box.setCurrentIndex(0) + + # WHEN: The property is accessed + result = page.transition_speed + + # THEN: The result should be correct + assert result == TransitionSpeed.Normal + + def test_set_transition_speed_int(self): + """ + Test the transition_speed setter with an int + """ + # GIVEN: A AlignmentTransitionsPage instance + page = AlignmentTransitionsPage() + + # WHEN: The property is set + page.transition_speed = TransitionSpeed.Fast + + # THEN: The combobox should be correct + assert page.transition_speed_combo_box.currentIndex() == 1 + + def test_set_transition_speed_str(self): + """ + Test the transition_speed setter with a str + """ + # GIVEN: A AlignmentTransitionsPage instance + page = AlignmentTransitionsPage() + + # WHEN: The property is set + page.transition_speed = TransitionSpeed.to_string(TransitionSpeed.Slow) + + # THEN: The combobox should be correct + assert page.transition_speed_combo_box.currentIndex() == 2 + + def test_set_transition_speed_exception(self): + """ + Test the transition_speed setter with something other than a str or int + """ + # GIVEN: A AlignmentTransitionsPage instance + page = AlignmentTransitionsPage() + + # WHEN: The property is set + # THEN: An exception is raised + with pytest.raises(TypeError, match='transition_speed must either be a string or an int'): + page.transition_speed = [] diff --git a/tests/openlp_core/pages/test_areaposition.py b/tests/openlp_core/pages/test_areaposition.py new file mode 100644 index 000000000..916221d9c --- /dev/null +++ b/tests/openlp_core/pages/test_areaposition.py @@ -0,0 +1,318 @@ +# -*- 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.pages.alignment package. +""" +from unittest import TestCase + +from openlp.core.pages.areaposition import AreaPositionPage +from tests.helpers.testmixin import TestMixin + + +class TestAreaPositionPage(TestCase, TestMixin): + + def setUp(self): + """Test setup""" + self.setup_application() + self.build_settings() + + def tearDown(self): + """Tear down tests""" + del self.app + + def test_init_(self): + """ + Test the initialisation of AreaPositionPage + """ + # GIVEN: The AreaPositionPage class + # WHEN: Initialising AreaPositionPage + # THEN: We should have an instance of the widget with no errors + AreaPositionPage() + + def test_get_use_main_default_location(self): + """ + Test the use_main_default_location getter + """ + # GIVEN: A AreaPositionPage instance with the combobox set to index 1 + page = AreaPositionPage() + page.main_position_check_box.setChecked(False) + + # WHEN: The property is accessed + result = page.use_main_default_location + + # THEN: The result should be correct + assert result is False + + def test_set_use_main_default_location(self): + """ + Test the use_main_default_location setter with an int + """ + # GIVEN: A AreaPositionPage instance + page = AreaPositionPage() + + # WHEN: The property is set + page.use_main_default_location = True + + # THEN: The combobox should be correct + assert page.main_position_check_box.isChecked() is True + + def test_get_main_x(self): + """ + Test the main_x getter + """ + # GIVEN: A AreaPositionPage instance with the combobox set to index 1 + page = AreaPositionPage() + page.main_x_spin_box.setValue(10) + + # WHEN: The property is accessed + result = page.main_x + + # THEN: The result should be correct + assert result == 10 + + def test_set_main_x(self): + """ + Test the main_x setter with an int + """ + # GIVEN: A AreaPositionPage instance + page = AreaPositionPage() + + # WHEN: The property is set + page.main_x = 20 + + # THEN: The combobox should be correct + assert page.main_x_spin_box.value() == 20 + + def test_get_main_y(self): + """ + Test the main_y getter + """ + # GIVEN: A AreaPositionPage instance with the combobox set to indey 1 + page = AreaPositionPage() + page.main_y_spin_box.setValue(10) + + # WHEN: The property is accessed + result = page.main_y + + # THEN: The result should be correct + assert result == 10 + + def test_set_main_y(self): + """ + Test the main_y setter with an int + """ + # GIVEN: A AreaPositionPage instance + page = AreaPositionPage() + + # WHEN: The property is set + page.main_y = 20 + + # THEN: The combobox should be correct + assert page.main_y_spin_box.value() == 20 + + def test_get_main_width(self): + """ + Test the main_width getter + """ + # GIVEN: A AreaPositionPage instance with the combobox set to indewidth 1 + page = AreaPositionPage() + page.main_width_spin_box.setValue(10) + + # WHEN: The property is accessed + result = page.main_width + + # THEN: The result should be correct + assert result == 10 + + def test_set_main_width(self): + """ + Test the main_width setter with an int + """ + # GIVEN: A AreaPositionPage instance + page = AreaPositionPage() + + # WHEN: The property is set + page.main_width = 20 + + # THEN: The combobox should be correct + assert page.main_width_spin_box.value() == 20 + + def test_get_main_height(self): + """ + Test the main_height getter + """ + # GIVEN: A AreaPositionPage instance with the combobox set to indeheight 1 + page = AreaPositionPage() + page.main_height_spin_box.setValue(10) + + # WHEN: The property is accessed + result = page.main_height + + # THEN: The result should be correct + assert result == 10 + + def test_set_main_height(self): + """ + Test the main_height setter with an int + """ + # GIVEN: A AreaPositionPage instance + page = AreaPositionPage() + + # WHEN: The property is set + page.main_height = 20 + + # THEN: The combobox should be correct + assert page.main_height_spin_box.value() == 20 + + def test_get_footer_x(self): + """ + Test the footer_x getter + """ + # GIVEN: A AreaPositionPage instance with the combobox set to index 1 + page = AreaPositionPage() + page.footer_x_spin_box.setValue(10) + + # WHEN: The property is accessed + result = page.footer_x + + # THEN: The result should be correct + assert result == 10 + + def test_set_footer_x(self): + """ + Test the footer_x setter with an int + """ + # GIVEN: A AreaPositionPage instance + page = AreaPositionPage() + + # WHEN: The property is set + page.footer_x = 20 + + # THEN: The combobox should be correct + assert page.footer_x_spin_box.value() == 20 + + def test_get_footer_y(self): + """ + Test the footer_y getter + """ + # GIVEN: A AreaPositionPage instance with the combobox set to indey 1 + page = AreaPositionPage() + page.footer_y_spin_box.setValue(10) + + # WHEN: The property is accessed + result = page.footer_y + + # THEN: The result should be correct + assert result == 10 + + def test_set_footer_y(self): + """ + Test the footer_y setter with an int + """ + # GIVEN: A AreaPositionPage instance + page = AreaPositionPage() + + # WHEN: The property is set + page.footer_y = 20 + + # THEN: The combobox should be correct + assert page.footer_y_spin_box.value() == 20 + + def test_get_footer_width(self): + """ + Test the footer_width getter + """ + # GIVEN: A AreaPositionPage instance with the combobox set to indewidth 1 + page = AreaPositionPage() + page.footer_width_spin_box.setValue(1900) + + # WHEN: The property is accessed + result = page.footer_width + + # THEN: The result should be correct + assert result == 1900 + + def test_set_footer_width(self): + """ + Test the footer_width setter with an int + """ + # GIVEN: A AreaPositionPage instance + page = AreaPositionPage() + + # WHEN: The property is set + page.footer_width = 1900 + + # THEN: The combobox should be correct + assert page.footer_width_spin_box.value() == 1900 + + def test_get_footer_height(self): + """ + Test the footer_height getter + """ + # GIVEN: A AreaPositionPage instance with the combobox set to indeheight 1 + page = AreaPositionPage() + page.footer_height_spin_box.setValue(1080) + + # WHEN: The property is accessed + result = page.footer_height + + # THEN: The result should be correct + assert result == 1080 + + def test_set_footer_height(self): + """ + Test the footer_height setter with an int + """ + # GIVEN: A AreaPositionPage instance + page = AreaPositionPage() + + # WHEN: The property is set + page.footer_height = 1080 + + # THEN: The combobox should be correct + assert page.footer_height_spin_box.value() == 1080 + + def test_get_use_footer_default_location(self): + """ + Test the use_footer_default_location getter + """ + # GIVEN: A AreaPositionPage instance with the combobox set to index 1 + page = AreaPositionPage() + page.footer_position_check_box.setChecked(False) + + # WHEN: The property is accessed + result = page.use_footer_default_location + + # THEN: The result should be correct + assert result is False + + def test_set_use_footer_default_location(self): + """ + Test the use_footer_default_location setter with an int + """ + # GIVEN: A AreaPositionPage instance + page = AreaPositionPage() + + # WHEN: The property is set + page.use_footer_default_location = True + + # THEN: The combobox should be correct + assert page.footer_position_check_box.isChecked() is True diff --git a/tests/openlp_core/pages/test_background.py b/tests/openlp_core/pages/test_background.py new file mode 100644 index 000000000..d6090dffa --- /dev/null +++ b/tests/openlp_core/pages/test_background.py @@ -0,0 +1,363 @@ +# -*- 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.pages.background package. +""" +from pathlib import Path +from unittest import TestCase +from unittest.mock import MagicMock + +import pytest + +from openlp.core.lib.theme import BackgroundType, BackgroundGradientType +from openlp.core.pages.background import BackgroundPage +from tests.helpers.testmixin import TestMixin + + +class TestBackgroundPage(TestCase, TestMixin): + + def setUp(self): + """Test setup""" + self.setup_application() + self.build_settings() + + def tearDown(self): + """Tear down tests""" + del self.app + + def test_init_(self): + """ + Test the initialisation of BackgroundPage + """ + # GIVEN: The BackgroundPage class + # WHEN: Initialising BackgroundPage + # THEN: We should have an instance of the widget with no errors + BackgroundPage() + + def test_on_background_type_index_changed(self): + """ + Test the _on_background_type_index_changed() slot + """ + # GIVEN: And instance of BackgroundPage and some mock widgets + page = BackgroundPage() + page.color_widgets = [MagicMock()] + page.gradient_widgets = [MagicMock()] + + # WHEN: _on_background_type_index_changed + page._on_background_type_index_changed(1) + + # THEN: The correct widgets should be visible + page.color_widgets[0].hide.assert_called_once() + page.gradient_widgets[0].hide.assert_called_once() + page.gradient_widgets[0].show.assert_called_once() + + def test_get_background_type(self): + """ + Test the background_type getter + """ + # GIVEN: A BackgroundPage instance with the combobox set to index 1 + page = BackgroundPage() + page.background_combo_box.setCurrentIndex(1) + + # WHEN: The property is accessed + result = page.background_type + + # THEN: The result should be correct + assert result == 'gradient' + + def test_set_background_type_int(self): + """ + Test the background_type setter with an int + """ + # GIVEN: A BackgroundPage instance + page = BackgroundPage() + + # WHEN: The property is set + page.background_type = BackgroundType.Image + + # THEN: The combobox should be correct + assert page.background_combo_box.currentIndex() == 2 + + def test_set_background_type_str(self): + """ + Test the background_type setter with a str + """ + # GIVEN: A BackgroundPage instance + page = BackgroundPage() + + # WHEN: The property is set + page.background_type = BackgroundType.to_string(BackgroundType.Gradient) + + # THEN: The combobox should be correct + assert page.background_combo_box.currentIndex() == 1 + + def test_set_background_type_exception(self): + """ + Test the background_type setter with something other than a str or int + """ + # GIVEN: A BackgroundPage instance + page = BackgroundPage() + + # WHEN: The property is set + # THEN: An exception is raised + with pytest.raises(TypeError, match='background_type must either be a string or an int'): + page.background_type = [] + + def test_get_color(self): + """ + Test the color getter + """ + # GIVEN: A BackgroundPage instance with the color button set to #f0f + page = BackgroundPage() + page.color_button.color = '#f0f' + + # WHEN: The property is accessed + result = page.color + + # THEN: The result should be correct + assert result == '#f0f' + + def test_set_color(self): + """ + Test the color setter + """ + # GIVEN: A BackgroundPage instance + page = BackgroundPage() + + # WHEN: The property is set + page.color = '#0f0' + + # THEN: The result should be correct + assert page.color_button.color == '#0f0' + + def test_get_gradient_type(self): + """ + Test the gradient_type getter + """ + # GIVEN: A BackgroundPage instance with the combobox set to index 1 + page = BackgroundPage() + page.gradient_combo_box.setCurrentIndex(1) + + # WHEN: The property is accessed + result = page.gradient_type + + # THEN: The result should be correct + assert result == 'vertical' + + def test_set_gradient_type_int(self): + """ + Test the gradient_type setter with an int + """ + # GIVEN: A BackgroundPage instance + page = BackgroundPage() + + # WHEN: The property is set + page.gradient_type = BackgroundGradientType.Horizontal + + # THEN: The combobox should be correct + assert page.gradient_combo_box.currentIndex() == 0 + + def test_set_gradient_type_str(self): + """ + Test the gradient_type setter with a str + """ + # GIVEN: A BackgroundPage instance + page = BackgroundPage() + + # WHEN: The property is set + page.gradient_type = BackgroundGradientType.to_string(BackgroundGradientType.Circular) + + # THEN: The combobox should be correct + assert page.gradient_combo_box.currentIndex() == 2 + + def test_set_gradient_type_exception(self): + """ + Test the gradient_type setter with something other than a str or int + """ + # GIVEN: A BackgroundPage instance + page = BackgroundPage() + + # WHEN: The property is set + # THEN: An exception is raised + with pytest.raises(TypeError, match='gradient_type must either be a string or an int'): + page.gradient_type = [] + + def test_get_gradient_start(self): + """ + Test the gradient_start getter + """ + # GIVEN: A BackgroundPage instance with the gradient_start button set to #f0f + page = BackgroundPage() + page.gradient_start_button.color = '#f0f' + + # WHEN: The property is accessed + result = page.gradient_start + + # THEN: The result should be correct + assert result == '#f0f' + + def test_set_gradient_start(self): + """ + Test the gradient_start setter + """ + # GIVEN: A BackgroundPage instance + page = BackgroundPage() + + # WHEN: The property is set + page.gradient_start = '#0f0' + + # THEN: The result should be correct + assert page.gradient_start_button.color == '#0f0' + + def test_get_gradient_end(self): + """ + Test the gradient_end getter + """ + # GIVEN: A BackgroundPage instance with the gradient_end button set to #f0f + page = BackgroundPage() + page.gradient_end_button.color = '#f0f' + + # WHEN: The property is accessed + result = page.gradient_end + + # THEN: The result should be correct + assert result == '#f0f' + + def test_set_gradient_end(self): + """ + Test the gradient_end setter + """ + # GIVEN: A BackgroundPage instance + page = BackgroundPage() + + # WHEN: The property is set + page.gradient_end = '#0f0' + + # THEN: The result should be correct + assert page.gradient_end_button.color == '#0f0' + + def test_get_image_color(self): + """ + Test the image_color getter + """ + # GIVEN: A BackgroundPage instance with the image_color button set to #f0f + page = BackgroundPage() + page.image_color_button.color = '#f0f' + + # WHEN: The property is accessed + result = page.image_color + + # THEN: The result should be correct + assert result == '#f0f' + + def test_set_image_color(self): + """ + Test the image_color setter + """ + # GIVEN: A BackgroundPage instance + page = BackgroundPage() + + # WHEN: The property is set + page.image_color = '#0f0' + + # THEN: The result should be correct + assert page.image_color_button.color == '#0f0' + + def test_get_image_path(self): + """ + Test the image_path getter + """ + # GIVEN: A BackgroundPage instance with the image_path edit set to a path + page = BackgroundPage() + page.image_path_edit.path = Path('.') + + # WHEN: The property is accessed + result = page.image_path + + # THEN: The result should be correct + assert result == Path('.') + + def test_set_image_path(self): + """ + Test the image_path setter + """ + # GIVEN: A BackgroundPage instance + page = BackgroundPage() + + # WHEN: The property is set + page.image_path = Path('openlp') + + # THEN: The result should be correct + assert page.image_path_edit.path == Path('openlp') + + def test_get_video_color(self): + """ + Test the video_color getter + """ + # GIVEN: A BackgroundPage instance with the video_color button set to #f0f + page = BackgroundPage() + page.video_color_button.color = '#f0f' + + # WHEN: The property is accessed + result = page.video_color + + # THEN: The result should be correct + assert result == '#f0f' + + def test_set_video_color(self): + """ + Test the video_color setter + """ + # GIVEN: A BackgroundPage instance + page = BackgroundPage() + + # WHEN: The property is set + page.video_color = '#0f0' + + # THEN: The result should be correct + assert page.video_color_button.color == '#0f0' + + def test_get_video_path(self): + """ + Test the video_path getter + """ + # GIVEN: A BackgroundPage instance with the video_path edit set to a path + page = BackgroundPage() + page.video_path_edit.path = Path('.') + + # WHEN: The property is accessed + result = page.video_path + + # THEN: The result should be correct + assert result == Path('.') + + def test_set_video_path(self): + """ + Test the video_path setter + """ + # GIVEN: A BackgroundPage instance + page = BackgroundPage() + + # WHEN: The property is set + page.video_path = Path('openlp') + + # THEN: The result should be correct + assert page.video_path_edit.path == Path('openlp') diff --git a/tests/openlp_core/pages/test_fontselection.py b/tests/openlp_core/pages/test_fontselection.py new file mode 100644 index 000000000..64ecb7600 --- /dev/null +++ b/tests/openlp_core/pages/test_fontselection.py @@ -0,0 +1,594 @@ +# -*- 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.widgets.fontselect package. +""" +from unittest import TestCase +from unittest.mock import MagicMock, patch + +import pytest + +from openlp.core.pages.fontselect import FontSelectPage +from tests.helpers.testmixin import TestMixin + + +class TestFontSelectPage(TestCase, TestMixin): + + def setUp(self): + """Test setup""" + self.setup_application() + self.build_settings() + + def tearDown(self): + """Tear down tests""" + del self.app + + def test_init_(self): + """ + Test the initialisation of FontSelectPage + """ + # GIVEN: The FontSelectPage class + # WHEN: Initialising FontSelectPage + # THEN: We should have an instance of the widget with no errors + FontSelectPage() + + def test_font_name_changed(self): + # GIVEN: An instance of FontSelectPage with a mocked out "font_name_changed" signal + instance = FontSelectPage() + instance.font_name_changed = MagicMock() + + # WHEN: The font name changes + instance._on_font_name_changed('Sans serif') + + # THEN: The signal should be emitted with the correct value + instance.font_name_changed.emit.assert_called_once_with('Sans serif') + + def test_font_name_changed_int(self): + # GIVEN: An instance of FontSelectPage with a mocked out "font_name_changed" signal + instance = FontSelectPage() + instance.font_name_changed = MagicMock() + + # WHEN: The font name changes + instance._on_font_name_changed(5) + + # THEN: The signal should be emitted with the correct value + assert instance.font_name_changed.emit.call_count == 0 + + def test_font_color_changed(self): + # GIVEN: An instance of FontSelectPage with a mocked out "font_color_changed" signal + instance = FontSelectPage() + instance.font_color_changed = MagicMock() + + # WHEN: The font color changes + instance._on_font_color_changed('#fff') + + # THEN: The signal should be emitted with the correct value + instance.font_color_changed.emit.assert_called_once_with('#fff') + + def test_is_bold_changed(self): + # GIVEN: An instance of FontSelectPage with a mocked out "is_bold_changed" signal + instance = FontSelectPage() + instance.is_bold_changed = MagicMock() + + # WHEN: The font name changes + instance._on_style_bold_toggled(True) + + # THEN: The signal should be emitted with the correct value + instance.is_bold_changed.emit.assert_called_once_with(True) + + def test_is_italic_changed(self): + # GIVEN: An instance of FontSelectPage with a mocked out "style_italic_changed" signal + instance = FontSelectPage() + instance.is_italic_changed = MagicMock() + + # WHEN: The font name changes + instance._on_style_italic_toggled(False) + + # THEN: The signal should be emitted with the correct value + instance.is_italic_changed.emit.assert_called_once_with(False) + + def test_font_size_changed(self): + # GIVEN: An instance of FontSelectPage with a mocked out "font_size_changed" signal + instance = FontSelectPage() + instance.font_size_changed = MagicMock() + + # WHEN: The font size changes + instance._on_font_size_changed(14) + + # THEN: The signal should be emitted with the correct value + instance.font_size_changed.emit.assert_called_once_with(14) + + def test_line_spacing_changed(self): + # GIVEN: An instance of FontSelectPage with a mocked out "line_spacing_changed" signal + instance = FontSelectPage() + instance.line_spacing_changed = MagicMock() + + # WHEN: The font name changes + instance._on_line_spacing_changed(1) + + # THEN: The signal should be emitted with the correct value + instance.line_spacing_changed.emit.assert_called_once_with(1) + + def test_is_outline_enabled_changed(self): + # GIVEN: An instance of FontSelectPage with a mocked out "outline_enabled_changed" signal + instance = FontSelectPage() + instance.is_outline_enabled_changed = MagicMock() + + # WHEN: The font name changes + instance._on_outline_toggled(True) + + # THEN: The signal should be emitted with the correct value + instance.is_outline_enabled_changed.emit.assert_called_once_with(True) + + def test_outline_color_changed(self): + # GIVEN: An instance of FontSelectPage with a mocked out "outline_color_changed" signal + instance = FontSelectPage() + instance.outline_color_changed = MagicMock() + + # WHEN: The font name changes + instance._on_outline_color_changed('#000') + + # THEN: The signal should be emitted with the correct value + instance.outline_color_changed.emit.assert_called_once_with('#000') + + def test_outline_size_changed(self): + # GIVEN: An instance of FontSelectPage with a mocked out "outline_size_changed" signal + instance = FontSelectPage() + instance.outline_size_changed = MagicMock() + + # WHEN: The font name changes + instance._on_outline_size_changed(2) + + # THEN: The signal should be emitted with the correct value + instance.outline_size_changed.emit.assert_called_once_with(2) + + def test_is_shadow_enabled_changed(self): + # GIVEN: An instance of FontSelectPage with a mocked out "is_shadow_enabled_changed" signal + instance = FontSelectPage() + instance.is_shadow_enabled_changed = MagicMock() + + # WHEN: The font name changes + instance._on_shadow_toggled(False) + + # THEN: The signal should be emitted with the correct value + instance.is_shadow_enabled_changed.emit.assert_called_once_with(False) + + def test_shadow_color_changed(self): + # GIVEN: An instance of FontSelectPage with a mocked out "shadow_color_changed" signal + instance = FontSelectPage() + instance.shadow_color_changed = MagicMock() + + # WHEN: The font name changes + instance._on_shadow_color_changed('#000') + + # THEN: The signal should be emitted with the correct value + instance.shadow_color_changed.emit.assert_called_once_with('#000') + + def test_shadow_size_changed(self): + # GIVEN: An instance of FontSelectPage with a mocked out "shadow_size_changed" signal + instance = FontSelectPage() + instance.shadow_size_changed = MagicMock() + + # WHEN: The font name changes + instance._on_shadow_size_changed(5) + + # THEN: The signal should be emitted with the correct value + instance.shadow_size_changed.emit.assert_called_once_with(5) + + def test_enable_features(self): + """ + Test that the `enable_features` method correctly enables widgets based on features + """ + # GIVEN: An instance of FontSelectPage with some mocks + instance = FontSelectPage() + mock_label = MagicMock() + mock_control = MagicMock() + instance.feature_widgets = {'test': [mock_label, mock_control]} + + # WHEN: The "test" feature is enabled + instance.enable_features('test') + + # THEN: "show()" is called on all the widgets + mock_label.show.assert_called_once() + mock_control.show.assert_called_once() + + def test_enable_missing_features(self): + """ + Test that the `enable_features` method correctly raises an error on a non-existent feature + """ + # GIVEN: An instance of FontSelectPage with some mocks + instance = FontSelectPage() + mock_label = MagicMock() + mock_control = MagicMock() + instance.feature_widgets = {'test1': [mock_label, mock_control]} + + # WHEN: The "test" feature is enabled + with pytest.raises(KeyError, match='No such feature'): + instance.enable_features('test2') + + def test_disable_features(self): + """ + Test that the `disable_features` method correctly disables widgets based on features + """ + # GIVEN: An instance of FontSelectPage with some mocks + instance = FontSelectPage() + mock_label = MagicMock() + mock_control = MagicMock() + instance.feature_widgets = {'test': [mock_label, mock_control]} + + # WHEN: The "test" feature is disabled + instance.disable_features('test') + + # THEN: "show()" is called on all the widgets + mock_label.hide.assert_called_once() + mock_control.hide.assert_called_once() + + def test_disable_missing_features(self): + """ + Test that the `disable_features` method correctly raises an error on a non-existent feature + """ + # GIVEN: An instance of FontSelectPage with some mocks + instance = FontSelectPage() + mock_label = MagicMock() + mock_control = MagicMock() + instance.feature_widgets = {'test1': [mock_label, mock_control]} + + # WHEN: The "test" feature is disabled + with pytest.raises(KeyError, match='No such feature'): + instance.disable_features('test2') + + def test_get_font_name_property(self): + """ + Test the `font_name` property + """ + # GIVEN: An instance of FontSelectPage with some mocks + instance = FontSelectPage() + instance.font_name_combobox.currentFont = MagicMock( + return_value=MagicMock(**{'family.return_value': 'Sans serif'})) + + # WHEN: The `font_name` propert is accessed + result = instance.font_name + + # THEN: The value should be correct + assert result == 'Sans serif' + + def test_set_font_name_property(self): + """ + Test setting the `font_name` property + """ + # GIVEN: An instance of FontSelectPage with some mocks + instance = FontSelectPage() + instance.font_name_combobox.setCurrentFont = MagicMock() + + # WHEN: The `font_name` property is set + with patch('openlp.core.pages.fontselect.QtGui.QFont') as MockFont: + mocked_font = MagicMock() + MockFont.return_value = mocked_font + instance.font_name = 'Serif' + + # THEN: The correct value should be set + MockFont.assert_called_once_with('Serif') + instance.font_name_combobox.setCurrentFont.assert_called_once_with(mocked_font) + + def test_get_font_color_property(self): + """ + Test the `font_color` property + """ + # GIVEN: An instance of FontSelectPage with some mocks + instance = FontSelectPage() + instance.font_color_button.color = '#000' + + # WHEN: The `font_color` propert is accessed + result = instance.font_color + + # THEN: The value should be correct + assert result == '#000' + + def test_set_font_color_property(self): + """ + Test setting the `font_color` property + """ + # GIVEN: An instance of FontSelectPage with some mocks + instance = FontSelectPage() + + # WHEN: The `font_color` property is set + instance.font_color = '#fff' + + # THEN: The correct value should be set + assert instance.font_color_button.color == '#fff' + + def test_get_is_bold_property(self): + """ + Test the `is_bold` property + """ + # GIVEN: An instance of FontSelectPage with some mocks + instance = FontSelectPage() + instance.style_bold_button.isChecked = MagicMock(return_value=False) + + # WHEN: The `is_bold` propert is accessed + result = instance.is_bold + + # THEN: The value should be correct + assert result is False + + def test_set_is_bold_property(self): + """ + Test setting the `is_bold` property + """ + # GIVEN: An instance of FontSelectPage with some mocks + instance = FontSelectPage() + instance.style_bold_button.setChecked = MagicMock() + + # WHEN: The `is_bold` property is set + instance.is_bold = True + + # THEN: The correct value should be set + instance.style_bold_button.setChecked.assert_called_once_with(True) + + def test_get_is_italic_property(self): + """ + Test the `is_italic` property + """ + # GIVEN: An instance of FontSelectPage with some mocks + instance = FontSelectPage() + instance.style_italic_button.isChecked = MagicMock(return_value=True) + + # WHEN: The `is_italic` propert is accessed + result = instance.is_italic + + # THEN: The value should be correct + assert result is True + + def test_set_is_italic_property(self): + """ + Test setting the `is_italic` property + """ + # GIVEN: An instance of FontSelectPage with some mocks + instance = FontSelectPage() + instance.style_italic_button.setChecked = MagicMock() + + # WHEN: The `is_italic` property is set + instance.is_italic = False + + # THEN: The correct value should be set + instance.style_italic_button.setChecked.assert_called_once_with(False) + + def test_get_font_size_property(self): + """ + Test the `font_size` property + """ + # GIVEN: An instance of FontSelectPage with some mocks + instance = FontSelectPage() + instance.font_size_spinbox.value = MagicMock(return_value=16) + + # WHEN: The `font_size` propert is accessed + result = instance.font_size + + # THEN: The value should be correct + assert result == 16 + + def test_set_font_size_property(self): + """ + Test setting the `font_size` property + """ + # GIVEN: An instance of FontSelectPage with some mocks + instance = FontSelectPage() + instance.font_size_spinbox.setValue = MagicMock() + + # WHEN: The `font_size` property is set + instance.font_size = 18 + + # THEN: The correct value should be set + instance.font_size_spinbox.setValue.assert_called_once_with(18) + + def test_get_line_spacing_property(self): + """ + Test the `line_spacing` property + """ + # GIVEN: An instance of FontSelectPage with some mocks + instance = FontSelectPage() + instance.line_spacing_spinbox.value = MagicMock(return_value=1) + + # WHEN: The `line_spacing` propert is accessed + result = instance.line_spacing + + # THEN: The value should be correct + assert result == 1 + + def test_set_line_spacing_property(self): + """ + Test setting the `line_spacing` property + """ + # GIVEN: An instance of FontSelectPage with some mocks + instance = FontSelectPage() + instance.line_spacing_spinbox.setValue = MagicMock() + + # WHEN: The `line_spacing` property is set + instance.line_spacing = 2 + + # THEN: The correct value should be set + instance.line_spacing_spinbox.setValue.assert_called_once_with(2) + + def test_get_is_outline_enabled_property(self): + """ + Test the `is_outline_enabled` property + """ + # GIVEN: An instance of FontSelectPage with some mocks + instance = FontSelectPage() + instance.outline_groupbox.isChecked = MagicMock(return_value=True) + + # WHEN: The `is_outline_enabled` propert is accessed + result = instance.is_outline_enabled + + # THEN: The value should be correct + assert result is True + + def test_set_is_outline_enabled_property(self): + """ + Test setting the `is_outline_enabled` property + """ + # GIVEN: An instance of FontSelectPage with some mocks + instance = FontSelectPage() + instance.outline_groupbox.setChecked = MagicMock() + + # WHEN: The `is_outline_enabled` property is set + instance.is_outline_enabled = False + + # THEN: The correct value should be set + instance.outline_groupbox.setChecked.assert_called_once_with(False) + + def test_get_outline_color_property(self): + """ + Test the `outline_color` property + """ + # GIVEN: An instance of FontSelectPage with some mocks + instance = FontSelectPage() + instance.outline_color_button.color = '#fff' + + # WHEN: The `outline_color` propert is accessed + result = instance.outline_color + + # THEN: The value should be correct + assert result == '#fff' + + def test_set_outline_color_property(self): + """ + Test setting the `outline_color` property + """ + # GIVEN: An instance of FontSelectPage with some mocks + instance = FontSelectPage() + + # WHEN: The `outline_color` property is set + instance.outline_color = '#000' + + # THEN: The correct value should be set + assert instance.outline_color_button.color == '#000' + + def test_get_outline_size_property(self): + """ + Test the `outline_size` property + """ + # GIVEN: An instance of FontSelectPage with some mocks + instance = FontSelectPage() + instance.outline_size_spinbox.value = MagicMock(return_value=2) + + # WHEN: The `outline_size` propert is accessed + result = instance.outline_size + + # THEN: The value should be correct + assert result == 2 + + def test_set_outline_size_property(self): + """ + Test setting the `outline_size` property + """ + # GIVEN: An instance of FontSelectPage with some mocks + instance = FontSelectPage() + instance.outline_size_spinbox.setValue = MagicMock() + + # WHEN: The `outline_size` property is set + instance.outline_size = 1 + + # THEN: The correct value should be set + instance.outline_size_spinbox.setValue.assert_called_once_with(1) + + def test_get_is_shadow_enabled_property(self): + """ + Test the `is_shadow_enabled` property + """ + # GIVEN: An instance of FontSelectPage with some mocks + instance = FontSelectPage() + instance.shadow_groupbox.isChecked = MagicMock(return_value=False) + + # WHEN: The `is_shadow_enabled` propert is accessed + result = instance.is_shadow_enabled + + # THEN: The value should be correct + assert result is False + + def test_set_is_shadow_enabled_property(self): + """ + Test setting the `is_shadow_enabled` property + """ + # GIVEN: An instance of FontSelectPage with some mocks + instance = FontSelectPage() + instance.shadow_groupbox.setChecked = MagicMock() + + # WHEN: The `is_shadow_enabled` property is set + instance.is_shadow_enabled = True + + # THEN: The correct value should be set + instance.shadow_groupbox.setChecked.assert_called_once_with(True) + + def test_get_shadow_color_property(self): + """ + Test the `shadow_color` property + """ + # GIVEN: An instance of FontSelectPage with some mocks + instance = FontSelectPage() + instance.shadow_color_button.color = '#000' + + # WHEN: The `shadow_color` propert is accessed + result = instance.shadow_color + + # THEN: The value should be correct + assert result == '#000' + + def test_set_shadow_color_property(self): + """ + Test setting the `shadow_color` property + """ + # GIVEN: An instance of FontSelectPage with some mocks + instance = FontSelectPage() + + # WHEN: The `shadow_color` property is set + instance.shadow_color = '#fff' + + # THEN: The correct value should be set + instance.shadow_color_button.color == '#fff' + + def test_get_shadow_size_property(self): + """ + Test the `shadow_size` property + """ + # GIVEN: An instance of FontSelectPage with some mocks + instance = FontSelectPage() + instance.shadow_size_spinbox.value = MagicMock(return_value=5) + + # WHEN: The `shadow_size` propert is accessed + result = instance.shadow_size + + # THEN: The value should be correct + assert result == 5 + + def test_set_shadow_size_property(self): + """ + Test setting the `shadow_size` property + """ + # GIVEN: An instance of FontSelectPage with some mocks + instance = FontSelectPage() + instance.shadow_size_spinbox.setValue = MagicMock() + + # WHEN: The `shadow_size` property is set + instance.shadow_size = 10 + + # THEN: The correct value should be set + instance.shadow_size_spinbox.setValue.assert_called_once_with(10) diff --git a/tests/openlp_core/pages/test_gridlayoutpage.py b/tests/openlp_core/pages/test_gridlayoutpage.py new file mode 100644 index 000000000..f813586c2 --- /dev/null +++ b/tests/openlp_core/pages/test_gridlayoutpage.py @@ -0,0 +1,97 @@ +# -*- 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.pages package. +""" +from unittest import TestCase +from unittest.mock import MagicMock, call, patch + +import pytest + +from openlp.core.pages import GridLayoutPage +from tests.helpers.testmixin import TestMixin + + +class TestGridLayoutPage(TestCase, TestMixin): + + def setUp(self): + """Test setup""" + self.setup_application() + self.build_settings() + + def tearDown(self): + """Tear down tests""" + del self.app + + @patch('openlp.core.pages.GridLayoutPage.setup_ui') + @patch('openlp.core.pages.GridLayoutPage.retranslate_ui') + def test_resize_event(self, mocked_retranslate_ui, mocked_setup_ui): + """ + Test that the `resizeEvent()` method called the `resize_columns()` method. + """ + # GIVEN: An instance of GridLayoutPage with a mocked out "resize_columns" method + instance = GridLayoutPage() + instance.resize_columns = MagicMock() + + # WHEN: resizeEvent is called + instance.resizeEvent(None) + + # THEN: resize_widgets should have been called + instance.resize_columns.assert_called_once() + + def test_unimplemented_setup_ui(self): + """ + Test that setup_ui() throws a NotImplementedError + """ + with pytest.raises(NotImplementedError, match='Descendant pages need to implement setup_ui'): + GridLayoutPage() + + @patch('openlp.core.pages.GridLayoutPage.setup_ui') + def test_unimplemented_retranslate_ui(self, mocked_setup_ui): + """ + Test that retranslate_ui() throws a NotImplementedError + """ + with pytest.raises(NotImplementedError, match='Descendant pages need to implement retranslate_ui'): + GridLayoutPage() + + @patch('openlp.core.pages.GridLayoutPage.setup_ui') + @patch('openlp.core.pages.GridLayoutPage.retranslate_ui') + def test_resize_columns(self, mocked_retranslate_ui, mocked_setup_ui): + """ + Test the `resize_columns()` method with an implemented page + """ + # GIVEN: An instance of GridLayoutPage and various mocked out methods + instance = GridLayoutPage() + instance.layout.contentsRect = MagicMock(return_value=MagicMock(**{'width.return_value': 100})) + instance.layout.horizontalSpacing = MagicMock(return_value=6) + instance.layout.columnCount = MagicMock(return_value=4) + instance.layout.setColumnMinimumWidth = MagicMock() + + # WHEN: `resize_columns()` is called + instance.resize_columns() + + # THEN: The column widths should be set to 16 + instance.layout.contentsRect.assert_called_once() + instance.layout.horizontalSpacing.assert_called_once() + instance.layout.columnCount.assert_called_once() + assert instance._column_width == 20 + assert instance.layout.setColumnMinimumWidth.call_args_list == [call(0, 20), call(1, 20), + call(2, 20), call(3, 20)] diff --git a/tests/openlp_core/ui/test_themeform.py b/tests/openlp_core/ui/test_themeform.py index 54a434729..7fdc69bd7 100644 --- a/tests/openlp_core/ui/test_themeform.py +++ b/tests/openlp_core/ui/test_themeform.py @@ -19,19 +19,22 @@ # along with this program. If not, see . # ########################################################################## """ -Interface tests to test the ThemeWizard class and related methods. +Test the ThemeForm class and related methods. """ +from pathlib import Path from unittest import TestCase from unittest.mock import patch, MagicMock from openlp.core.common.registry import Registry +from openlp.core.lib.theme import BackgroundType from openlp.core.ui.themeform import ThemeForm +from openlp.core.ui.themelayoutform import ThemeLayoutForm from tests.helpers.testmixin import TestMixin -class TestThemeManager(TestCase, TestMixin): +class TestThemeForm(TestCase, TestMixin): """ - Test the functions in the ThemeManager module + Test the functions in the ThemeForm Class """ def setUp(self): """ @@ -41,8 +44,8 @@ class TestThemeManager(TestCase, TestMixin): mocked_renderer = MagicMock() Registry().register('renderer', mocked_renderer) - @patch('openlp.core.display.window.QtWidgets.QVBoxLayout') - def test_create_theme_wizard(self, mocked_qvboxlayout): + @patch('openlp.core.ui.themeform.ThemeForm._setup') + def test_create_theme_wizard(self, mocked_setup): """ Test creating a ThemeForm instance """ @@ -50,3 +53,358 @@ class TestThemeManager(TestCase, TestMixin): # WHEN: An object is created # THEN: There should be no problems ThemeForm(None) + + def test_setup(self): + """ + Test the _setup method + """ + # GIVEN: A ThemeForm instance + with patch('openlp.core.ui.themeform.ThemeForm._setup'): + theme_form = ThemeForm(None) + theme_form.setup_ui = MagicMock() + theme_form.main_area_page = MagicMock() + theme_form.footer_area_page = MagicMock() + + # WHEN: _setup() is called + theme_form._setup() + + # THEN: The right calls should have been made + theme_form.setup_ui.assert_called_once_with(theme_form) + assert theme_form.can_update_theme is True + assert theme_form.temp_background_filename is None + assert isinstance(theme_form.theme_layout_form, ThemeLayoutForm) + theme_form.main_area_page.font_name_changed.connect.assert_called_once_with(theme_form.calculate_lines) + theme_form.main_area_page.font_size_changed.connect.assert_called_once_with(theme_form.calculate_lines) + theme_form.main_area_page.line_spacing_changed.connect.assert_called_once_with(theme_form.calculate_lines) + theme_form.main_area_page.is_outline_enabled_changed.connect.assert_called_once_with( + theme_form.on_outline_toggled) + theme_form.main_area_page.outline_size_changed.connect.assert_called_once_with(theme_form.calculate_lines) + theme_form.main_area_page.is_shadow_enabled_changed.connect.assert_called_once_with( + theme_form.on_shadow_toggled) + theme_form.main_area_page.shadow_size_changed.connect.assert_called_once_with(theme_form.calculate_lines) + theme_form.footer_area_page.font_name_changed.connect.assert_called_once_with(theme_form.update_theme) + theme_form.footer_area_page.font_size_changed.connect.assert_called_once_with(theme_form.update_theme) + + @patch('openlp.core.ui.themeform.ThemeForm._setup') + def test_set_defaults(self, mocked_setup): + """ + Test that the right methods are called by the set_defaults() method + """ + # GIVEN: A ThemeForm instance with mocked methods + theme_form = ThemeForm(None) + theme_form.restart = MagicMock() + theme_form.set_background_page_values = MagicMock() + theme_form.set_main_area_page_values = MagicMock() + theme_form.set_footer_area_page_values = MagicMock() + theme_form.set_alignment_page_values = MagicMock() + theme_form.set_position_page_values = MagicMock() + theme_form.set_preview_page_values = MagicMock() + + # WHEN: set_defaults() is called + theme_form.set_defaults() + + # THEN: all the mocks are called + theme_form.restart.assert_called_once() + theme_form.set_background_page_values.assert_called_once() + theme_form.set_main_area_page_values.assert_called_once() + theme_form.set_footer_area_page_values.assert_called_once() + theme_form.set_alignment_page_values.assert_called_once() + theme_form.set_position_page_values.assert_called_once() + theme_form.set_preview_page_values.assert_called_once() + + @patch('openlp.core.ui.themeform.ThemeForm._setup') + def test_calculate_lines(self, mocked_setup): + """ + Test the calculate_lines() method + """ + # GIVEN: A ThemeForm instance with some mocked methods + theme_form = ThemeForm(None) + theme_form.theme = None + theme_form.welcome_page = None + theme_form.currentPage = MagicMock() + theme_form.update_theme = MagicMock() + mocked_theme_manager = MagicMock() + Registry().register('theme_manager', mocked_theme_manager) + + # WHEN: calculate_lines() is called + theme_form.calculate_lines() + + # THEN: The mocks should have been called correctly + theme_form.currentPage.assert_called_once() + theme_form.update_theme.assert_called_once() + mocked_theme_manager.generate_image.assert_called_once_with(None, True) + + @patch('openlp.core.ui.themeform.ThemeForm._setup') + def test_update_lines_text(self, mocked_setup): + """ + Test the update_lines_text() method + """ + # GIVEN: A ThemeForm instance with some mocked methods + theme_form = ThemeForm(None) + theme_form.main_line_count_label = MagicMock() + + # WHEN: calculate_lines() is called + theme_form.update_lines_text(10) + + # THEN: The mocks should have been called correctly + theme_form.main_line_count_label.setText.assert_called_once_with('(approximately 10 lines per slide)') + + @patch('openlp.core.ui.themeform.QtWidgets.QWizard.resizeEvent') + @patch('openlp.core.ui.themeform.QtGui.QResizeEvent') + @patch('openlp.core.ui.themeform.ThemeForm._setup') + def test_resize_event(self, mocked_setup, MockResizeEvent, mocked_resizeEvent): + """ + Test that the resizeEvent method handles resizing correctly + """ + # GIVEN: A ThemeForm instance with a number of mocked methods + mocked_event = MagicMock() + MockResizeEvent.return_value = mocked_event + theme_form = ThemeForm(None) + theme_form.size = MagicMock(return_value=1920) + theme_form.preview_area_layout = MagicMock() + theme_form.preview_box = MagicMock(**{'width.return_value': 300}) + mocked_renderer = MagicMock(**{'width.return_value': 1920, 'height.return_value': 1080}) + Registry().remove('renderer') + Registry().register('renderer', mocked_renderer) + + # WHEN: resizeEvent() is called + theme_form.resizeEvent() + + # THEN: The correct calls should have been made + MockResizeEvent.assert_called_once_with(1920, 1920) + mocked_resizeEvent.assert_called_once_with(theme_form, mocked_event) + assert mocked_renderer.width.call_count == 2 + mocked_renderer.height.assert_called_once() + theme_form.preview_area_layout.set_aspect_ratio.assert_called_once_with(16 / 9) + theme_form.preview_box.set_scale.assert_called_once_with(float(300 / 1920)) + + @patch('openlp.core.ui.themeform.QtWidgets.QWizard.resizeEvent') + @patch('openlp.core.ui.themeform.QtGui.QResizeEvent') + @patch('openlp.core.ui.themeform.ThemeForm._setup') + def test_resize_event_dbze(self, mocked_setup, MockResizeEvent, mocked_resizeEvent): + """ + Test that the resizeEvent method handles a divide by zero exception correctly + """ + # GIVEN: A ThemeForm instance with a number of mocked methods + mocked_event = MagicMock() + MockResizeEvent.return_value = mocked_event + theme_form = ThemeForm(None) + theme_form.size = MagicMock(return_value=1920) + theme_form.preview_area_layout = MagicMock() + theme_form.preview_box = MagicMock(**{'width.return_value': 300}) + mocked_renderer = MagicMock(**{'width.return_value': 1920, 'height.return_value': 0}) + Registry().remove('renderer') + Registry().register('renderer', mocked_renderer) + + # WHEN: resizeEvent() is called + theme_form.resizeEvent() + + # THEN: The correct calls should have been made + MockResizeEvent.assert_called_once_with(1920, 1920) + mocked_resizeEvent.assert_called_once_with(theme_form, mocked_event) + assert mocked_renderer.width.call_count == 2 + mocked_renderer.height.assert_called_once() + theme_form.preview_area_layout.set_aspect_ratio.assert_called_once_with(1) + theme_form.preview_box.set_scale.assert_called_once_with(float(300 / 1920)) + + @patch('openlp.core.ui.themeform.QtWidgets.QMessageBox.critical') + @patch('openlp.core.ui.themeform.is_not_image_file') + @patch('openlp.core.ui.themeform.ThemeForm._setup') + def test_validate_current_page_with_image(self, mocked_setup, mocked_is_not_image_file, mocked_critical): + """ + Test the validateCurrentPage() method + """ + # GIVEN: An instance of ThemeForm with some mocks + theme_form = ThemeForm(None) + theme_form.background_page = MagicMock(background_type=BackgroundType.to_string(BackgroundType.Image), + image_path=Path('picture.jpg')) + theme_form.page = MagicMock(return_value=theme_form.background_page) + mocked_is_not_image_file.return_value = True + + # WHEN: validateCurrentPage() is called + result = theme_form.validateCurrentPage() + + # THEN: The right methods were called, and the result is False + mocked_critical.assert_called_once_with(theme_form, 'Background Image Empty', + 'You have not selected a background image. ' + 'Please select one before continuing.') + assert result is False + + @patch('openlp.core.ui.themeform.ThemeForm._setup') + def test_validate_current_page(self, mocked_setup): + """ + Test the validateCurrentPage() method + """ + # GIVEN: An instance of ThemeForm with some mocks + theme_form = ThemeForm(None) + theme_form.background_page = MagicMock() + theme_form.page = MagicMock() + + # WHEN: validateCurrentPage() is called + result = theme_form.validateCurrentPage() + + # THEN: The right methods were called, and the result is False + assert result is True + + @patch('openlp.core.ui.themeform.ThemeForm._setup') + def test_on_current_id_changed_preview(self, mocked_setup): + """ + Test the on_current_id_changed() method + """ + # GIVEN: An instance of ThemeForm with some mocks + theme_form = ThemeForm(None) + theme_form.theme = 'my fake theme' + theme_form.area_position_page = MagicMock() + theme_form.preview_page = MagicMock() + theme_form.page = MagicMock(return_value=theme_form.preview_page) + theme_form.update_theme = MagicMock() + theme_form.preview_box = MagicMock(**{'width.return_value': 300}) + theme_form.preview_area_layout = MagicMock() + theme_form.resizeEvent = MagicMock() + mocked_renderer = MagicMock(**{'width.return_value': 1920, 'height.return_value': 0}) + Registry().remove('renderer') + Registry().register('renderer', mocked_renderer) + + # WHEN: on_current_id_changed() is called + theme_form.on_current_id_changed(1) + + # THEN: The right options should have been set + theme_form.update_theme.assert_called_once() + theme_form.preview_box.set_theme.assert_called_once_with('my fake theme') + theme_form.preview_box.clear_slides.assert_called_once() + theme_form.preview_box.set_scale.assert_called_once_with(float(300 / 1920)) + theme_form.preview_area_layout.set_aspect_ratio(16 / 9) + theme_form.resizeEvent.assert_called_once() + theme_form.preview_box.show.assert_called_once() + theme_form.preview_box.generate_preview.assert_called_once_with('my fake theme', False, False) + + @patch('openlp.core.ui.themeform.ThemeForm._setup') + def test_on_outline_toggled(self, mocked_setup): + """ + Test the on_outline_toggled() method + """ + # GIVEN: An instance of ThemeForm with some mocks + theme_form = ThemeForm(None) + theme_form.can_update_theme = True + theme_form.theme = MagicMock() + theme_form.calculate_lines = MagicMock() + + # WHEN: on_outline_toggled is called + theme_form.on_outline_toggled(True) + + # THEN: Everything is working right + assert theme_form.theme.font_main_outline is True + theme_form.calculate_lines.assert_called_once() + + @patch('openlp.core.ui.themeform.ThemeForm._setup') + def test_on_shadow_toggled(self, mocked_setup): + """ + Test the on_shadow_toggled() method + """ + # GIVEN: An instance of ThemeForm with some mocks + theme_form = ThemeForm(None) + theme_form.can_update_theme = True + theme_form.theme = MagicMock() + theme_form.calculate_lines = MagicMock() + + # WHEN: on_shadow_toggled is called + theme_form.on_shadow_toggled(True) + + # THEN: Everything is working right + assert theme_form.theme.font_main_shadow is True + theme_form.calculate_lines.assert_called_once() + + @patch('openlp.core.ui.themeform.ThemeForm._setup') + def test_initialise_page_background(self, mocked_setup): + """ + Test the initializePage() method with the background page + """ + # GIVEN: An instance of ThemeForm with some mocks + theme_form = ThemeForm(None) + theme_form.background_page = MagicMock() + theme_form.page = MagicMock(return_value=theme_form.background_page) + theme_form.set_background_page_values = MagicMock() + + # WHEN: on_shadow_toggled is called + theme_form.initializePage(0) + + # THEN: Everything is working right + theme_form.set_background_page_values.assert_called_once() + + @patch('openlp.core.ui.themeform.ThemeForm._setup') + def test_initialise_page_main_area(self, mocked_setup): + """ + Test the initializePage() method with the main_area page + """ + # GIVEN: An instance of ThemeForm with some mocks + theme_form = ThemeForm(None) + theme_form.background_page = MagicMock() + theme_form.main_area_page = MagicMock() + theme_form.page = MagicMock(return_value=theme_form.main_area_page) + theme_form.set_main_area_page_values = MagicMock() + + # WHEN: on_shadow_toggled is called + theme_form.initializePage(0) + + # THEN: Everything is working right + theme_form.set_main_area_page_values.assert_called_once() + + @patch('openlp.core.ui.themeform.ThemeForm._setup') + def test_initialise_page_footer_area(self, mocked_setup): + """ + Test the initializePage() method with the footer_area page + """ + # GIVEN: An instance of ThemeForm with some mocks + theme_form = ThemeForm(None) + theme_form.background_page = MagicMock() + theme_form.main_area_page = MagicMock() + theme_form.footer_area_page = MagicMock() + theme_form.page = MagicMock(return_value=theme_form.footer_area_page) + theme_form.set_footer_area_page_values = MagicMock() + + # WHEN: on_shadow_toggled is called + theme_form.initializePage(0) + + # THEN: Everything is working right + theme_form.set_footer_area_page_values.assert_called_once() + + @patch('openlp.core.ui.themeform.ThemeForm._setup') + def test_initialise_page_alignment(self, mocked_setup): + """ + Test the initializePage() method with the alignment page + """ + # GIVEN: An instance of ThemeForm with some mocks + theme_form = ThemeForm(None) + theme_form.background_page = MagicMock() + theme_form.main_area_page = MagicMock() + theme_form.footer_area_page = MagicMock() + theme_form.alignment_page = MagicMock() + theme_form.page = MagicMock(return_value=theme_form.alignment_page) + theme_form.set_alignment_page_values = MagicMock() + + # WHEN: on_shadow_toggled is called + theme_form.initializePage(0) + + # THEN: Everything is working right + theme_form.set_alignment_page_values.assert_called_once() + + @patch('openlp.core.ui.themeform.ThemeForm._setup') + def test_initialise_page_area_position(self, mocked_setup): + """ + Test the initializePage() method with the area_position page + """ + # GIVEN: An instance of ThemeForm with some mocks + theme_form = ThemeForm(None) + theme_form.background_page = MagicMock() + theme_form.main_area_page = MagicMock() + theme_form.footer_area_page = MagicMock() + theme_form.alignment_page = MagicMock() + theme_form.area_position_page = MagicMock() + theme_form.page = MagicMock(return_value=theme_form.area_position_page) + theme_form.set_position_page_values = MagicMock() + + # WHEN: on_shadow_toggled is called + theme_form.initializePage(0) + + # THEN: Everything is working right + theme_form.set_position_page_values.assert_called_once() diff --git a/tests/openlp_core/widgets/test_widgets.py b/tests/openlp_core/widgets/test_widgets.py index 796021807..5754e4346 100644 --- a/tests/openlp_core/widgets/test_widgets.py +++ b/tests/openlp_core/widgets/test_widgets.py @@ -24,12 +24,11 @@ Package to test the openlp.core.widgets.widgets package. from unittest import TestCase from unittest.mock import MagicMock, call, patch -import pytest from PyQt5 import QtCore, QtWidgets from openlp.core.common.settings import ProxyMode from openlp.core.display.screens import Screen -from openlp.core.widgets.widgets import ProxyWidget, ProxyDialog, ScreenButton, ScreenSelectionWidget, FontSelectWidget +from openlp.core.widgets.widgets import ProxyWidget, ProxyDialog, ScreenButton, ScreenSelectionWidget from tests.helpers.testmixin import TestMixin @@ -526,604 +525,3 @@ class TestScreenSelectionWidget(TestCase, TestMixin): 'checkbox for that screen.', parent=instance, question=False) assert instance.use_screen_check_box.isChecked() is True assert instance.display_group_box.isChecked() is True - - -class TestFontSelectWidget(TestCase, TestMixin): - - def setUp(self): - """Test setup""" - self.setup_application() - self.build_settings() - - def tearDown(self): - """Tear down tests""" - del self.app - - def test_init_(self): - """ - Test the initialisation of FontSelectWidget - """ - # GIVEN: The FontSelectWidget class - # WHEN: Initialising FontSelectWidget - # THEN: We should have an instance of the widget with no errors - FontSelectWidget() - - def test_resize_event(self): - """ - Test that the `resizeEvent()` method called the `resize_widgets()` method. - """ - # GIVEN: An instance of FontSelectWidget with a mocked out "resize_widgets" method - instance = FontSelectWidget() - instance.resize_widgets = MagicMock() - - # WHEN: resizeEvent is called - instance.resizeEvent(None) - - # THEN: resize_widgets should have been called - instance.resize_widgets.assert_called_once() - - def test_font_name_changed(self): - # GIVEN: An instance of FontSelectWidget with a mocked out "font_name_changed" signal - instance = FontSelectWidget() - instance.font_name_changed = MagicMock() - - # WHEN: The font name changes - instance._on_font_name_changed('Sans serif') - - # THEN: The signal should be emitted with the correct value - instance.font_name_changed.emit.assert_called_once_with('Sans serif') - - def test_font_name_changed_int(self): - # GIVEN: An instance of FontSelectWidget with a mocked out "font_name_changed" signal - instance = FontSelectWidget() - instance.font_name_changed = MagicMock() - - # WHEN: The font name changes - instance._on_font_name_changed(5) - - # THEN: The signal should be emitted with the correct value - assert instance.font_name_changed.emit.call_count == 0 - - def test_font_color_changed(self): - # GIVEN: An instance of FontSelectWidget with a mocked out "font_color_changed" signal - instance = FontSelectWidget() - instance.font_color_changed = MagicMock() - - # WHEN: The font color changes - instance._on_font_color_changed('#fff') - - # THEN: The signal should be emitted with the correct value - instance.font_color_changed.emit.assert_called_once_with('#fff') - - def test_is_bold_changed(self): - # GIVEN: An instance of FontSelectWidget with a mocked out "is_bold_changed" signal - instance = FontSelectWidget() - instance.is_bold_changed = MagicMock() - - # WHEN: The font name changes - instance._on_style_bold_toggled(True) - - # THEN: The signal should be emitted with the correct value - instance.is_bold_changed.emit.assert_called_once_with(True) - - def test_is_italic_changed(self): - # GIVEN: An instance of FontSelectWidget with a mocked out "style_italic_changed" signal - instance = FontSelectWidget() - instance.is_italic_changed = MagicMock() - - # WHEN: The font name changes - instance._on_style_italic_toggled(False) - - # THEN: The signal should be emitted with the correct value - instance.is_italic_changed.emit.assert_called_once_with(False) - - def test_font_size_changed(self): - # GIVEN: An instance of FontSelectWidget with a mocked out "font_size_changed" signal - instance = FontSelectWidget() - instance.font_size_changed = MagicMock() - - # WHEN: The font size changes - instance._on_font_size_changed(14) - - # THEN: The signal should be emitted with the correct value - instance.font_size_changed.emit.assert_called_once_with(14) - - def test_line_spacing_changed(self): - # GIVEN: An instance of FontSelectWidget with a mocked out "line_spacing_changed" signal - instance = FontSelectWidget() - instance.line_spacing_changed = MagicMock() - - # WHEN: The font name changes - instance._on_line_spacing_changed(1) - - # THEN: The signal should be emitted with the correct value - instance.line_spacing_changed.emit.assert_called_once_with(1) - - def test_is_outline_enabled_changed(self): - # GIVEN: An instance of FontSelectWidget with a mocked out "outline_enabled_changed" signal - instance = FontSelectWidget() - instance.is_outline_enabled_changed = MagicMock() - - # WHEN: The font name changes - instance._on_outline_toggled(True) - - # THEN: The signal should be emitted with the correct value - instance.is_outline_enabled_changed.emit.assert_called_once_with(True) - - def test_outline_color_changed(self): - # GIVEN: An instance of FontSelectWidget with a mocked out "outline_color_changed" signal - instance = FontSelectWidget() - instance.outline_color_changed = MagicMock() - - # WHEN: The font name changes - instance._on_outline_color_changed('#000') - - # THEN: The signal should be emitted with the correct value - instance.outline_color_changed.emit.assert_called_once_with('#000') - - def test_outline_size_changed(self): - # GIVEN: An instance of FontSelectWidget with a mocked out "outline_size_changed" signal - instance = FontSelectWidget() - instance.outline_size_changed = MagicMock() - - # WHEN: The font name changes - instance._on_outline_size_changed(2) - - # THEN: The signal should be emitted with the correct value - instance.outline_size_changed.emit.assert_called_once_with(2) - - def test_is_shadow_enabled_changed(self): - # GIVEN: An instance of FontSelectWidget with a mocked out "is_shadow_enabled_changed" signal - instance = FontSelectWidget() - instance.is_shadow_enabled_changed = MagicMock() - - # WHEN: The font name changes - instance._on_shadow_toggled(False) - - # THEN: The signal should be emitted with the correct value - instance.is_shadow_enabled_changed.emit.assert_called_once_with(False) - - def test_shadow_color_changed(self): - # GIVEN: An instance of FontSelectWidget with a mocked out "shadow_color_changed" signal - instance = FontSelectWidget() - instance.shadow_color_changed = MagicMock() - - # WHEN: The font name changes - instance._on_shadow_color_changed('#000') - - # THEN: The signal should be emitted with the correct value - instance.shadow_color_changed.emit.assert_called_once_with('#000') - - def test_shadow_size_changed(self): - # GIVEN: An instance of FontSelectWidget with a mocked out "shadow_size_changed" signal - instance = FontSelectWidget() - instance.shadow_size_changed = MagicMock() - - # WHEN: The font name changes - instance._on_shadow_size_changed(5) - - # THEN: The signal should be emitted with the correct value - instance.shadow_size_changed.emit.assert_called_once_with(5) - - def test_resize_widgets(self): - """ - Test the `resize_widgets()` method - """ - # GIVEN: An instance of FontSelectWidget and various mocked out methods - instance = FontSelectWidget() - instance.geometry = MagicMock(return_value=MagicMock(**{'width.return_value': 100})) - instance.layout.contentsMargins = MagicMock(return_value=MagicMock(**{'left.return_value': 8, - 'right.return_value': 8})) - instance.layout.horizontalSpacing = MagicMock(return_value=6) - instance.layout.setColumnMinimumWidth = MagicMock() - - # WHEN: `resize_widgets()` is called - instance.resize_widgets() - - # THEN: The column widths should be set to 16 - instance.geometry.assert_called_once() - instance.layout.contentsMargins.assert_called_once() - instance.layout.horizontalSpacing.assert_called_once() - assert instance._column_width == 16 - assert instance.layout.setColumnMinimumWidth.call_args_list == [call(0, 16), call(1, 16), - call(2, 16), call(3, 16)] - - def test_enable_features(self): - """ - Test that the `enable_features` method correctly enables widgets based on features - """ - # GIVEN: An instance of FontSelectWidget with some mocks - instance = FontSelectWidget() - mock_label = MagicMock() - mock_control = MagicMock() - instance.feature_widgets = {'test': [mock_label, mock_control]} - - # WHEN: The "test" feature is enabled - instance.enable_features('test') - - # THEN: "show()" is called on all the widgets - mock_label.show.assert_called_once() - mock_control.show.assert_called_once() - - def test_enable_missing_features(self): - """ - Test that the `enable_features` method correctly raises an error on a non-existent feature - """ - # GIVEN: An instance of FontSelectWidget with some mocks - instance = FontSelectWidget() - mock_label = MagicMock() - mock_control = MagicMock() - instance.feature_widgets = {'test1': [mock_label, mock_control]} - - # WHEN: The "test" feature is enabled - with pytest.raises(KeyError, match='No such feature'): - instance.enable_features('test2') - - def test_disable_features(self): - """ - Test that the `disable_features` method correctly disables widgets based on features - """ - # GIVEN: An instance of FontSelectWidget with some mocks - instance = FontSelectWidget() - mock_label = MagicMock() - mock_control = MagicMock() - instance.feature_widgets = {'test': [mock_label, mock_control]} - - # WHEN: The "test" feature is disabled - instance.disable_features('test') - - # THEN: "show()" is called on all the widgets - mock_label.hide.assert_called_once() - mock_control.hide.assert_called_once() - - def test_disable_missing_features(self): - """ - Test that the `disable_features` method correctly raises an error on a non-existent feature - """ - # GIVEN: An instance of FontSelectWidget with some mocks - instance = FontSelectWidget() - mock_label = MagicMock() - mock_control = MagicMock() - instance.feature_widgets = {'test1': [mock_label, mock_control]} - - # WHEN: The "test" feature is disabled - with pytest.raises(KeyError, match='No such feature'): - instance.disable_features('test2') - - def test_get_font_name_property(self): - """ - Test the `font_name` property - """ - # GIVEN: An instance of FontSelectWidget with some mocks - instance = FontSelectWidget() - instance.font_name_combobox.currentFont = MagicMock( - return_value=MagicMock(**{'family.return_value': 'Sans serif'})) - - # WHEN: The `font_name` propert is accessed - result = instance.font_name - - # THEN: The value should be correct - assert result == 'Sans serif' - - def test_set_font_name_property(self): - """ - Test setting the `font_name` property - """ - # GIVEN: An instance of FontSelectWidget with some mocks - instance = FontSelectWidget() - instance.font_name_combobox.setCurrentFont = MagicMock() - - # WHEN: The `font_name` property is set - with patch('openlp.core.widgets.widgets.QtGui.QFont') as MockFont: - mocked_font = MagicMock() - MockFont.return_value = mocked_font - instance.font_name = 'Serif' - - # THEN: The correct value should be set - MockFont.assert_called_once_with('Serif') - instance.font_name_combobox.setCurrentFont.assert_called_once_with(mocked_font) - - def test_get_font_color_property(self): - """ - Test the `font_color` property - """ - # GIVEN: An instance of FontSelectWidget with some mocks - instance = FontSelectWidget() - instance.font_color_button.color = '#000' - - # WHEN: The `font_color` propert is accessed - result = instance.font_color - - # THEN: The value should be correct - assert result == '#000' - - def test_set_font_color_property(self): - """ - Test setting the `font_color` property - """ - # GIVEN: An instance of FontSelectWidget with some mocks - instance = FontSelectWidget() - - # WHEN: The `font_color` property is set - instance.font_color = '#fff' - - # THEN: The correct value should be set - assert instance.font_color_button.color == '#fff' - - def test_get_is_bold_property(self): - """ - Test the `is_bold` property - """ - # GIVEN: An instance of FontSelectWidget with some mocks - instance = FontSelectWidget() - instance.style_bold_button.isChecked = MagicMock(return_value=False) - - # WHEN: The `is_bold` propert is accessed - result = instance.is_bold - - # THEN: The value should be correct - assert result is False - - def test_set_is_bold_property(self): - """ - Test setting the `is_bold` property - """ - # GIVEN: An instance of FontSelectWidget with some mocks - instance = FontSelectWidget() - instance.style_bold_button.setChecked = MagicMock() - - # WHEN: The `is_bold` property is set - instance.is_bold = True - - # THEN: The correct value should be set - instance.style_bold_button.setChecked.assert_called_once_with(True) - - def test_get_is_italic_property(self): - """ - Test the `is_italic` property - """ - # GIVEN: An instance of FontSelectWidget with some mocks - instance = FontSelectWidget() - instance.style_italic_button.isChecked = MagicMock(return_value=True) - - # WHEN: The `is_italic` propert is accessed - result = instance.is_italic - - # THEN: The value should be correct - assert result is True - - def test_set_is_italic_property(self): - """ - Test setting the `is_italic` property - """ - # GIVEN: An instance of FontSelectWidget with some mocks - instance = FontSelectWidget() - instance.style_italic_button.setChecked = MagicMock() - - # WHEN: The `is_italic` property is set - instance.is_italic = False - - # THEN: The correct value should be set - instance.style_italic_button.setChecked.assert_called_once_with(False) - - def test_get_font_size_property(self): - """ - Test the `font_size` property - """ - # GIVEN: An instance of FontSelectWidget with some mocks - instance = FontSelectWidget() - instance.font_size_spinbox.value = MagicMock(return_value=16) - - # WHEN: The `font_size` propert is accessed - result = instance.font_size - - # THEN: The value should be correct - assert result == 16 - - def test_set_font_size_property(self): - """ - Test setting the `font_size` property - """ - # GIVEN: An instance of FontSelectWidget with some mocks - instance = FontSelectWidget() - instance.font_size_spinbox.setValue = MagicMock() - - # WHEN: The `font_size` property is set - instance.font_size = 18 - - # THEN: The correct value should be set - instance.font_size_spinbox.setValue.assert_called_once_with(18) - - def test_get_line_spacing_property(self): - """ - Test the `line_spacing` property - """ - # GIVEN: An instance of FontSelectWidget with some mocks - instance = FontSelectWidget() - instance.line_spacing_spinbox.value = MagicMock(return_value=1) - - # WHEN: The `line_spacing` propert is accessed - result = instance.line_spacing - - # THEN: The value should be correct - assert result == 1 - - def test_set_line_spacing_property(self): - """ - Test setting the `line_spacing` property - """ - # GIVEN: An instance of FontSelectWidget with some mocks - instance = FontSelectWidget() - instance.line_spacing_spinbox.setValue = MagicMock() - - # WHEN: The `line_spacing` property is set - instance.line_spacing = 2 - - # THEN: The correct value should be set - instance.line_spacing_spinbox.setValue.assert_called_once_with(2) - - def test_get_is_outline_enabled_property(self): - """ - Test the `is_outline_enabled` property - """ - # GIVEN: An instance of FontSelectWidget with some mocks - instance = FontSelectWidget() - instance.outline_groupbox.isChecked = MagicMock(return_value=True) - - # WHEN: The `is_outline_enabled` propert is accessed - result = instance.is_outline_enabled - - # THEN: The value should be correct - assert result is True - - def test_set_is_outline_enabled_property(self): - """ - Test setting the `is_outline_enabled` property - """ - # GIVEN: An instance of FontSelectWidget with some mocks - instance = FontSelectWidget() - instance.outline_groupbox.setChecked = MagicMock() - - # WHEN: The `is_outline_enabled` property is set - instance.is_outline_enabled = False - - # THEN: The correct value should be set - instance.outline_groupbox.setChecked.assert_called_once_with(False) - - def test_get_outline_color_property(self): - """ - Test the `outline_color` property - """ - # GIVEN: An instance of FontSelectWidget with some mocks - instance = FontSelectWidget() - instance.outline_color_button.color = '#fff' - - # WHEN: The `outline_color` propert is accessed - result = instance.outline_color - - # THEN: The value should be correct - assert result == '#fff' - - def test_set_outline_color_property(self): - """ - Test setting the `outline_color` property - """ - # GIVEN: An instance of FontSelectWidget with some mocks - instance = FontSelectWidget() - - # WHEN: The `outline_color` property is set - instance.outline_color = '#000' - - # THEN: The correct value should be set - assert instance.outline_color_button.color == '#000' - - def test_get_outline_size_property(self): - """ - Test the `outline_size` property - """ - # GIVEN: An instance of FontSelectWidget with some mocks - instance = FontSelectWidget() - instance.outline_size_spinbox.value = MagicMock(return_value=2) - - # WHEN: The `outline_size` propert is accessed - result = instance.outline_size - - # THEN: The value should be correct - assert result == 2 - - def test_set_outline_size_property(self): - """ - Test setting the `outline_size` property - """ - # GIVEN: An instance of FontSelectWidget with some mocks - instance = FontSelectWidget() - instance.outline_size_spinbox.setValue = MagicMock() - - # WHEN: The `outline_size` property is set - instance.outline_size = 1 - - # THEN: The correct value should be set - instance.outline_size_spinbox.setValue.assert_called_once_with(1) - - def test_get_is_shadow_enabled_property(self): - """ - Test the `is_shadow_enabled` property - """ - # GIVEN: An instance of FontSelectWidget with some mocks - instance = FontSelectWidget() - instance.shadow_groupbox.isChecked = MagicMock(return_value=False) - - # WHEN: The `is_shadow_enabled` propert is accessed - result = instance.is_shadow_enabled - - # THEN: The value should be correct - assert result is False - - def test_set_is_shadow_enabled_property(self): - """ - Test setting the `is_shadow_enabled` property - """ - # GIVEN: An instance of FontSelectWidget with some mocks - instance = FontSelectWidget() - instance.shadow_groupbox.setChecked = MagicMock() - - # WHEN: The `is_shadow_enabled` property is set - instance.is_shadow_enabled = True - - # THEN: The correct value should be set - instance.shadow_groupbox.setChecked.assert_called_once_with(True) - - def test_get_shadow_color_property(self): - """ - Test the `shadow_color` property - """ - # GIVEN: An instance of FontSelectWidget with some mocks - instance = FontSelectWidget() - instance.shadow_color_button.color = '#000' - - # WHEN: The `shadow_color` propert is accessed - result = instance.shadow_color - - # THEN: The value should be correct - assert result == '#000' - - def test_set_shadow_color_property(self): - """ - Test setting the `shadow_color` property - """ - # GIVEN: An instance of FontSelectWidget with some mocks - instance = FontSelectWidget() - - # WHEN: The `shadow_color` property is set - instance.shadow_color = '#fff' - - # THEN: The correct value should be set - instance.shadow_color_button.color == '#fff' - - def test_get_shadow_size_property(self): - """ - Test the `shadow_size` property - """ - # GIVEN: An instance of FontSelectWidget with some mocks - instance = FontSelectWidget() - instance.shadow_size_spinbox.value = MagicMock(return_value=5) - - # WHEN: The `shadow_size` propert is accessed - result = instance.shadow_size - - # THEN: The value should be correct - assert result == 5 - - def test_set_shadow_size_property(self): - """ - Test setting the `shadow_size` property - """ - # GIVEN: An instance of FontSelectWidget with some mocks - instance = FontSelectWidget() - instance.shadow_size_spinbox.setValue = MagicMock() - - # WHEN: The `shadow_size` property is set - instance.shadow_size = 10 - - # THEN: The correct value should be set - instance.shadow_size_spinbox.setValue.assert_called_once_with(10)