forked from openlp/openlp
Finally got round to finishing the Theme clean up from 2,2!
Themes now save to JSON and read XML or JSON so fully compatible with 2.4. Add this to your merge proposal: -------------------------------- lp:~trb143/openlp/themecleanup (revision 2743) [SUCCESS] https://ci.openlp.io/job/Branch-01-Pull/2036/ [SUCCESS] https://ci.openlp.io/job/Branch-02-Functional-Tests/1946/ [SUCCESS] https://ci.openlp.io/job/Branch-03-Interface-Tests/1875/ [SUCCESS] https://ci.openlp.io/job/Branch-04a-Code_Analy... bzr-revno: 2744
This commit is contained in:
commit
21af845791
@ -26,7 +26,6 @@ import os
|
||||
import logging
|
||||
import json
|
||||
|
||||
from xml.dom.minidom import Document
|
||||
from lxml import etree, objectify
|
||||
from openlp.core.common import AppLocation, de_hump
|
||||
|
||||
@ -150,7 +149,7 @@ INTEGER_LIST = ['size', 'line_adjustment', 'x', 'height', 'y', 'width', 'shadow_
|
||||
'horizontal_align', 'vertical_align', 'wrap_style']
|
||||
|
||||
|
||||
class ThemeXML(object):
|
||||
class Theme(object):
|
||||
"""
|
||||
A class to encapsulate the Theme XML.
|
||||
"""
|
||||
@ -195,184 +194,6 @@ class ThemeXML(object):
|
||||
self.background_filename = self.background_filename.strip()
|
||||
self.background_filename = os.path.join(path, self.theme_name, self.background_filename)
|
||||
|
||||
def _new_document(self, name):
|
||||
"""
|
||||
Create a new theme XML document.
|
||||
"""
|
||||
self.theme_xml = Document()
|
||||
self.theme = self.theme_xml.createElement('theme')
|
||||
self.theme_xml.appendChild(self.theme)
|
||||
self.theme.setAttribute('version', '2.0')
|
||||
self.name = self.theme_xml.createElement('name')
|
||||
text_node = self.theme_xml.createTextNode(name)
|
||||
self.name.appendChild(text_node)
|
||||
self.theme.appendChild(self.name)
|
||||
|
||||
def add_background_transparent(self):
|
||||
"""
|
||||
Add a transparent background.
|
||||
"""
|
||||
background = self.theme_xml.createElement('background')
|
||||
background.setAttribute('type', 'transparent')
|
||||
self.theme.appendChild(background)
|
||||
|
||||
def add_background_solid(self, bkcolor):
|
||||
"""
|
||||
Add a Solid background.
|
||||
|
||||
:param bkcolor: The color of the background.
|
||||
"""
|
||||
background = self.theme_xml.createElement('background')
|
||||
background.setAttribute('type', 'solid')
|
||||
self.theme.appendChild(background)
|
||||
self.child_element(background, 'color', str(bkcolor))
|
||||
|
||||
def add_background_gradient(self, startcolor, endcolor, direction):
|
||||
"""
|
||||
Add a gradient background.
|
||||
|
||||
:param startcolor: The gradient's starting colour.
|
||||
:param endcolor: The gradient's ending colour.
|
||||
:param direction: The direction of the gradient.
|
||||
"""
|
||||
background = self.theme_xml.createElement('background')
|
||||
background.setAttribute('type', 'gradient')
|
||||
self.theme.appendChild(background)
|
||||
# Create startColor element
|
||||
self.child_element(background, 'startColor', str(startcolor))
|
||||
# Create endColor element
|
||||
self.child_element(background, 'endColor', str(endcolor))
|
||||
# Create direction element
|
||||
self.child_element(background, 'direction', str(direction))
|
||||
|
||||
def add_background_image(self, filename, border_color):
|
||||
"""
|
||||
Add a image background.
|
||||
|
||||
:param filename: The file name of the image.
|
||||
:param border_color:
|
||||
"""
|
||||
background = self.theme_xml.createElement('background')
|
||||
background.setAttribute('type', 'image')
|
||||
self.theme.appendChild(background)
|
||||
# Create Filename element
|
||||
self.child_element(background, 'filename', filename)
|
||||
# Create endColor element
|
||||
self.child_element(background, 'borderColor', str(border_color))
|
||||
|
||||
def add_background_video(self, filename, border_color):
|
||||
"""
|
||||
Add a video background.
|
||||
|
||||
:param filename: The file name of the video.
|
||||
:param border_color:
|
||||
"""
|
||||
background = self.theme_xml.createElement('background')
|
||||
background.setAttribute('type', 'video')
|
||||
self.theme.appendChild(background)
|
||||
# Create Filename element
|
||||
self.child_element(background, 'filename', filename)
|
||||
# Create endColor element
|
||||
self.child_element(background, 'borderColor', str(border_color))
|
||||
|
||||
def add_font(self, name, color, size, override, fonttype='main', bold='False', italics='False',
|
||||
line_adjustment=0, xpos=0, ypos=0, width=0, height=0, outline='False', outline_color='#ffffff',
|
||||
outline_pixel=2, shadow='False', shadow_color='#ffffff', shadow_pixel=5):
|
||||
"""
|
||||
Add a Font.
|
||||
|
||||
:param name: The name of the font.
|
||||
:param color: The colour of the font.
|
||||
:param size: The size of the font.
|
||||
:param override: Whether or not to override the default positioning of the theme.
|
||||
:param fonttype: The type of font, ``main`` or ``footer``. Defaults to ``main``.
|
||||
:param bold:
|
||||
:param italics: The weight of then font Defaults to 50 Normal
|
||||
:param line_adjustment: Does the font render to italics Defaults to 0 Normal
|
||||
:param xpos: The X position of the text block.
|
||||
:param ypos: The Y position of the text block.
|
||||
:param width: The width of the text block.
|
||||
:param height: The height of the text block.
|
||||
:param outline: Whether or not to show an outline.
|
||||
:param outline_color: The colour of the outline.
|
||||
:param outline_pixel: How big the Shadow is
|
||||
:param shadow: Whether or not to show a shadow.
|
||||
:param shadow_color: The colour of the shadow.
|
||||
:param shadow_pixel: How big the Shadow is
|
||||
"""
|
||||
background = self.theme_xml.createElement('font')
|
||||
background.setAttribute('type', fonttype)
|
||||
self.theme.appendChild(background)
|
||||
# Create Font name element
|
||||
self.child_element(background, 'name', name)
|
||||
# Create Font color element
|
||||
self.child_element(background, 'color', str(color))
|
||||
# Create Proportion name element
|
||||
self.child_element(background, 'size', str(size))
|
||||
# Create weight name element
|
||||
self.child_element(background, 'bold', str(bold))
|
||||
# Create italics name element
|
||||
self.child_element(background, 'italics', str(italics))
|
||||
# Create indentation name element
|
||||
self.child_element(background, 'line_adjustment', str(line_adjustment))
|
||||
# Create Location element
|
||||
element = self.theme_xml.createElement('location')
|
||||
element.setAttribute('override', str(override))
|
||||
element.setAttribute('x', str(xpos))
|
||||
element.setAttribute('y', str(ypos))
|
||||
element.setAttribute('width', str(width))
|
||||
element.setAttribute('height', str(height))
|
||||
background.appendChild(element)
|
||||
# Shadow
|
||||
element = self.theme_xml.createElement('shadow')
|
||||
element.setAttribute('shadowColor', str(shadow_color))
|
||||
element.setAttribute('shadowSize', str(shadow_pixel))
|
||||
value = self.theme_xml.createTextNode(str(shadow))
|
||||
element.appendChild(value)
|
||||
background.appendChild(element)
|
||||
# Outline
|
||||
element = self.theme_xml.createElement('outline')
|
||||
element.setAttribute('outlineColor', str(outline_color))
|
||||
element.setAttribute('outlineSize', str(outline_pixel))
|
||||
value = self.theme_xml.createTextNode(str(outline))
|
||||
element.appendChild(value)
|
||||
background.appendChild(element)
|
||||
|
||||
def add_display(self, horizontal, vertical, transition):
|
||||
"""
|
||||
Add a Display options.
|
||||
|
||||
:param horizontal: The horizontal alignment of the text.
|
||||
:param vertical: The vertical alignment of the text.
|
||||
:param transition: Whether the slide transition is active.
|
||||
"""
|
||||
background = self.theme_xml.createElement('display')
|
||||
self.theme.appendChild(background)
|
||||
# Horizontal alignment
|
||||
element = self.theme_xml.createElement('horizontalAlign')
|
||||
value = self.theme_xml.createTextNode(str(horizontal))
|
||||
element.appendChild(value)
|
||||
background.appendChild(element)
|
||||
# Vertical alignment
|
||||
element = self.theme_xml.createElement('verticalAlign')
|
||||
value = self.theme_xml.createTextNode(str(vertical))
|
||||
element.appendChild(value)
|
||||
background.appendChild(element)
|
||||
# Slide Transition
|
||||
element = self.theme_xml.createElement('slideTransition')
|
||||
value = self.theme_xml.createTextNode(str(transition))
|
||||
element.appendChild(value)
|
||||
background.appendChild(element)
|
||||
|
||||
def child_element(self, element, tag, value):
|
||||
"""
|
||||
Generic child element creator.
|
||||
"""
|
||||
child = self.theme_xml.createElement(tag)
|
||||
child.appendChild(self.theme_xml.createTextNode(value))
|
||||
element.appendChild(child)
|
||||
return child
|
||||
|
||||
def set_default_header_footer(self):
|
||||
"""
|
||||
Set the header and footer size into the current primary screen.
|
||||
@ -386,25 +207,24 @@ class ThemeXML(object):
|
||||
self.font_footer_y = current_screen['size'].height() * 9 / 10
|
||||
self.font_footer_height = current_screen['size'].height() / 10
|
||||
|
||||
def dump_xml(self):
|
||||
def load_theme(self, theme):
|
||||
"""
|
||||
Dump the XML to file used for debugging
|
||||
"""
|
||||
return self.theme_xml.toprettyxml(indent=' ')
|
||||
Convert the JSON file and expand it.
|
||||
|
||||
def extract_xml(self):
|
||||
:param theme: the theme string
|
||||
"""
|
||||
Print out the XML string.
|
||||
"""
|
||||
self._build_xml_from_attrs()
|
||||
return self.theme_xml.toxml('utf-8').decode('utf-8')
|
||||
jsn = json.loads(theme)
|
||||
self.expand_json(jsn)
|
||||
|
||||
def extract_formatted_xml(self):
|
||||
def export_theme(self):
|
||||
"""
|
||||
Pull out the XML string formatted for human consumption
|
||||
Loop through the fields and build a dictionary of them
|
||||
|
||||
"""
|
||||
self._build_xml_from_attrs()
|
||||
return self.theme_xml.toprettyxml(indent=' ', newl='\n', encoding='utf-8')
|
||||
theme_data = {}
|
||||
for attr, value in self.__dict__.items():
|
||||
theme_data["{attr}".format(attr=attr)] = value
|
||||
return json.dumps(theme_data)
|
||||
|
||||
def parse(self, xml):
|
||||
"""
|
||||
@ -461,7 +281,8 @@ class ThemeXML(object):
|
||||
if element.tag == 'name':
|
||||
self._create_attr('theme', element.tag, element.text)
|
||||
|
||||
def _translate_tags(self, master, element, value):
|
||||
@staticmethod
|
||||
def _translate_tags(master, element, value):
|
||||
"""
|
||||
Clean up XML removing and redefining tags
|
||||
"""
|
||||
@ -514,71 +335,5 @@ class ThemeXML(object):
|
||||
theme_strings = []
|
||||
for key in dir(self):
|
||||
if key[0:1] != '_':
|
||||
# TODO: Due to bound methods returned, I don't know how to write a proper test
|
||||
theme_strings.append('{key:>30}: {value}'.format(key=key, value=getattr(self, key)))
|
||||
return '\n'.join(theme_strings)
|
||||
|
||||
def _build_xml_from_attrs(self):
|
||||
"""
|
||||
Build the XML from the varables in the object
|
||||
"""
|
||||
self._new_document(self.theme_name)
|
||||
if self.background_type == BackgroundType.to_string(BackgroundType.Solid):
|
||||
self.add_background_solid(self.background_color)
|
||||
elif self.background_type == BackgroundType.to_string(BackgroundType.Gradient):
|
||||
self.add_background_gradient(
|
||||
self.background_start_color,
|
||||
self.background_end_color,
|
||||
self.background_direction
|
||||
)
|
||||
elif self.background_type == BackgroundType.to_string(BackgroundType.Image):
|
||||
filename = os.path.split(self.background_filename)[1]
|
||||
self.add_background_image(filename, self.background_border_color)
|
||||
elif self.background_type == BackgroundType.to_string(BackgroundType.Video):
|
||||
filename = os.path.split(self.background_filename)[1]
|
||||
self.add_background_video(filename, self.background_border_color)
|
||||
elif self.background_type == BackgroundType.to_string(BackgroundType.Transparent):
|
||||
self.add_background_transparent()
|
||||
self.add_font(
|
||||
self.font_main_name,
|
||||
self.font_main_color,
|
||||
self.font_main_size,
|
||||
self.font_main_override, 'main',
|
||||
self.font_main_bold,
|
||||
self.font_main_italics,
|
||||
self.font_main_line_adjustment,
|
||||
self.font_main_x,
|
||||
self.font_main_y,
|
||||
self.font_main_width,
|
||||
self.font_main_height,
|
||||
self.font_main_outline,
|
||||
self.font_main_outline_color,
|
||||
self.font_main_outline_size,
|
||||
self.font_main_shadow,
|
||||
self.font_main_shadow_color,
|
||||
self.font_main_shadow_size
|
||||
)
|
||||
self.add_font(
|
||||
self.font_footer_name,
|
||||
self.font_footer_color,
|
||||
self.font_footer_size,
|
||||
self.font_footer_override, 'footer',
|
||||
self.font_footer_bold,
|
||||
self.font_footer_italics,
|
||||
0, # line adjustment
|
||||
self.font_footer_x,
|
||||
self.font_footer_y,
|
||||
self.font_footer_width,
|
||||
self.font_footer_height,
|
||||
self.font_footer_outline,
|
||||
self.font_footer_outline_color,
|
||||
self.font_footer_outline_size,
|
||||
self.font_footer_shadow,
|
||||
self.font_footer_shadow_color,
|
||||
self.font_footer_shadow_size
|
||||
)
|
||||
self.add_display(
|
||||
self.display_horizontal_align,
|
||||
self.display_vertical_align,
|
||||
self.display_slide_transition
|
||||
)
|
||||
|
@ -698,7 +698,7 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ServiceMa
|
||||
translate('OpenLP.ServiceManager',
|
||||
'OpenLP Service Files (*.osz);; OpenLP Service Files - lite (*.oszl)'))
|
||||
else:
|
||||
file_name, filter_uesd = QtWidgets.QFileDialog.getSaveFileName(
|
||||
file_name, filter_used = QtWidgets.QFileDialog.getSaveFileName(
|
||||
self.main_window, UiStrings().SaveService, path,
|
||||
translate('OpenLP.ServiceManager', 'OpenLP Service Files (*.osz);;'))
|
||||
if not file_name:
|
||||
|
@ -22,6 +22,7 @@
|
||||
"""
|
||||
The Theme Manager manages adding, deleteing and modifying of themes.
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import zipfile
|
||||
import shutil
|
||||
@ -33,7 +34,7 @@ from openlp.core.common import Registry, RegistryProperties, AppLocation, Settin
|
||||
check_directory_exists, UiStrings, translate, is_win, get_filesystem_encoding, delete_file
|
||||
from openlp.core.lib import FileDialog, ImageSource, ValidationError, get_text_file_string, build_icon, \
|
||||
check_item_selected, create_thumb, validate_thumb
|
||||
from openlp.core.lib.theme import ThemeXML, BackgroundType
|
||||
from openlp.core.lib.theme import Theme, BackgroundType
|
||||
from openlp.core.lib.ui import critical_error_message_box, create_widget_action
|
||||
from openlp.core.ui import FileRenameForm, ThemeForm
|
||||
from openlp.core.ui.lib import OpenLPToolbar
|
||||
@ -245,7 +246,7 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ThemeManage
|
||||
their customisations.
|
||||
:param field:
|
||||
"""
|
||||
theme = ThemeXML()
|
||||
theme = Theme()
|
||||
theme.set_default_header_footer()
|
||||
self.theme_form.theme = theme
|
||||
self.theme_form.exec()
|
||||
@ -378,11 +379,12 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ThemeManage
|
||||
critical_error_message_box(message=translate('OpenLP.ThemeManager', 'You have not selected a theme.'))
|
||||
return
|
||||
theme = item.data(QtCore.Qt.UserRole)
|
||||
path = QtWidgets.QFileDialog.getExistingDirectory(self,
|
||||
translate('OpenLP.ThemeManager',
|
||||
'Save Theme - ({name})').format(name=theme),
|
||||
Settings().value(self.settings_section +
|
||||
'/last directory export'))
|
||||
path, filter_used = \
|
||||
QtWidgets.QFileDialog.getSaveFileName(self.main_window,
|
||||
translate('OpenLP.ThemeManager', 'Save Theme - ({name})').
|
||||
format(name=theme),
|
||||
Settings().value(self.settings_section + '/last directory export'),
|
||||
translate('OpenLP.ThemeManager', 'OpenLP Themes (*.otz)'))
|
||||
self.application.set_busy_cursor()
|
||||
if path:
|
||||
Settings().setValue(self.settings_section + '/last directory export', path)
|
||||
@ -393,13 +395,12 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ThemeManage
|
||||
'Your theme has been successfully exported.'))
|
||||
self.application.set_normal_cursor()
|
||||
|
||||
def _export_theme(self, path, theme):
|
||||
def _export_theme(self, theme_path, theme):
|
||||
"""
|
||||
Create the zipfile with the theme contents.
|
||||
:param path: Location where the zip file will be placed
|
||||
:param theme_path: Location where the zip file will be placed
|
||||
:param theme: The name of the theme to be exported
|
||||
"""
|
||||
theme_path = os.path.join(path, theme + '.otz')
|
||||
theme_zip = None
|
||||
try:
|
||||
theme_zip = zipfile.ZipFile(theme_path, 'w')
|
||||
@ -452,7 +453,7 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ThemeManage
|
||||
files = AppLocation.get_files(self.settings_section, '.png')
|
||||
# No themes have been found so create one
|
||||
if not files:
|
||||
theme = ThemeXML()
|
||||
theme = Theme()
|
||||
theme.theme_name = UiStrings().Default
|
||||
self._write_theme(theme, None, None)
|
||||
Settings().setValue(self.settings_section + '/global theme', theme.theme_name)
|
||||
@ -505,19 +506,27 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ThemeManage
|
||||
|
||||
def get_theme_data(self, theme_name):
|
||||
"""
|
||||
Returns a theme object from an XML file
|
||||
Returns a theme object from an XML or JSON file
|
||||
|
||||
:param theme_name: Name of the theme to load from file
|
||||
:return: The theme object.
|
||||
"""
|
||||
self.log_debug('get theme data for theme {name}'.format(name=theme_name))
|
||||
xml_file = os.path.join(self.path, str(theme_name), str(theme_name) + '.xml')
|
||||
xml = get_text_file_string(xml_file)
|
||||
if not xml:
|
||||
theme_file = os.path.join(self.path, str(theme_name), str(theme_name) + '.json')
|
||||
theme_data = get_text_file_string(theme_file)
|
||||
jsn = True
|
||||
if not theme_data:
|
||||
theme_file = os.path.join(self.path, str(theme_name), str(theme_name) + '.xml')
|
||||
theme_data = get_text_file_string(theme_file)
|
||||
jsn = False
|
||||
if not theme_data:
|
||||
self.log_debug('No theme data - using default theme')
|
||||
return ThemeXML()
|
||||
return Theme()
|
||||
else:
|
||||
return self._create_theme_from_xml(xml, self.path)
|
||||
if jsn:
|
||||
return self._create_theme_from_json(theme_data, self.path)
|
||||
else:
|
||||
return self._create_theme_from_xml(theme_data, self.path)
|
||||
|
||||
def over_write_message_box(self, theme_name):
|
||||
"""
|
||||
@ -547,11 +556,16 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ThemeManage
|
||||
out_file = None
|
||||
file_xml = None
|
||||
abort_import = True
|
||||
json_theme = False
|
||||
theme_name = ""
|
||||
try:
|
||||
theme_zip = zipfile.ZipFile(file_name)
|
||||
json_file = [name for name in theme_zip.namelist() if os.path.splitext(name)[1].lower() == '.json']
|
||||
if len(json_file) != 1:
|
||||
# TODO: remove XML handling at some point but would need a auto conversion to run first.
|
||||
xml_file = [name for name in theme_zip.namelist() if os.path.splitext(name)[1].lower() == '.xml']
|
||||
if len(xml_file) != 1:
|
||||
self.log_error('Theme contains "{val:d}" XML files'.format(val=len(xml_file)))
|
||||
self.log_error('Theme contains "{val:d}" theme files'.format(val=len(xml_file)))
|
||||
raise ValidationError
|
||||
xml_tree = ElementTree(element=XML(theme_zip.read(xml_file[0]))).getroot()
|
||||
theme_version = xml_tree.get('version', default=None)
|
||||
@ -559,6 +573,11 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ThemeManage
|
||||
self.log_error('Theme version is less than 2.0')
|
||||
raise ValidationError
|
||||
theme_name = xml_tree.find('name').text.strip()
|
||||
else:
|
||||
new_theme = Theme()
|
||||
new_theme.load_theme(theme_zip.read(json_file[0]).decode("utf-8"))
|
||||
theme_name = new_theme.theme_name
|
||||
json_theme = True
|
||||
theme_folder = os.path.join(directory, theme_name)
|
||||
theme_exists = os.path.exists(theme_folder)
|
||||
if theme_exists and not self.over_write_message_box(theme_name):
|
||||
@ -574,7 +593,7 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ThemeManage
|
||||
continue
|
||||
full_name = os.path.join(directory, out_name)
|
||||
check_directory_exists(os.path.dirname(full_name))
|
||||
if os.path.splitext(name)[1].lower() == '.xml':
|
||||
if os.path.splitext(name)[1].lower() == '.xml' or os.path.splitext(name)[1].lower() == '.json':
|
||||
file_xml = str(theme_zip.read(name), 'utf-8')
|
||||
out_file = open(full_name, 'w', encoding='utf-8')
|
||||
out_file.write(file_xml)
|
||||
@ -597,6 +616,9 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ThemeManage
|
||||
if not abort_import:
|
||||
# As all files are closed, we can create the Theme.
|
||||
if file_xml:
|
||||
if json_theme:
|
||||
theme = self._create_theme_from_json(file_xml, self.path)
|
||||
else:
|
||||
theme = self._create_theme_from_xml(file_xml, self.path)
|
||||
self.generate_and_save_image(theme_name, theme)
|
||||
# Only show the error message, when IOError was not raised (in
|
||||
@ -646,16 +668,16 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ThemeManage
|
||||
:param image_to: Where the Theme Image is to be saved to
|
||||
"""
|
||||
name = theme.theme_name
|
||||
theme_pretty_xml = theme.extract_formatted_xml()
|
||||
theme_pretty = theme.export_theme()
|
||||
theme_dir = os.path.join(self.path, name)
|
||||
check_directory_exists(theme_dir)
|
||||
theme_file = os.path.join(theme_dir, name + '.xml')
|
||||
theme_file = os.path.join(theme_dir, name + '.json')
|
||||
if self.old_background_image and image_to != self.old_background_image:
|
||||
delete_file(self.old_background_image)
|
||||
out_file = None
|
||||
try:
|
||||
out_file = open(theme_file, 'w', encoding='utf-8')
|
||||
out_file.write(theme_pretty_xml.decode('utf-8'))
|
||||
out_file.write(theme_pretty)
|
||||
except IOError:
|
||||
self.log_exception('Saving theme to file failed')
|
||||
finally:
|
||||
@ -717,7 +739,8 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ThemeManage
|
||||
"""
|
||||
return os.path.join(self.path, theme + '.png')
|
||||
|
||||
def _create_theme_from_xml(self, theme_xml, image_path):
|
||||
@staticmethod
|
||||
def _create_theme_from_xml(theme_xml, image_path):
|
||||
"""
|
||||
Return a theme object using information parsed from XML
|
||||
|
||||
@ -725,11 +748,25 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ThemeManage
|
||||
:param image_path: Where the theme image is stored
|
||||
:return: Theme data.
|
||||
"""
|
||||
theme = ThemeXML()
|
||||
theme = Theme()
|
||||
theme.parse(theme_xml)
|
||||
theme.extend_image_filename(image_path)
|
||||
return theme
|
||||
|
||||
@staticmethod
|
||||
def _create_theme_from_json(theme_json, image_path):
|
||||
"""
|
||||
Return a theme object using information parsed from JSON
|
||||
|
||||
:param theme_json: The Theme data object.
|
||||
:param image_path: Where the theme image is stored
|
||||
:return: Theme data.
|
||||
"""
|
||||
theme = Theme()
|
||||
theme.load_theme(theme_json)
|
||||
theme.extend_image_filename(image_path)
|
||||
return theme
|
||||
|
||||
def _validate_theme_action(self, select_text, confirm_title, confirm_text, test_plugin=True, confirm=True):
|
||||
"""
|
||||
Check to see if theme has been selected and the destructive action is allowed.
|
||||
|
@ -30,7 +30,7 @@ from PyQt5 import QtCore
|
||||
from openlp.core.common import Registry
|
||||
from openlp.core.lib import Renderer, ScreenList, ServiceItem, FormattingTags
|
||||
from openlp.core.lib.renderer import words_split, get_start_tags
|
||||
from openlp.core.lib.theme import ThemeXML
|
||||
from openlp.core.lib.theme import Theme
|
||||
|
||||
|
||||
SCREEN = {
|
||||
@ -189,7 +189,7 @@ class TestRenderer(TestCase):
|
||||
# GIVEN: test object and data
|
||||
mock_lyrics_css.return_value = ' FORMAT CSS; '
|
||||
mock_outline_css.return_value = ' OUTLINE CSS; '
|
||||
theme_data = ThemeXML()
|
||||
theme_data = Theme()
|
||||
theme_data.font_main_name = 'Arial'
|
||||
theme_data.font_main_size = 20
|
||||
theme_data.font_main_color = '#FFFFFF'
|
||||
|
@ -25,36 +25,30 @@ Package to test the openlp.core.lib.theme package.
|
||||
from unittest import TestCase
|
||||
import os
|
||||
|
||||
from openlp.core.lib.theme import ThemeXML
|
||||
from openlp.core.lib.theme import Theme
|
||||
|
||||
|
||||
class TestThemeXML(TestCase):
|
||||
class TestTheme(TestCase):
|
||||
"""
|
||||
Test the ThemeXML class
|
||||
Test the Theme class
|
||||
"""
|
||||
def test_new_theme(self):
|
||||
"""
|
||||
Test the ThemeXML constructor
|
||||
Test the Theme constructor
|
||||
"""
|
||||
# GIVEN: The ThemeXML class
|
||||
# GIVEN: The Theme class
|
||||
# WHEN: A theme object is created
|
||||
default_theme = ThemeXML()
|
||||
default_theme = Theme()
|
||||
|
||||
# THEN: The default values should be correct
|
||||
self.assertEqual('#000000', default_theme.background_border_color,
|
||||
'background_border_color should be "#000000"')
|
||||
self.assertEqual('solid', default_theme.background_type, 'background_type should be "solid"')
|
||||
self.assertEqual(0, default_theme.display_vertical_align, 'display_vertical_align should be 0')
|
||||
self.assertEqual('Arial', default_theme.font_footer_name, 'font_footer_name should be "Arial"')
|
||||
self.assertFalse(default_theme.font_main_bold, 'font_main_bold should be False')
|
||||
self.assertEqual(47, len(default_theme.__dict__), 'The theme should have 47 attributes')
|
||||
self.check_theme(default_theme)
|
||||
|
||||
def test_expand_json(self):
|
||||
"""
|
||||
Test the expand_json method
|
||||
"""
|
||||
# GIVEN: A ThemeXML object and some JSON to "expand"
|
||||
theme = ThemeXML()
|
||||
# GIVEN: A Theme object and some JSON to "expand"
|
||||
theme = Theme()
|
||||
theme_json = {
|
||||
'background': {
|
||||
'border_color': '#000000',
|
||||
@ -73,31 +67,48 @@ class TestThemeXML(TestCase):
|
||||
}
|
||||
}
|
||||
|
||||
# WHEN: ThemeXML.expand_json() is run
|
||||
# WHEN: Theme.expand_json() is run
|
||||
theme.expand_json(theme_json)
|
||||
|
||||
# THEN: The attributes should be set on the object
|
||||
self.assertEqual('#000000', theme.background_border_color, 'background_border_color should be "#000000"')
|
||||
self.assertEqual('solid', theme.background_type, 'background_type should be "solid"')
|
||||
self.assertEqual(0, theme.display_vertical_align, 'display_vertical_align should be 0')
|
||||
self.assertFalse(theme.font_footer_bold, 'font_footer_bold should be False')
|
||||
self.assertEqual('Arial', theme.font_main_name, 'font_main_name should be "Arial"')
|
||||
self.check_theme(theme)
|
||||
|
||||
def test_extend_image_filename(self):
|
||||
"""
|
||||
Test the extend_image_filename method
|
||||
"""
|
||||
# GIVEN: A theme object
|
||||
theme = ThemeXML()
|
||||
theme = Theme()
|
||||
theme.theme_name = 'MyBeautifulTheme '
|
||||
theme.background_filename = ' video.mp4'
|
||||
theme.background_type = 'video'
|
||||
path = os.path.expanduser('~')
|
||||
|
||||
# WHEN: ThemeXML.extend_image_filename is run
|
||||
# WHEN: Theme.extend_image_filename is run
|
||||
theme.extend_image_filename(path)
|
||||
|
||||
# THEN: The filename of the background should be correct
|
||||
expected_filename = os.path.join(path, 'MyBeautifulTheme', 'video.mp4')
|
||||
self.assertEqual(expected_filename, theme.background_filename)
|
||||
self.assertEqual('MyBeautifulTheme', theme.theme_name)
|
||||
|
||||
def test_save_retrieve(self):
|
||||
"""
|
||||
Load a dummy theme, save it and reload it
|
||||
"""
|
||||
# GIVEN: The default Theme class
|
||||
# WHEN: A theme object is created
|
||||
default_theme = Theme()
|
||||
# THEN: The default values should be correct
|
||||
save_theme_json = default_theme.export_theme()
|
||||
lt = Theme()
|
||||
lt.load_theme(save_theme_json)
|
||||
self.check_theme(lt)
|
||||
|
||||
def check_theme(self, theme):
|
||||
self.assertEqual('#000000', theme.background_border_color, 'background_border_color should be "#000000"')
|
||||
self.assertEqual('solid', theme.background_type, 'background_type should be "solid"')
|
||||
self.assertEqual(0, theme.display_vertical_align, 'display_vertical_align should be 0')
|
||||
self.assertFalse(theme.font_footer_bold, 'font_footer_bold should be False')
|
||||
self.assertEqual('Arial', theme.font_main_name, 'font_main_name should be "Arial"')
|
||||
self.assertEqual(47, len(theme.__dict__), 'The theme should have 47 attributes')
|
||||
|
@ -63,7 +63,7 @@ class TestThemeManager(TestCase):
|
||||
mocked_zipfile_init.return_value = None
|
||||
|
||||
# WHEN: The theme is exported
|
||||
theme_manager._export_theme(os.path.join('some', 'path'), 'Default')
|
||||
theme_manager._export_theme(os.path.join('some', 'path', 'Default.otz'), 'Default')
|
||||
|
||||
# THEN: The zipfile should be created at the given path
|
||||
mocked_zipfile_init.assert_called_with(os.path.join('some', 'path', 'Default.otz'), 'w')
|
||||
@ -126,8 +126,9 @@ class TestThemeManager(TestCase):
|
||||
theme_manager.path = ''
|
||||
mocked_theme = MagicMock()
|
||||
mocked_theme.theme_name = 'themename'
|
||||
mocked_theme.extract_formatted_xml = MagicMock()
|
||||
mocked_theme.extract_formatted_xml.return_value = 'fake_theme_xml'.encode()
|
||||
mocked_theme.filename = "filename"
|
||||
# mocked_theme.extract_formatted_xml = MagicMock()
|
||||
# mocked_theme.extract_formatted_xml.return_value = 'fake_theme_xml'.encode()
|
||||
|
||||
# WHEN: Calling _write_theme with path to different images
|
||||
file_name1 = os.path.join(TEST_RESOURCES_PATH, 'church.jpg')
|
||||
@ -148,14 +149,13 @@ class TestThemeManager(TestCase):
|
||||
theme_manager.path = self.temp_folder
|
||||
mocked_theme = MagicMock()
|
||||
mocked_theme.theme_name = 'theme 愛 name'
|
||||
mocked_theme.extract_formatted_xml = MagicMock()
|
||||
mocked_theme.extract_formatted_xml.return_value = 'fake theme 愛 XML'.encode()
|
||||
mocked_theme.export_theme.return_value = "{}"
|
||||
|
||||
# WHEN: Calling _write_theme with a theme with a name with special characters in it
|
||||
theme_manager._write_theme(mocked_theme, None, None)
|
||||
|
||||
# THEN: It should have been created
|
||||
self.assertTrue(os.path.exists(os.path.join(self.temp_folder, 'theme 愛 name', 'theme 愛 name.xml')),
|
||||
self.assertTrue(os.path.exists(os.path.join(self.temp_folder, 'theme 愛 name', 'theme 愛 name.json')),
|
||||
'Theme with special characters should have been created!')
|
||||
|
||||
def test_over_write_message_box_yes(self):
|
||||
|
Loading…
Reference in New Issue
Block a user