# -*- coding: utf-8 -*- ########################################################################## # OpenLP - Open Source Lyrics Projection # # ---------------------------------------------------------------------- # # Copyright (c) 2008-2022 OpenLP Developers # # ---------------------------------------------------------------------- # # This program is free software: you can redistribute it and/or modify # # it under the terms of the GNU General Public License as published by # # the Free Software Foundation, either version 3 of the License, or # # (at your option) any later version. # # # # This program is distributed in the hope that it will be useful, # # but WITHOUT ANY WARRANTY; without even the implied warranty of # # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # # GNU General Public License for more details. # # # # You should have received a copy of the GNU General Public License # # along with this program. If not, see . # ########################################################################## """ The Theme wizard """ import logging from PyQt5 import QtCore, QtGui, QtWidgets from openlp.core.common import is_not_image_file from openlp.core.common.enum import ServiceItemType 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 BackgroundType from openlp.core.lib.ui import critical_error_message_box from openlp.core.ui.themelayoutform import ThemeLayoutForm from openlp.core.ui.themewizard import Ui_ThemeWizard log = logging.getLogger(__name__) class ThemeForm(QtWidgets.QWizard, Ui_ThemeWizard, RegistryProperties): """ This is the Theme Import Wizard, which allows easy creation and editing of OpenLP themes. """ log.info('ThemeWizardForm loaded') def __init__(self, parent): """ Instantiate the wizard, and run any extra setup we need to. :param parent: The QWidget-derived parent of the wizard. """ super(ThemeForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowCloseButtonHint) self._setup() def _setup(self): """ Set up the class. This method is mocked out by the tests. """ self.setup_ui(self) self.can_update_theme = True self.temp_background_filename = None self.theme_layout_form = ThemeLayoutForm(self) self.customButtonClicked.connect(self.on_custom_1_button_clicked) self.currentIdChanged.connect(self.on_current_id_changed) Registry().register_function('theme_line_count', self.update_lines_text) 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) self.setOption(QtWidgets.QWizard.HaveHelpButton, True) self.helpRequested.connect(self.provide_help) def provide_help(self): """ Provide help within the wizard by opening the appropriate page of the openlp manual in the user's browser """ QtGui.QDesktopServices.openUrl(QtCore.QUrl("https://manual.openlp.org/themes.html")) def set_defaults(self): """ Set up display at start of theme edit. """ self.restart() self.set_background_page_values() self.set_main_area_page_values() self.set_footer_area_page_values() self.set_alignment_page_values() self.set_position_page_values() self.set_preview_page_values() def calculate_lines(self, *args): """ Calculate the number of lines on a page by rendering text """ # Do not trigger on start up if self.currentPage() != self.welcome_page: self.update_theme() self.theme_manager.generate_image(self.theme, True) def update_lines_text(self, lines): """ Updates the lines on a page on the wizard :param lines: then number of lines to be displayed """ self.main_line_count_label.setText( translate('OpenLP.ThemeForm', '(approximately %d lines per slide)') % int(lines)) def resizeEvent(self, event=None): """ Rescale the theme preview thumbnail on resize events. """ if not event: event = QtGui.QResizeEvent(self.size(), self.size()) QtWidgets.QWizard.resizeEvent(self, event) try: self.display_aspect_ratio = self.renderer.width() / self.renderer.height() except ZeroDivisionError: self.display_aspect_ratio = 1 # Make sure we don't resize before the widgets are actually created if hasattr(self, 'preview_area_layout'): self.preview_area_layout.set_aspect_ratio(self.display_aspect_ratio) self.application.process_events() self.preview_box.set_scale(float(self.preview_box.width()) / self.renderer.width()) def validateCurrentPage(self): """ Validate the current page """ if self.page(self.currentId()) == self.background_page: background_image = BackgroundType.to_string(BackgroundType.Image) background_video = BackgroundType.to_string(BackgroundType.Video) background_stream = BackgroundType.to_string(BackgroundType.Stream) if 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.')) return False elif self.background_page.background_type == background_video and \ not self.background_page.video_path: QtWidgets.QMessageBox.critical(self, translate('OpenLP.ThemeWizard', 'Background Video Empty'), translate('OpenLP.ThemeWizard', 'You have not selected a ' 'background video. Please select one before continuing.')) return False elif self.background_page.background_type == background_stream and \ not self.background_page.stream_mrl.strip(): QtWidgets.QMessageBox.critical(self, translate('OpenLP.ThemeWizard', 'Background Stream Empty'), translate('OpenLP.ThemeWizard', 'You have not selected a ' 'background stream. Please select one before continuing.')) return False else: return True return True def on_current_id_changed(self, page_id): """ Detects Page changes and updates as appropriate. :param page_id: current page number """ enabled = self.page(page_id) == self.area_position_page self.setOption(QtWidgets.QWizard.HaveCustomButton1, enabled) if self.page(page_id) == self.preview_page: self.update_theme() self.resizeEvent() self.preview_box.clear_slides() self.preview_box.show() self.preview_box.generate_preview(self.theme, False, False) def on_custom_1_button_clicked(self, number): """ Generate layout preview and display the form. """ self.update_theme() width = self.renderer.width() height = self.renderer.height() pixmap = QtGui.QPixmap(width, height) pixmap.fill(QtCore.Qt.white) paint = QtGui.QPainter(pixmap) paint.setPen(QtGui.QPen(QtCore.Qt.blue, 2)) main_rect = QtCore.QRect(int(self.theme.font_main_x), int(self.theme.font_main_y), int(self.theme.font_main_width - 1), int(self.theme.font_main_height - 1)) paint.drawRect(main_rect) paint.setPen(QtGui.QPen(QtCore.Qt.red, 2)) footer_rect = QtCore.QRect(int(self.theme.font_footer_x), int(self.theme.font_footer_y), int(self.theme.font_footer_width - 1), int(self.theme.font_footer_height - 1)) paint.drawRect(footer_rect) paint.end() self.theme_layout_form.exec(pixmap) def on_outline_toggled(self, is_enabled): """ Change state as Outline check box changed """ if self.can_update_theme: self.theme.font_main_outline = is_enabled self.calculate_lines() def on_shadow_toggled(self, is_enabled): """ Change state as Shadow check box changed """ if self.can_update_theme: self.theme.font_main_shadow = is_enabled 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.can_update_theme = False self.set_defaults() self.can_update_theme = True self.theme_name_label.setVisible(not edit) self.theme_name_edit.setVisible(not edit) self.edit_mode = edit if edit: self.setWindowTitle(translate('OpenLP.ThemeWizard', 'Edit Theme - {name}' ).format(name=self.theme.theme_name)) self.next() else: self.setWindowTitle(UiStrings().NewTheme) return QtWidgets.QWizard.exec(self) def initializePage(self, page_id): """ Set up the pages for Initial run through dialog """ log.debug('initializePage {page}'.format(page=page_id)) wizard_page = self.page(page_id) if wizard_page == self.background_page: self.set_background_page_values() elif wizard_page == self.main_area_page: self.set_main_area_page_values() elif wizard_page == self.footer_area_page: self.set_footer_area_page_values() elif wizard_page == self.alignment_page: self.set_alignment_page_values() elif wizard_page == self.area_position_page: self.set_position_page_values() def set_background_page_values(self): """ 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.background_page.color = self.theme.background_color elif self.theme.background_type == BackgroundType.to_string(BackgroundType.Gradient): 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.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.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 elif self.theme.background_type == BackgroundType.to_string(BackgroundType.Stream): self.background_page.stream_color = self.theme.background_border_color self.background_page.stream_mrl = self.theme.background_source def set_main_area_page_values(self): """ Handle the display and state of the Main Area page. """ 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_area_page.font_name = self.theme.font_footer_name self.footer_area_page.font_color = self.theme.font_footer_color self.footer_area_page.is_bold = self.theme.font_footer_bold self.footer_area_page.is_italic = self.theme.font_footer_italics self.footer_area_page.font_size = self.theme.font_footer_size def set_position_page_values(self): """ Handle the display and state of the _position page. """ # Main Area self.area_position_page.use_main_default_location = not self.theme.font_main_override self.area_position_page.main_x = int(self.theme.font_main_x) self.area_position_page.main_y = int(self.theme.font_main_y) self.area_position_page.main_height = int(self.theme.font_main_height) self.area_position_page.main_width = int(self.theme.font_main_width) # Footer self.area_position_page.use_footer_default_location = not self.theme.font_footer_override self.area_position_page.footer_x = int(self.theme.font_footer_x) self.area_position_page.footer_y = int(self.theme.font_footer_y) self.area_position_page.footer_height = int(self.theme.font_footer_height) self.area_position_page.footer_width = int(self.theme.font_footer_width) def set_alignment_page_values(self): """ Handle the display and state of the Alignments page. """ 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 self.alignment_page.transition_direction = self.theme.display_slide_transition_direction self.alignment_page.is_transition_reverse_enabled = self.theme.display_slide_transition_reverse def set_preview_page_values(self): """ Handle the display and state of the Preview page. """ self.theme_name_edit.setText(self.theme.theme_name) self.preview_box.set_theme(self.theme, service_item_type=ServiceItemType.Text) def update_theme(self): """ Update the theme object from the UI for fields not already updated when the are changed. """ 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 elif self.theme.background_type == BackgroundType.to_string(BackgroundType.Stream): self.theme.background_border_color = self.background_page.stream_color self.theme.background_source = self.background_page.stream_mrl self.theme.background_filename = self.background_page.stream_mrl # main page self.theme.font_main_name = self.main_area_page.font_name self.theme.font_main_color = self.main_area_page.font_color 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 = self.main_area_page.is_outline_enabled self.theme.font_main_outline_color = self.main_area_page.outline_color self.theme.font_main_outline_size = self.main_area_page.outline_size self.theme.font_main_shadow = self.main_area_page.is_shadow_enabled self.theme.font_main_shadow_size = self.main_area_page.shadow_size self.main_area_page.shadow_color = self.theme.font_main_shadow_color 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_area_page.font_name self.theme.font_footer_color = self.footer_area_page.font_color self.theme.font_footer_size = self.footer_area_page.font_size self.theme.font_footer_bold = self.footer_area_page.is_bold self.theme.font_footer_italics = self.footer_area_page.is_italic # 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.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.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() # 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 self.theme.display_slide_transition_direction = self.alignment_page.transition_direction self.theme.display_slide_transition_reverse = self.alignment_page.is_transition_reverse_enabled def accept(self): """ Lets save the theme as Finish has been triggered """ # Save the theme 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'), translate('OpenLP.ThemeWizard', 'There is no name for this theme. Please enter one.')) return if self.theme.theme_name == '-1' or self.theme.theme_name == 'None': critical_error_message_box( translate('OpenLP.ThemeWizard', 'Theme Name Invalid'), translate('OpenLP.ThemeWizard', 'Invalid theme name. Please enter one.')) return destination_path = None if self.theme.background_type == BackgroundType.to_string(BackgroundType.Image) or \ 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 self.theme.background_type == BackgroundType.to_string(BackgroundType.Stream): destination_path = self.theme.background_source if not self.edit_mode and not self.theme_manager.check_if_theme_exists(self.theme.theme_name): return # Set the theme background to the cache location self.theme.background_filename = destination_path self.theme_manager.save_theme(self.theme) self.theme_manager.save_preview(self.theme.theme_name, self.preview_box.save_screenshot()) return QtWidgets.QDialog.accept(self)