2009-04-06 18:45:45 +00:00
|
|
|
# -*- coding: utf-8 -*-
|
2012-12-28 22:06:43 +00:00
|
|
|
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
2009-07-14 13:51:27 +00:00
|
|
|
|
2009-09-08 19:58:05 +00:00
|
|
|
###############################################################################
|
|
|
|
# OpenLP - Open Source Lyrics Projection #
|
|
|
|
# --------------------------------------------------------------------------- #
|
2019-02-14 15:09:09 +00:00
|
|
|
# Copyright (c) 2008-2019 OpenLP Developers #
|
2009-09-08 19:58:05 +00:00
|
|
|
# --------------------------------------------------------------------------- #
|
|
|
|
# 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; version 2 of the License. #
|
|
|
|
# #
|
|
|
|
# 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, write to the Free Software Foundation, Inc., 59 #
|
|
|
|
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
|
|
|
###############################################################################
|
2010-06-19 17:31:42 +00:00
|
|
|
"""
|
|
|
|
Provide the theme XML and handling functions for OpenLP v2 themes.
|
|
|
|
"""
|
2013-10-13 13:51:13 +00:00
|
|
|
import json
|
2017-09-26 16:39:13 +00:00
|
|
|
import logging
|
2009-07-08 06:55:08 +00:00
|
|
|
|
2010-10-05 19:52:28 +00:00
|
|
|
from lxml import etree, objectify
|
2017-10-07 07:05:07 +00:00
|
|
|
|
|
|
|
from openlp.core.common import de_hump
|
|
|
|
from openlp.core.common.applocation import AppLocation
|
2017-09-26 16:39:13 +00:00
|
|
|
from openlp.core.common.json import OpenLPJsonDecoder, OpenLPJsonEncoder
|
2017-10-10 21:15:08 +00:00
|
|
|
from openlp.core.display.screens import ScreenList
|
2018-10-02 04:39:42 +00:00
|
|
|
from openlp.core.lib import get_text_file_string, str_to_bool
|
|
|
|
|
2009-07-08 06:55:08 +00:00
|
|
|
|
2010-10-09 10:59:49 +00:00
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
2013-02-01 19:58:18 +00:00
|
|
|
|
2010-10-16 13:54:57 +00:00
|
|
|
class BackgroundType(object):
|
2011-02-02 15:52:17 +00:00
|
|
|
"""
|
|
|
|
Type enumeration for backgrounds.
|
|
|
|
"""
|
2010-10-16 13:54:57 +00:00
|
|
|
Solid = 0
|
|
|
|
Gradient = 1
|
|
|
|
Image = 2
|
2012-01-04 17:19:49 +00:00
|
|
|
Transparent = 3
|
2016-04-30 15:40:23 +00:00
|
|
|
Video = 4
|
2010-10-16 13:54:57 +00:00
|
|
|
|
|
|
|
@staticmethod
|
2011-02-02 15:52:17 +00:00
|
|
|
def to_string(background_type):
|
|
|
|
"""
|
|
|
|
Return a string representation of a background type.
|
|
|
|
"""
|
|
|
|
if background_type == BackgroundType.Solid:
|
2013-08-31 18:17:38 +00:00
|
|
|
return 'solid'
|
2011-02-02 15:52:17 +00:00
|
|
|
elif background_type == BackgroundType.Gradient:
|
2013-08-31 18:17:38 +00:00
|
|
|
return 'gradient'
|
2011-02-02 15:52:17 +00:00
|
|
|
elif background_type == BackgroundType.Image:
|
2013-08-31 18:17:38 +00:00
|
|
|
return 'image'
|
2012-01-04 17:19:49 +00:00
|
|
|
elif background_type == BackgroundType.Transparent:
|
2013-08-31 18:17:38 +00:00
|
|
|
return 'transparent'
|
2016-04-30 15:40:23 +00:00
|
|
|
elif background_type == BackgroundType.Video:
|
|
|
|
return 'video'
|
2010-10-16 13:54:57 +00:00
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def from_string(type_string):
|
2011-02-02 15:52:17 +00:00
|
|
|
"""
|
|
|
|
Return a background type for the given string.
|
|
|
|
"""
|
2013-08-31 18:17:38 +00:00
|
|
|
if type_string == 'solid':
|
2010-10-16 13:54:57 +00:00
|
|
|
return BackgroundType.Solid
|
2013-08-31 18:17:38 +00:00
|
|
|
elif type_string == 'gradient':
|
2010-10-16 13:54:57 +00:00
|
|
|
return BackgroundType.Gradient
|
2013-08-31 18:17:38 +00:00
|
|
|
elif type_string == 'image':
|
2010-10-16 13:54:57 +00:00
|
|
|
return BackgroundType.Image
|
2013-08-31 18:17:38 +00:00
|
|
|
elif type_string == 'transparent':
|
2012-01-04 17:19:49 +00:00
|
|
|
return BackgroundType.Transparent
|
2016-04-30 15:40:23 +00:00
|
|
|
elif type_string == 'video':
|
|
|
|
return BackgroundType.Video
|
2010-10-16 13:54:57 +00:00
|
|
|
|
2011-02-25 17:27:06 +00:00
|
|
|
|
2010-10-16 13:54:57 +00:00
|
|
|
class BackgroundGradientType(object):
|
2011-02-02 15:52:17 +00:00
|
|
|
"""
|
|
|
|
Type enumeration for background gradients.
|
|
|
|
"""
|
2010-10-16 13:54:57 +00:00
|
|
|
Horizontal = 0
|
|
|
|
Vertical = 1
|
|
|
|
Circular = 2
|
|
|
|
LeftTop = 3
|
|
|
|
LeftBottom = 4
|
|
|
|
|
|
|
|
@staticmethod
|
2011-02-02 15:52:17 +00:00
|
|
|
def to_string(gradient_type):
|
|
|
|
"""
|
|
|
|
Return a string representation of a background gradient type.
|
|
|
|
"""
|
|
|
|
if gradient_type == BackgroundGradientType.Horizontal:
|
2013-08-31 18:17:38 +00:00
|
|
|
return 'horizontal'
|
2011-02-02 15:52:17 +00:00
|
|
|
elif gradient_type == BackgroundGradientType.Vertical:
|
2013-08-31 18:17:38 +00:00
|
|
|
return 'vertical'
|
2011-02-02 15:52:17 +00:00
|
|
|
elif gradient_type == BackgroundGradientType.Circular:
|
2013-08-31 18:17:38 +00:00
|
|
|
return 'circular'
|
2011-02-02 15:52:17 +00:00
|
|
|
elif gradient_type == BackgroundGradientType.LeftTop:
|
2013-08-31 18:17:38 +00:00
|
|
|
return 'leftTop'
|
2011-02-02 15:52:17 +00:00
|
|
|
elif gradient_type == BackgroundGradientType.LeftBottom:
|
2013-08-31 18:17:38 +00:00
|
|
|
return 'leftBottom'
|
2010-10-16 13:54:57 +00:00
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def from_string(type_string):
|
2011-02-02 15:52:17 +00:00
|
|
|
"""
|
|
|
|
Return a background gradient type for the given string.
|
|
|
|
"""
|
2013-08-31 18:17:38 +00:00
|
|
|
if type_string == 'horizontal':
|
2010-10-16 13:54:57 +00:00
|
|
|
return BackgroundGradientType.Horizontal
|
2013-08-31 18:17:38 +00:00
|
|
|
elif type_string == 'vertical':
|
2010-10-16 13:54:57 +00:00
|
|
|
return BackgroundGradientType.Vertical
|
2013-08-31 18:17:38 +00:00
|
|
|
elif type_string == 'circular':
|
2010-10-16 13:54:57 +00:00
|
|
|
return BackgroundGradientType.Circular
|
2013-08-31 18:17:38 +00:00
|
|
|
elif type_string == 'leftTop':
|
2010-10-16 13:54:57 +00:00
|
|
|
return BackgroundGradientType.LeftTop
|
2013-08-31 18:17:38 +00:00
|
|
|
elif type_string == 'leftBottom':
|
2010-10-16 13:54:57 +00:00
|
|
|
return BackgroundGradientType.LeftBottom
|
|
|
|
|
2011-02-10 22:49:30 +00:00
|
|
|
|
2010-10-17 18:58:42 +00:00
|
|
|
class HorizontalType(object):
|
2011-02-02 15:52:17 +00:00
|
|
|
"""
|
|
|
|
Type enumeration for horizontal alignment.
|
|
|
|
"""
|
2010-10-17 18:58:42 +00:00
|
|
|
Left = 0
|
2011-02-12 10:04:10 +00:00
|
|
|
Right = 1
|
2011-02-18 03:15:09 +00:00
|
|
|
Center = 2
|
2011-09-16 17:23:39 +00:00
|
|
|
Justify = 3
|
2010-10-17 18:58:42 +00:00
|
|
|
|
2013-08-31 18:17:38 +00:00
|
|
|
Names = ['left', 'right', 'center', 'justify']
|
2011-02-10 22:49:30 +00:00
|
|
|
|
|
|
|
|
2010-10-17 18:58:42 +00:00
|
|
|
class VerticalType(object):
|
2011-02-02 15:52:17 +00:00
|
|
|
"""
|
|
|
|
Type enumeration for vertical alignment.
|
|
|
|
"""
|
2010-10-17 18:58:42 +00:00
|
|
|
Top = 0
|
|
|
|
Middle = 1
|
|
|
|
Bottom = 2
|
|
|
|
|
2013-08-31 18:17:38 +00:00
|
|
|
Names = ['top', 'middle', 'bottom']
|
2011-02-10 22:49:30 +00:00
|
|
|
|
|
|
|
|
2013-08-31 18:17:38 +00:00
|
|
|
BOOLEAN_LIST = ['bold', 'italics', 'override', 'outline', 'shadow', 'slide_transition']
|
2010-09-11 06:59:36 +00:00
|
|
|
|
2013-08-31 18:17:38 +00:00
|
|
|
INTEGER_LIST = ['size', 'line_adjustment', 'x', 'height', 'y', 'width', 'shadow_size', 'outline_size',
|
2013-12-24 08:56:50 +00:00
|
|
|
'horizontal_align', 'vertical_align', 'wrap_style']
|
2010-09-11 06:59:36 +00:00
|
|
|
|
2011-02-25 17:27:06 +00:00
|
|
|
|
2017-05-13 07:47:22 +00:00
|
|
|
class Theme(object):
|
2009-07-08 06:55:08 +00:00
|
|
|
"""
|
|
|
|
A class to encapsulate the Theme XML.
|
|
|
|
"""
|
2009-03-28 20:12:22 +00:00
|
|
|
def __init__(self):
|
2009-07-08 06:55:08 +00:00
|
|
|
"""
|
|
|
|
Initialise the theme object.
|
|
|
|
"""
|
2013-10-13 15:52:04 +00:00
|
|
|
# basic theme object with defaults
|
2017-08-12 17:45:56 +00:00
|
|
|
json_path = AppLocation.get_directory(AppLocation.AppDir) / 'core' / 'lib' / 'json' / 'theme.json'
|
|
|
|
jsn = get_text_file_string(json_path)
|
2017-09-26 16:39:13 +00:00
|
|
|
self.load_theme(jsn)
|
|
|
|
self.background_filename = None
|
2013-10-18 18:10:47 +00:00
|
|
|
|
|
|
|
def expand_json(self, var, prev=None):
|
|
|
|
"""
|
|
|
|
Expand the json objects and make into variables.
|
|
|
|
|
2014-03-17 19:05:55 +00:00
|
|
|
:param var: The array list to be processed.
|
|
|
|
:param prev: The preceding string to add to the key to make the variable.
|
2013-10-18 18:10:47 +00:00
|
|
|
"""
|
|
|
|
for key, value in var.items():
|
|
|
|
if prev:
|
|
|
|
key = prev + "_" + key
|
|
|
|
if isinstance(value, dict):
|
|
|
|
self.expand_json(value, key)
|
|
|
|
else:
|
|
|
|
setattr(self, key, value)
|
2009-03-28 20:12:22 +00:00
|
|
|
|
2009-05-22 05:14:55 +00:00
|
|
|
def extend_image_filename(self, path):
|
|
|
|
"""
|
|
|
|
Add the path name to the image name so the background can be rendered.
|
2009-07-08 06:55:08 +00:00
|
|
|
|
2017-09-26 16:39:13 +00:00
|
|
|
:param openlp.core.common.path.Path path: The path name to be added.
|
|
|
|
:rtype: None
|
2009-05-22 05:14:55 +00:00
|
|
|
"""
|
2016-04-30 15:40:23 +00:00
|
|
|
if self.background_type == 'image' or self.background_type == 'video':
|
2010-10-11 16:43:14 +00:00
|
|
|
if self.background_filename and path:
|
|
|
|
self.theme_name = self.theme_name.strip()
|
2017-09-26 16:39:13 +00:00
|
|
|
self.background_filename = path / self.theme_name / self.background_filename
|
2009-05-16 19:47:30 +00:00
|
|
|
|
2012-05-20 14:05:06 +00:00
|
|
|
def set_default_header_footer(self):
|
2012-05-16 13:14:32 +00:00
|
|
|
"""
|
2012-05-28 02:43:00 +00:00
|
|
|
Set the header and footer size into the current primary screen.
|
|
|
|
10 px on each side is removed to allow for a border.
|
2012-05-16 13:14:32 +00:00
|
|
|
"""
|
2018-10-07 20:36:04 +00:00
|
|
|
current_screen_geometry = ScreenList().current.display_geometry
|
2012-05-20 14:05:06 +00:00
|
|
|
self.font_main_y = 0
|
2018-10-07 20:36:04 +00:00
|
|
|
self.font_main_width = current_screen_geometry.width() - 20
|
|
|
|
self.font_main_height = current_screen_geometry.height() * 9 / 10
|
|
|
|
self.font_footer_width = current_screen_geometry.width() - 20
|
|
|
|
self.font_footer_y = current_screen_geometry.height() * 9 / 10
|
|
|
|
self.font_footer_height = current_screen_geometry.height() / 10
|
2012-05-16 13:14:32 +00:00
|
|
|
|
2017-09-26 16:39:13 +00:00
|
|
|
def load_theme(self, theme, theme_path=None):
|
2009-09-04 22:27:53 +00:00
|
|
|
"""
|
2017-05-24 19:31:48 +00:00
|
|
|
Convert the JSON file and expand it.
|
2017-05-24 19:55:30 +00:00
|
|
|
|
2017-05-13 07:47:22 +00:00
|
|
|
:param theme: the theme string
|
2017-09-26 16:39:13 +00:00
|
|
|
:param openlp.core.common.path.Path theme_path: The path to the theme
|
|
|
|
:rtype: None
|
2009-09-04 22:27:53 +00:00
|
|
|
"""
|
2017-09-26 16:39:13 +00:00
|
|
|
if theme_path:
|
|
|
|
jsn = json.loads(theme, cls=OpenLPJsonDecoder, base_path=theme_path)
|
|
|
|
else:
|
|
|
|
jsn = json.loads(theme, cls=OpenLPJsonDecoder)
|
2017-05-13 07:47:22 +00:00
|
|
|
self.expand_json(jsn)
|
2009-09-04 22:27:53 +00:00
|
|
|
|
2017-09-26 16:39:13 +00:00
|
|
|
def export_theme(self, theme_path=None):
|
2017-05-24 19:31:48 +00:00
|
|
|
"""
|
|
|
|
Loop through the fields and build a dictionary of them
|
|
|
|
|
|
|
|
"""
|
|
|
|
theme_data = {}
|
|
|
|
for attr, value in self.__dict__.items():
|
|
|
|
theme_data["{attr}".format(attr=attr)] = value
|
2017-09-26 16:39:13 +00:00
|
|
|
if theme_path:
|
|
|
|
return json.dumps(theme_data, cls=OpenLPJsonEncoder, base_path=theme_path)
|
|
|
|
return json.dumps(theme_data, cls=OpenLPJsonEncoder)
|
2017-05-24 19:31:48 +00:00
|
|
|
|
2009-04-07 19:45:21 +00:00
|
|
|
def parse(self, xml):
|
2009-07-08 06:55:08 +00:00
|
|
|
"""
|
|
|
|
Read in an XML string and parse it.
|
|
|
|
|
2014-03-17 19:05:55 +00:00
|
|
|
:param xml: The XML string to parse.
|
2009-07-08 06:55:08 +00:00
|
|
|
"""
|
2013-08-31 18:17:38 +00:00
|
|
|
self.parse_xml(str(xml))
|
2009-04-15 04:58:51 +00:00
|
|
|
|
|
|
|
def parse_xml(self, xml):
|
2009-07-08 06:55:08 +00:00
|
|
|
"""
|
|
|
|
Parse an XML string.
|
|
|
|
|
2014-03-17 19:05:55 +00:00
|
|
|
:param xml: The XML string to parse.
|
2009-07-08 06:55:08 +00:00
|
|
|
"""
|
2010-10-05 19:52:28 +00:00
|
|
|
# remove encoding string
|
2013-08-31 18:17:38 +00:00
|
|
|
line = xml.find('?>')
|
2010-10-09 10:59:49 +00:00
|
|
|
if line:
|
|
|
|
xml = xml[line + 2:]
|
2010-10-05 19:52:28 +00:00
|
|
|
try:
|
2010-11-24 01:51:08 +00:00
|
|
|
theme_xml = objectify.fromstring(xml)
|
2010-10-05 19:52:28 +00:00
|
|
|
except etree.XMLSyntaxError:
|
2016-05-15 17:33:42 +00:00
|
|
|
log.exception('Invalid xml {text}'.format(text=xml))
|
2010-10-09 10:59:49 +00:00
|
|
|
return
|
2010-05-29 19:50:50 +00:00
|
|
|
xml_iter = theme_xml.getiterator()
|
|
|
|
for element in xml_iter:
|
2013-08-31 18:17:38 +00:00
|
|
|
master = ''
|
|
|
|
if element.tag == 'background':
|
2012-01-04 17:19:49 +00:00
|
|
|
if element.attrib:
|
|
|
|
for attr in element.attrib:
|
2012-12-28 22:06:43 +00:00
|
|
|
self._create_attr(element.tag, attr, element.attrib[attr])
|
2012-01-04 17:19:49 +00:00
|
|
|
parent = element.getparent()
|
2010-10-05 19:52:28 +00:00
|
|
|
if parent is not None:
|
2013-08-31 18:17:38 +00:00
|
|
|
if parent.tag == 'font':
|
|
|
|
master = parent.tag + '_' + parent.attrib['type']
|
2010-10-11 16:14:36 +00:00
|
|
|
# set up Outline and Shadow Tags and move to font_main
|
2013-08-31 18:17:38 +00:00
|
|
|
if parent.tag == 'display':
|
|
|
|
if element.tag.startswith('shadow') or element.tag.startswith('outline'):
|
|
|
|
self._create_attr('font_main', element.tag, element.text)
|
2012-01-04 17:19:49 +00:00
|
|
|
master = parent.tag
|
2013-08-31 18:17:38 +00:00
|
|
|
if parent.tag == 'background':
|
2012-01-04 17:19:49 +00:00
|
|
|
master = parent.tag
|
2010-10-05 19:52:28 +00:00
|
|
|
if master:
|
2010-10-16 07:21:24 +00:00
|
|
|
self._create_attr(master, element.tag, element.text)
|
2010-10-05 19:52:28 +00:00
|
|
|
if element.attrib:
|
|
|
|
for attr in element.attrib:
|
2010-10-07 05:15:02 +00:00
|
|
|
base_element = attr
|
|
|
|
# correction for the shadow and outline tags
|
2013-08-31 18:17:38 +00:00
|
|
|
if element.tag == 'shadow' or element.tag == 'outline':
|
2010-10-07 05:15:02 +00:00
|
|
|
if not attr.startswith(element.tag):
|
2013-08-31 18:17:38 +00:00
|
|
|
base_element = element.tag + '_' + attr
|
2012-12-28 22:06:43 +00:00
|
|
|
self._create_attr(master, base_element, element.attrib[attr])
|
2009-04-06 18:45:45 +00:00
|
|
|
else:
|
2013-08-31 18:17:38 +00:00
|
|
|
if element.tag == 'name':
|
|
|
|
self._create_attr('theme', element.tag, element.text)
|
2010-09-11 06:59:36 +00:00
|
|
|
|
2017-05-13 07:47:22 +00:00
|
|
|
@staticmethod
|
|
|
|
def _translate_tags(master, element, value):
|
2010-10-11 16:14:36 +00:00
|
|
|
"""
|
|
|
|
Clean up XML removing and redefining tags
|
|
|
|
"""
|
|
|
|
master = master.strip().lstrip()
|
|
|
|
element = element.strip().lstrip()
|
2013-08-31 18:17:38 +00:00
|
|
|
value = str(value).strip().lstrip()
|
|
|
|
if master == 'display':
|
|
|
|
if element == 'wrapStyle':
|
2010-10-11 16:14:36 +00:00
|
|
|
return True, None, None, None
|
2013-08-31 18:17:38 +00:00
|
|
|
if element.startswith('shadow') or element.startswith('outline'):
|
|
|
|
master = 'font_main'
|
2010-10-11 16:14:36 +00:00
|
|
|
# fix bold font
|
2016-07-23 21:41:24 +00:00
|
|
|
ret_value = None
|
2013-08-31 18:17:38 +00:00
|
|
|
if element == 'weight':
|
|
|
|
element = 'bold'
|
|
|
|
if value == 'Normal':
|
2016-07-01 21:17:20 +00:00
|
|
|
ret_value = False
|
2010-10-11 16:14:36 +00:00
|
|
|
else:
|
2016-07-01 21:17:20 +00:00
|
|
|
ret_value = True
|
2013-08-31 18:17:38 +00:00
|
|
|
if element == 'proportion':
|
|
|
|
element = 'size'
|
2016-07-23 21:41:24 +00:00
|
|
|
return False, master, element, ret_value if ret_value is not None else value
|
2010-10-11 16:14:36 +00:00
|
|
|
|
2012-04-03 17:58:42 +00:00
|
|
|
def _create_attr(self, master, element, value):
|
2010-09-11 11:11:19 +00:00
|
|
|
"""
|
|
|
|
Create the attributes with the correct data types and name format
|
|
|
|
"""
|
2012-12-28 22:06:43 +00:00
|
|
|
reject, master, element, value = self._translate_tags(master, element, value)
|
2010-10-11 16:14:36 +00:00
|
|
|
if reject:
|
|
|
|
return
|
2013-12-13 19:44:17 +00:00
|
|
|
field = de_hump(element)
|
2013-08-31 18:17:38 +00:00
|
|
|
tag = master + '_' + field
|
2011-02-02 15:52:17 +00:00
|
|
|
if field in BOOLEAN_LIST:
|
2010-10-05 19:52:28 +00:00
|
|
|
setattr(self, tag, str_to_bool(value))
|
2011-02-02 15:52:17 +00:00
|
|
|
elif field in INTEGER_LIST:
|
2010-10-05 19:52:28 +00:00
|
|
|
setattr(self, tag, int(value))
|
2010-09-11 06:59:36 +00:00
|
|
|
else:
|
2010-10-05 19:52:28 +00:00
|
|
|
# make string value unicode
|
2013-08-31 18:17:38 +00:00
|
|
|
if not isinstance(value, str):
|
|
|
|
value = str(str(value), 'utf-8')
|
2010-10-29 06:44:38 +00:00
|
|
|
# None means an empty string so lets have one.
|
2013-08-31 18:17:38 +00:00
|
|
|
if value == 'None':
|
|
|
|
value = ''
|
|
|
|
setattr(self, tag, str(value).strip().lstrip())
|
2009-04-06 18:45:45 +00:00
|
|
|
|
2011-01-08 13:57:03 +00:00
|
|
|
def __str__(self):
|
|
|
|
"""
|
|
|
|
Return a string representation of this object.
|
|
|
|
"""
|
|
|
|
theme_strings = []
|
|
|
|
for key in dir(self):
|
2013-08-31 18:17:38 +00:00
|
|
|
if key[0:1] != '_':
|
2016-06-04 00:15:19 +00:00
|
|
|
theme_strings.append('{key:>30}: {value}'.format(key=key, value=getattr(self, key)))
|
2013-08-31 18:17:38 +00:00
|
|
|
return '\n'.join(theme_strings)
|