openlp/openlp/core/lib/__init__.py

627 lines
25 KiB
Python
Raw Normal View History

2010-09-10 19:47:33 +00:00
# -*- coding: utf-8 -*-
2012-12-28 22:06:43 +00:00
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
2012-12-27 16:27:59 +00:00
2010-09-10 19:47:33 +00:00
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
2016-12-31 11:01:36 +00:00
# Copyright (c) 2008-2017 OpenLP Developers #
2010-09-10 19:47:33 +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 #
###############################################################################
"""
The :mod:`lib` module contains most of the components and libraries that make
OpenLP work.
"""
2017-05-17 20:06:45 +00:00
import html
2010-09-10 19:47:33 +00:00
import logging
2012-04-12 14:16:12 +00:00
import os
2016-01-03 17:01:44 +00:00
import re
2016-07-25 20:07:07 +00:00
import math
2010-09-10 19:47:33 +00:00
2015-11-07 00:49:40 +00:00
from PyQt5 import QtCore, QtGui, Qt, QtWidgets
2013-10-13 20:36:42 +00:00
from openlp.core.common import translate
from openlp.core.common.path import Path
2013-10-13 20:36:42 +00:00
2014-04-12 20:19:22 +00:00
log = logging.getLogger(__name__ + '.__init__')
2010-09-10 19:47:33 +00:00
2017-05-17 20:06:45 +00:00
SLIMCHARS = 'fiíIÍjlĺľrtť.,;/ ()|"\'!:\\'
2013-03-23 07:07:06 +00:00
class ServiceItemContext(object):
"""
The context in which a Service Item is being generated
"""
Preview = 0
Live = 1
Service = 2
2012-07-01 18:41:59 +00:00
class ImageSource(object):
"""
2013-03-07 12:30:24 +00:00
This enumeration class represents different image sources. An image sources states where an image is used. This
enumeration class is need in the context of the :class:~openlp.core.lib.imagemanager`.
2012-07-01 19:41:12 +00:00
``ImagePlugin``
This states that an image is being used by the image plugin.
``Theme``
This says, that the image is used by a theme.
``CommandPlugins``
This states that an image is being used by a command plugin.
2012-07-01 18:41:59 +00:00
"""
ImagePlugin = 1
Theme = 2
CommandPlugins = 3
2012-07-01 18:41:59 +00:00
class MediaType(object):
"""
An enumeration class for types of media.
"""
Audio = 1
Video = 2
class ServiceItemAction(object):
"""
2013-03-07 12:30:24 +00:00
Provides an enumeration for the required action moving between service items by left/right arrow keys
"""
Previous = 1
PreviousLastSlide = 2
Next = 3
2012-04-22 19:50:18 +00:00
def get_text_file_string(text_file_path):
2010-09-10 19:47:33 +00:00
"""
Open a file and return its content as a string. If the supplied file path is not a file then the function
2013-03-07 12:30:24 +00:00
returns False. If there is an error loading the file or the content can't be decoded then the function will return
None.
2010-09-10 19:47:33 +00:00
:param openlp.core.common.path.Path text_file_path: The path to the file.
:return: The contents of the file, False if the file does not exist, or None if there is an Error reading or
decoding the file.
:rtype: str | False | None
2010-09-10 19:47:33 +00:00
"""
if not text_file_path.is_file():
2010-09-10 19:47:33 +00:00
return False
2013-03-27 09:25:39 +00:00
content = None
2010-09-10 19:47:33 +00:00
try:
with text_file_path.open('r', encoding='utf-8') as file_handle:
if file_handle.read(3) != '\xEF\xBB\xBF':
# no BOM was found
file_handle.seek(0)
content = file_handle.read()
2010-09-10 19:47:33 +00:00
except (IOError, UnicodeError):
log.exception('Failed to open text file {text}'.format(text=text_file_path))
2013-03-27 09:25:39 +00:00
return content
2010-09-10 19:47:33 +00:00
2012-04-22 19:50:18 +00:00
2013-03-07 12:30:24 +00:00
def str_to_bool(string_value):
2010-09-10 19:47:33 +00:00
"""
Convert a string version of a boolean into a real boolean.
2014-01-01 09:33:07 +00:00
:param string_value: The string value to examine and convert to a boolean type.
2015-09-08 19:13:59 +00:00
:return: The correct boolean value
2010-09-10 19:47:33 +00:00
"""
2013-03-07 12:30:24 +00:00
if isinstance(string_value, bool):
return string_value
2013-08-31 18:17:38 +00:00
return str(string_value).strip().lower() in ('true', 'yes', 'y')
2010-09-10 19:47:33 +00:00
2012-04-22 19:50:18 +00:00
2010-09-10 19:47:33 +00:00
def build_icon(icon):
"""
2013-03-07 12:30:24 +00:00
Build a QIcon instance from an existing QIcon, a resource location, or a physical file location. If the icon is a
QIcon instance, that icon is simply returned. If not, it builds a QIcon instance from the resource or file name.
2010-09-10 19:47:33 +00:00
:param QtGui.QIcon | Path | QtGui.QIcon | str icon:
The icon to build. This can be a QIcon, a resource string in the form ``:/resource/file.png``, or a file path
location like ``Path(/path/to/file.png)``. However, the **recommended** way is to specify a resource string.
2015-09-08 19:13:59 +00:00
:return: The build icon.
:rtype: QtGui.QIcon
2010-09-10 19:47:33 +00:00
"""
if isinstance(icon, QtGui.QIcon):
2016-10-27 17:45:50 +00:00
return icon
2016-10-30 08:29:22 +00:00
pix_map = None
2016-10-27 17:45:50 +00:00
button_icon = QtGui.QIcon()
if isinstance(icon, str):
2016-10-30 08:29:22 +00:00
pix_map = QtGui.QPixmap(icon)
elif isinstance(icon, Path):
pix_map = QtGui.QPixmap(str(icon))
2010-09-10 19:47:33 +00:00
elif isinstance(icon, QtGui.QImage):
2016-10-27 17:45:50 +00:00
pix_map = QtGui.QPixmap.fromImage(icon)
2016-10-30 08:29:22 +00:00
if pix_map:
button_icon.addPixmap(pix_map, QtGui.QIcon.Normal, QtGui.QIcon.Off)
2010-09-10 19:47:33 +00:00
return button_icon
2012-04-22 19:50:18 +00:00
def image_to_byte(image, base_64=True):
2010-09-10 19:47:33 +00:00
"""
2013-03-07 12:30:24 +00:00
Resize an image to fit on the current screen for the web and returns it as a byte stream.
2010-09-10 19:47:33 +00:00
2014-01-01 09:33:07 +00:00
:param image: The image to converted.
2014-01-09 19:52:20 +00:00
:param base_64: If True returns the image as Base64 bytes, otherwise the image is returned as a byte array.
To preserve original intention, this defaults to True
2010-09-10 19:47:33 +00:00
"""
2013-08-31 18:17:38 +00:00
log.debug('image_to_byte - start')
2010-09-10 19:47:33 +00:00
byte_array = QtCore.QByteArray()
# use buffer to store pixmap into byteArray
buffie = QtCore.QBuffer(byte_array)
buffie.open(QtCore.QIODevice.WriteOnly)
2010-10-15 15:33:06 +00:00
image.save(buffie, "PNG")
2013-08-31 18:17:38 +00:00
log.debug('image_to_byte - end')
if not base_64:
return byte_array
2010-09-10 19:47:33 +00:00
# convert to base64 encoding so does not get missed!
2013-03-27 09:41:01 +00:00
return bytes(byte_array.toBase64()).decode('utf-8')
2010-09-10 19:47:33 +00:00
2012-04-22 19:50:18 +00:00
2011-06-12 15:59:46 +00:00
def create_thumb(image_path, thumb_path, return_icon=True, size=None):
2011-06-12 15:17:01 +00:00
"""
2013-03-07 12:30:24 +00:00
Create a thumbnail from the given image path and depending on ``return_icon`` it returns an icon from this thumb.
2011-06-12 15:17:01 +00:00
2014-01-01 09:33:07 +00:00
:param image_path: The image file to create the icon from.
:param thumb_path: The filename to save the thumbnail to.
:param return_icon: States if an icon should be build and returned from the thumb. Defaults to ``True``.
:param size: Allows to state a own size (QtCore.QSize) to use. Defaults to ``None``, which means that a default
height of 88 is used.
2015-09-08 19:13:59 +00:00
:return: The final icon.
2011-06-12 15:17:01 +00:00
"""
ext = os.path.splitext(thumb_path)[1].lower()
reader = QtGui.QImageReader(image_path)
2011-06-12 15:59:46 +00:00
if size is None:
# No size given; use default height of 88
if reader.size().isEmpty():
ratio = 1
else:
ratio = reader.size().width() / reader.size().height()
2011-06-12 15:59:46 +00:00
reader.setScaledSize(QtCore.QSize(int(ratio * 88), 88))
elif size.isValid():
# Complete size given
2011-06-12 15:59:46 +00:00
reader.setScaledSize(size)
else:
# Invalid size given
if reader.size().isEmpty():
ratio = 1
else:
ratio = reader.size().width() / reader.size().height()
if size.width() >= 0:
# Valid width; scale height
reader.setScaledSize(QtCore.QSize(size.width(), int(size.width() / ratio)))
elif size.height() >= 0:
# Valid height; scale width
reader.setScaledSize(QtCore.QSize(int(ratio * size.height()), size.height()))
else:
# Invalid; use default height of 88
reader.setScaledSize(QtCore.QSize(int(ratio * 88), 88))
2011-06-12 15:17:01 +00:00
thumb = reader.read()
thumb.save(thumb_path, ext[1:])
if not return_icon:
return
if os.path.exists(thumb_path):
2013-10-28 21:23:17 +00:00
return build_icon(thumb_path)
2011-06-12 15:17:01 +00:00
# Fallback for files with animation support.
2013-10-28 21:23:17 +00:00
return build_icon(image_path)
2011-06-12 15:17:01 +00:00
2012-04-22 19:50:18 +00:00
2011-06-12 15:59:46 +00:00
def validate_thumb(file_path, thumb_path):
2011-06-12 15:17:01 +00:00
"""
2013-03-07 12:30:24 +00:00
Validates whether an file's thumb still exists and if is up to date. **Note**, you must **not** call this function,
before checking the existence of the file.
2011-06-12 15:17:01 +00:00
2017-09-17 19:43:15 +00:00
:param openlp.core.common.path.Path file_path: The path to the file. The file **must** exist!
:param openlp.core.common.path.Path thumb_path: The path to the thumb.
:return: Has the image changed since the thumb was created?
:rtype: bool
2011-06-12 15:17:01 +00:00
"""
if not thumb_path.exists():
2011-06-12 15:17:01 +00:00
return False
image_date = file_path.stat().st_mtime
thumb_date = thumb_path.stat().st_mtime
2011-06-12 15:17:01 +00:00
return image_date <= thumb_date
2012-04-22 19:50:18 +00:00
def resize_image(image_path, width, height, background='#000000', ignore_aspect_ratio=False):
2010-09-10 19:47:33 +00:00
"""
Resize an image to fit on the current screen.
DO NOT REMOVE THE DEFAULT BACKGROUND VALUE!
2014-01-01 09:33:07 +00:00
:param image_path: The path to the image to resize.
:param width: The new image width.
:param height: The new image height.
:param background: The background colour. Defaults to black.
2010-09-10 19:47:33 +00:00
"""
2013-08-31 18:17:38 +00:00
log.debug('resize_image - start')
2011-06-09 10:41:02 +00:00
reader = QtGui.QImageReader(image_path)
2011-06-07 07:30:50 +00:00
# The image's ratio.
2013-04-24 19:05:34 +00:00
image_ratio = reader.size().width() / reader.size().height()
resize_ratio = width / height
2011-06-07 07:30:50 +00:00
# Figure out the size we want to resize the image to (keep aspect ratio).
if image_ratio == resize_ratio or ignore_aspect_ratio:
2011-06-07 07:30:50 +00:00
size = QtCore.QSize(width, height)
elif image_ratio < resize_ratio:
# Use the image's height as reference for the new size.
size = QtCore.QSize(image_ratio * height, height)
2010-10-15 15:33:06 +00:00
else:
2011-06-07 07:30:50 +00:00
# Use the image's width as reference for the new size.
size = QtCore.QSize(width, 1 / (image_ratio / width))
reader.setScaledSize(size)
preview = reader.read()
if image_ratio == resize_ratio:
# We neither need to centre the image nor add "bars" to the image.
return preview
2012-07-01 18:45:14 +00:00
real_width = preview.width()
real_height = preview.height()
2010-09-10 19:47:33 +00:00
# and move it to the centre of the preview space
2012-12-28 22:06:43 +00:00
new_image = QtGui.QImage(width, height, QtGui.QImage.Format_ARGB32_Premultiplied)
2010-09-10 19:47:33 +00:00
painter = QtGui.QPainter(new_image)
painter.fillRect(new_image.rect(), QtGui.QColor(background))
2013-04-24 19:05:34 +00:00
painter.drawImage((width - real_width) // 2, (height - real_height) // 2, preview)
2010-09-10 19:47:33 +00:00
return new_image
2012-04-22 19:50:18 +00:00
2010-09-10 19:47:33 +00:00
def check_item_selected(list_widget, message):
"""
Check if a list item is selected so an action may be performed on it
2014-01-01 09:33:07 +00:00
:param list_widget: The list to check for selected items
:param message: The message to give the user if no item is selected
2010-09-10 19:47:33 +00:00
"""
if not list_widget.selectedIndexes():
2015-11-07 00:49:40 +00:00
QtWidgets.QMessageBox.information(list_widget.parent(),
translate('OpenLP.MediaManagerItem', 'No Items Selected'), message)
2010-09-10 19:47:33 +00:00
return False
return True
2012-04-22 19:50:18 +00:00
def clean_tags(text, remove_chords=False):
2010-09-10 19:47:33 +00:00
"""
Remove Tags from text for display
2014-01-01 09:33:07 +00:00
:param text: Text to be cleaned
:param remove_chords: Clean ChordPro tags
2010-09-10 19:47:33 +00:00
"""
2013-08-31 18:17:38 +00:00
text = text.replace('<br>', '\n')
text = text.replace('{br}', '\n')
2017-01-29 21:25:52 +00:00
text = text.replace('&nbsp;', ' ')
for tag in FormattingTags.get_html_tags():
2013-08-31 18:17:38 +00:00
text = text.replace(tag['start tag'], '')
text = text.replace(tag['end tag'], '')
2016-01-03 17:01:44 +00:00
# Remove ChordPro tags
if remove_chords:
2016-01-03 17:01:44 +00:00
text = re.sub(r'\[.+?\]', r'', text)
2010-09-10 19:47:33 +00:00
return text
2012-04-22 19:50:18 +00:00
def expand_tags(text, expand_chord_tags=False, for_printing=False):
2010-09-10 19:47:33 +00:00
"""
Expand tags HTML for display
2014-01-01 09:33:07 +00:00
:param text: The text to be expanded.
2010-09-10 19:47:33 +00:00
"""
if expand_chord_tags:
if for_printing:
text = expand_chords_for_printing(text, '{br}')
else:
text = expand_chords(text)
for tag in FormattingTags.get_html_tags():
2013-08-31 18:17:38 +00:00
text = text.replace(tag['start tag'], tag['start html'])
text = text.replace(tag['end tag'], tag['end html'])
2010-09-10 19:47:33 +00:00
return text
2013-10-13 20:36:42 +00:00
def expand_and_align_chords_in_line(match):
"""
Expand the chords in the line and align them using whitespaces.
NOTE: There is equivalent javascript code in chords.js, in the updateSlide function. Make sure to update both!
:param match:
:return: The line with expanded html-chords
"""
whitespaces = ''
chordlen = 0
taillen = 0
# The match could be "[G]sweet the " from a line like "A[D]mazing [D7]grace! How [G]sweet the [D]sound!"
# The actual chord, would be "G" in match "[G]sweet the "
chord = match.group(1)
# The tailing word of the chord, would be "sweet" in match "[G]sweet the "
tail = match.group(2)
# The remainder of the line, until line end or next chord. Would be " the " in match "[G]sweet the "
remainder = match.group(3)
# Line end if found, else None
end = match.group(4)
# Based on char width calculate width of chord
for chord_char in chord:
2017-03-17 21:12:29 +00:00
if chord_char not in SLIMCHARS:
chordlen += 2
else:
chordlen += 1
# Based on char width calculate width of tail
for tail_char in tail:
2017-03-17 21:12:29 +00:00
if tail_char not in SLIMCHARS:
taillen += 2
else:
taillen += 1
# Based on char width calculate width of remainder
for remainder_char in remainder:
2017-03-17 21:12:29 +00:00
if remainder_char not in SLIMCHARS:
taillen += 2
else:
taillen += 1
# If the chord is wider than the tail+remainder and the line goes on, some padding is needed
if chordlen >= taillen and end is None:
# Decide if the padding should be "_" for drawing out words or spaces
if tail:
if not remainder:
2017-03-17 21:12:29 +00:00
for c in range(math.ceil((chordlen - taillen) / 2) + 2):
whitespaces += '_'
else:
2017-03-17 21:12:29 +00:00
for c in range(chordlen - taillen + 1):
whitespaces += '&nbsp;'
else:
if not remainder:
for c in range(math.floor((chordlen - taillen) / 2)):
whitespaces += '_'
else:
for c in range(chordlen - taillen + 1):
whitespaces += '&nbsp;'
else:
if not tail and remainder and remainder[0] == ' ':
for c in range(chordlen):
whitespaces += '&nbsp;'
if whitespaces:
2017-02-20 21:22:17 +00:00
if '_' in whitespaces:
ws_length = len(whitespaces)
if ws_length == 1:
2017-02-23 21:40:22 +00:00
whitespaces = '&ndash;'
2017-02-20 21:22:17 +00:00
else:
wsl_mod = ws_length // 2
ws_right = ws_left = ' ' * wsl_mod
2017-02-23 21:40:22 +00:00
whitespaces = ws_left + '&ndash;' + ws_right
whitespaces = '<span class="ws">' + whitespaces + '</span>'
2017-05-17 20:06:45 +00:00
return '<span class="chord"><span><strong>' + html.escape(chord) + '</strong></span></span>' + html.escape(tail) + \
whitespaces + html.escape(remainder)
def expand_chords(text):
"""
Expand ChordPro tags
:param text:
"""
text_lines = text.split('{br}')
expanded_text_lines = []
chords_on_prev_line = False
for line in text_lines:
# If a ChordPro is detected in the line, replace it with a html-span tag and wrap the line in a span tag.
if '[' in line and ']' in line:
if chords_on_prev_line:
new_line = '<span class="chordline">'
else:
new_line = '<span class="chordline firstchordline">'
chords_on_prev_line = True
# Matches a chord, a tail, a remainder and a line end. See expand_and_align_chords_in_line() for more info.
new_line += re.sub(r'\[(.*?)\]([\u0080-\uFFFF,\w]*)'
'([\u0080-\uFFFF,\w,\s,\.,\,,\!,\?,\;,\:,\|,\",\',\-,\_]*)(\Z)?',
expand_and_align_chords_in_line, line)
new_line += '</span>'
expanded_text_lines.append(new_line)
else:
chords_on_prev_line = False
2017-05-17 20:06:45 +00:00
expanded_text_lines.append(html.escape(line))
return '{br}'.join(expanded_text_lines)
2017-01-28 22:04:16 +00:00
def compare_chord_lyric(chord, lyric):
2016-07-25 20:07:07 +00:00
"""
2017-02-20 21:22:17 +00:00
Compare the width of chord and lyrics. If chord width is greater than the lyric width the diff is returned.
2016-07-26 19:02:35 +00:00
2017-01-28 22:04:16 +00:00
:param chord:
:param lyric:
:return:
2016-07-25 20:07:07 +00:00
"""
chordlen = 0
2017-01-28 22:04:16 +00:00
if chord == '&nbsp;':
return 0
chord = re.sub(r'\{.*?\}', r'', chord)
lyric = re.sub(r'\{.*?\}', r'', lyric)
2016-07-25 20:07:07 +00:00
for chord_char in chord:
2017-01-28 22:04:16 +00:00
if chord_char not in SLIMCHARS:
2016-07-25 20:07:07 +00:00
chordlen += 2
else:
chordlen += 1
2017-01-28 22:04:16 +00:00
lyriclen = 0
for lyric_char in lyric:
if lyric_char not in SLIMCHARS:
lyriclen += 2
2016-07-25 20:07:07 +00:00
else:
2017-01-28 22:04:16 +00:00
lyriclen += 1
if chordlen > lyriclen:
return chordlen - lyriclen
2016-07-25 20:07:07 +00:00
else:
2017-01-28 22:04:16 +00:00
return 0
2016-07-25 20:07:07 +00:00
def find_formatting_tags(text, active_formatting_tags):
"""
Look for formatting tags in lyrics and adds/removes them to/from the given list. Returns the update list.
:param text:
:param active_formatting_tags:
:return:
"""
if not re.search(r'\{.*?\}', text):
return active_formatting_tags
word_it = iter(text)
# Loop through lyrics to find any formatting tags
for char in word_it:
if char == '{':
tag = ''
char = next(word_it)
start_tag = True
if char == '/':
start_tag = False
2017-04-11 12:44:28 +00:00
char = next(word_it)
while char != '}':
tag += char
char = next(word_it)
# See if the found tag has an end tag
for formatting_tag in FormattingTags.get_html_tags():
if formatting_tag['start tag'] == '{' + tag + '}':
if formatting_tag['end tag']:
if start_tag:
# prepend the new tag to the list of active formatting tags
2017-04-11 12:44:28 +00:00
active_formatting_tags[:0] = [tag]
else:
# remove the tag from the list
active_formatting_tags.remove(tag)
2017-04-11 12:44:28 +00:00
# Break out of the loop matching the found tag against the tag list.
break
return active_formatting_tags
def expand_chords_for_printing(text, line_split):
2016-01-03 17:01:44 +00:00
"""
Expand ChordPro tags
:param text:
:param line_split:
2016-01-03 17:01:44 +00:00
"""
if not re.search(r'\[.*?\]', text):
return text
text_lines = text.split(line_split)
2016-01-03 17:01:44 +00:00
expanded_text_lines = []
for line in text_lines:
# If a ChordPro is detected in the line, build html tables.
new_line = '<table class="line" width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td>'
active_formatting_tags = []
if re.search(r'\[.*?\]', line):
words = line.split(' ')
in_chord = False
for word in words:
2017-01-28 22:04:16 +00:00
chords = []
lyrics = []
new_line += '<table class="segment" cellpadding="0" cellspacing="0" border="0" align="left">'
# If the word contains a chord, we need to handle it.
if re.search(r'\[.*?\]', word):
chord = ''
lyric = ''
# Loop over each character of the word
for char in word:
if char == '[':
in_chord = True
if lyric != '':
2017-01-28 22:04:16 +00:00
if chord == '':
chord = '&nbsp;'
chords.append(chord)
lyrics.append(lyric)
2017-01-28 22:04:16 +00:00
chord = ''
lyric = ''
elif char == ']' and in_chord:
in_chord = False
elif in_chord:
2017-01-28 22:04:16 +00:00
chord += char
else:
lyric += char
2017-01-28 22:04:16 +00:00
if lyric != '' or chord != '':
if chord == '':
chord = '&nbsp;'
if lyric == '':
lyric = '&nbsp;'
2017-01-28 22:04:16 +00:00
chords.append(chord)
lyrics.append(lyric)
2017-02-02 21:55:05 +00:00
new_chord_line = '<tr class="chordrow">'
2017-01-28 22:04:16 +00:00
new_lyric_line = '</tr><tr>'
2017-01-26 21:14:19 +00:00
for i in range(len(lyrics)):
2017-01-28 22:04:16 +00:00
spacer = compare_chord_lyric(chords[i], lyrics[i])
# Handle formatting tags
start_formatting_tags = ''
if active_formatting_tags:
start_formatting_tags = '{' + '}{'.join(active_formatting_tags) + '}'
# Update list of active formatting tags
active_formatting_tags = find_formatting_tags(lyrics[i], active_formatting_tags)
end_formatting_tags = ''
if active_formatting_tags:
end_formatting_tags = '{/' + '}{/'.join(active_formatting_tags) + '}'
new_chord_line += '<td class="chord">%s</td>' % chords[i]
2017-04-18 19:20:36 +00:00
# Check if this is the last column, if so skip spacing calc and instead insert a single space
2017-01-26 21:14:19 +00:00
if i + 1 == len(lyrics):
new_lyric_line += '<td class="lyrics">{starttags}{lyrics}&nbsp;{endtags}</td>'.format(
starttags=start_formatting_tags, lyrics=lyrics[i], endtags=end_formatting_tags)
2017-01-28 22:04:16 +00:00
else:
spacing = ''
2017-01-28 22:04:16 +00:00
if spacer > 0:
space = '&nbsp;' * int(math.ceil(spacer / 2))
spacing = '<span class="chordspacing">%s-%s</span>' % (space, space)
new_lyric_line += '<td class="lyrics">{starttags}{lyrics}{spacing}{endtags}</td>'.format(
starttags=start_formatting_tags, lyrics=lyrics[i], spacing=spacing,
endtags=end_formatting_tags)
2017-01-28 22:04:16 +00:00
new_line += new_chord_line + new_lyric_line + '</tr>'
else:
start_formatting_tags = ''
if active_formatting_tags:
start_formatting_tags = '{' + '}{'.join(active_formatting_tags) + '}'
active_formatting_tags = find_formatting_tags(word, active_formatting_tags)
end_formatting_tags = ''
if active_formatting_tags:
end_formatting_tags = '{/' + '}{/'.join(active_formatting_tags) + '}'
new_line += '<tr class="chordrow"><td class="chord">&nbsp;</td></tr><tr><td class="lyrics">' \
'{starttags}{lyrics}&nbsp;{endtags}</td></tr>'.format(
starttags=start_formatting_tags, lyrics=word, endtags=end_formatting_tags)
new_line += '</table>'
2016-01-03 17:01:44 +00:00
else:
new_line += line
new_line += '</td></tr></table>'
expanded_text_lines.append(new_line)
# the {br} tag used to split lines is not inserted again since the style of the line-tables makes them redundant.
2017-01-29 21:25:52 +00:00
return ''.join(expanded_text_lines)
2016-01-03 17:01:44 +00:00
2013-10-13 17:23:52 +00:00
def create_separated_list(string_list):
2012-02-16 20:36:35 +00:00
"""
2016-10-04 00:03:15 +00:00
Returns a string that represents a join of a list of strings with a localized separator.
Localized separation will be done via the translate() function by the translators.
2014-01-01 09:33:07 +00:00
:param string_list: List of unicode strings
:return: Formatted string
2012-02-16 20:36:35 +00:00
"""
list_length = len(string_list)
if list_length == 1:
list_to_string = string_list[0]
elif list_length == 2:
list_to_string = translate('OpenLP.core.lib', '{one} and {two}').format(one=string_list[0], two=string_list[1])
elif list_length > 2:
2016-11-21 20:56:48 +00:00
list_to_string = translate('OpenLP.core.lib', '{first} and {last}').format(first=', '.join(string_list[:-1]),
last=string_list[-1])
2012-02-16 20:36:35 +00:00
else:
list_to_string = ''
return list_to_string
2013-08-31 18:17:38 +00:00
from .exceptions import ValidationError
2013-08-31 18:17:38 +00:00
from .screen import ScreenList
from .formattingtags import FormattingTags
from .plugin import PluginStatus, StringContent, Plugin
from .pluginmanager import PluginManager
from .settingstab import SettingsTab
from .serviceitem import ServiceItem, ServiceItemType, ItemCapabilities
from .htmlbuilder import build_html, build_lyrics_format_css, build_lyrics_outline_css, build_chords_css
2013-08-31 18:17:38 +00:00
from .imagemanager import ImageManager
from .mediamanageritem import MediaManagerItem
2014-10-06 19:10:03 +00:00
from .projector.db import ProjectorDB, Projector
2017-08-06 07:23:26 +00:00
from .projector.pjlink import PJLink
2014-10-06 19:10:03 +00:00
from .projector.constants import PJLINK_PORT, ERROR_MSG, ERROR_STRING