- Merged trunk on 27.6.16

This commit is contained in:
suutari-olli 2016-06-27 02:23:50 +03:00
commit 22ff4247ad
21 changed files with 587 additions and 418 deletions

View File

@ -390,163 +390,207 @@ is the function which has to be called from outside. The generated and returned
import logging
from PyQt5 import QtWebKit
from string import Template
from openlp.core.common import Settings
from openlp.core.lib.theme import BackgroundType, BackgroundGradientType, VerticalType, HorizontalType
log = logging.getLogger(__name__)
# TODO: Verify where this is used before converting to python3
HTMLSRC = """
<!DOCTYPE html>
<html>
<head>
<title>OpenLP Display</title>
<style>
*{
HTML_SRC = Template("""
<!DOCTYPE html>
<html>
<head>
<title>OpenLP Display</title>
<style>
*{
margin: 0;
padding: 0;
border: 0;
overflow: hidden;
-webkit-user-select: none;
}
body {
${bg_css};
}
.size {
position: absolute;
left: 0px;
top: 0px;
width: 100%;
height: 100%;
}
#black {
z-index: 8;
background-color: black;
display: none;
}
#bgimage {
z-index: 1;
}
#image {
z-index: 2;
}
${css_additions}
#footer {
position: absolute;
z-index: 6;
${footer_css}
}
/* lyric css */${lyrics_css}
sup {
font-size: 0.6em;
vertical-align: top;
position: relative;
top: -0.3em;
}
</style>
<script>
var timer = null;
var transition = ${transitions};
${js_additions}
function show_image(src){
var img = document.getElementById('image');
img.src = src;
if(src == '')
img.style.display = 'none';
else
img.style.display = 'block';
}
function show_blank(state){
var black = 'none';
var lyrics = '';
switch(state){
case 'theme':
lyrics = 'hidden';
break;
case 'black':
black = 'block';
break;
case 'desktop':
break;
}
document.getElementById('black').style.display = black;
document.getElementById('lyricsmain').style.visibility = lyrics;
document.getElementById('image').style.visibility = lyrics;
document.getElementById('footer').style.visibility = lyrics;
}
function show_footer(footertext){
document.getElementById('footer').innerHTML = footertext;
}
function show_text(new_text){
var match = /-webkit-text-fill-color:[^;\"]+/gi;
if(timer != null)
clearTimeout(timer);
/*
QtWebkit bug with outlines and justify causing outline alignment
problems. (Bug 859950) Surround each word with a <span> to workaround,
but only in this scenario.
*/
var txt = document.getElementById('lyricsmain');
if(window.getComputedStyle(txt).textAlign == 'justify'){
if(window.getComputedStyle(txt).webkitTextStrokeWidth != '0px'){
new_text = new_text.replace(/(\s|&nbsp;)+(?![^<]*>)/g,
function(match) {
return '</span>' + match + '<span>';
});
new_text = '<span>' + new_text + '</span>';
}
}
text_fade('lyricsmain', new_text);
}
function text_fade(id, new_text){
/*
Show the text.
*/
var text = document.getElementById(id);
if(text == null) return;
if(!transition){
text.innerHTML = new_text;
return;
}
// Fade text out. 0.1 to minimize the time "nothing" is shown on the screen.
text.style.opacity = '0.1';
// Fade new text in after the old text has finished fading out.
timer = window.setTimeout(function(){_show_text(text, new_text)}, 400);
}
function _show_text(text, new_text) {
/*
Helper function to show the new_text delayed.
*/
text.innerHTML = new_text;
text.style.opacity = '1';
// Wait until the text is completely visible. We want to save the timer id, to be able to call
// clearTimeout(timer) when the text has changed before finishing fading.
timer = window.setTimeout(function(){timer = null;}, 400);
}
function show_text_completed(){
return (timer == null);
}
</script>
</head>
<body>
<img id="bgimage" class="size" ${bg_image} />
<img id="image" class="size" ${image} />
${html_additions}
<div class="lyricstable"><div id="lyricsmain" style="opacity:1" class="lyricscell lyricsmain"></div></div>
<div id="footer" class="footer"></div>
<div id="black" class="size"></div>
</body>
</html>
""")
LYRICS_SRC = Template("""
.lyricstable {
z-index: 5;
position: absolute;
display: table;
${stable}
}
.lyricscell {
display: table-cell;
word-wrap: break-word;
-webkit-transition: opacity 0.4s ease;
${lyrics}
}
.lyricsmain {
${main}
}
""")
FOOTER_SRC = Template("""
left: ${left}px;
bottom: ${bottom}px;
width: ${width}px;
font-family: ${family};
font-size: ${size}pt;
color: ${color};
text-align: left;
white-space: ${space};
""")
LYRICS_FORMAT_SRC = Template("""
${justify}word-wrap: break-word;
text-align: ${align};
vertical-align: ${valign};
font-family: ${font};
font-size: ${size}pt;
color: ${color};
line-height: ${line}%;
margin: 0;
padding: 0;
border: 0;
overflow: hidden;
-webkit-user-select: none;
}
body {
%s;
}
.size {
position: absolute;
left: 0px;
top: 0px;
width: 100%%;
height: 100%%;
}
#black {
z-index: 8;
background-color: black;
display: none;
}
#bgimage {
z-index: 1;
}
#image {
z-index: 2;
}
%s
#footer {
position: absolute;
z-index: 6;
%s
}
/* lyric css */
%s
sup {
font-size: 0.6em;
vertical-align: top;
position: relative;
top: -0.3em;
}
</style>
<script>
var timer = null;
var transition = %s;
%s
function show_image(src){
var img = document.getElementById('image');
img.src = src;
if(src == '')
img.style.display = 'none';
else
img.style.display = 'block';
}
function show_blank(state){
var black = 'none';
var lyrics = '';
switch(state){
case 'theme':
lyrics = 'hidden';
break;
case 'black':
black = 'block';
break;
case 'desktop':
break;
}
document.getElementById('black').style.display = black;
document.getElementById('lyricsmain').style.visibility = lyrics;
document.getElementById('image').style.visibility = lyrics;
document.getElementById('footer').style.visibility = lyrics;
}
function show_footer(footertext){
document.getElementById('footer').innerHTML = footertext;
}
function show_text(new_text){
var match = /-webkit-text-fill-color:[^;\"]+/gi;
if(timer != null)
clearTimeout(timer);
/*
QtWebkit bug with outlines and justify causing outline alignment
problems. (Bug 859950) Surround each word with a <span> to workaround,
but only in this scenario.
*/
var txt = document.getElementById('lyricsmain');
if(window.getComputedStyle(txt).textAlign == 'justify'){
if(window.getComputedStyle(txt).webkitTextStrokeWidth != '0px'){
new_text = new_text.replace(/(\s|&nbsp;)+(?![^<]*>)/g,
function(match) {
return '</span>' + match + '<span>';
});
new_text = '<span>' + new_text + '</span>';
}
}
text_fade('lyricsmain', new_text);
}
function text_fade(id, new_text){
/*
Show the text.
*/
var text = document.getElementById(id);
if(text == null) return;
if(!transition){
text.innerHTML = new_text;
return;
}
// Fade text out. 0.1 to minimize the time "nothing" is shown on the screen.
text.style.opacity = '0.1';
// Fade new text in after the old text has finished fading out.
timer = window.setTimeout(function(){_show_text(text, new_text)}, 400);
}
function _show_text(text, new_text) {
/*
Helper function to show the new_text delayed.
*/
text.innerHTML = new_text;
text.style.opacity = '1';
// Wait until the text is completely visible. We want to save the timer id, to be able to call
// clearTimeout(timer) when the text has changed before finishing fading.
timer = window.setTimeout(function(){timer = null;}, 400);
}
function show_text_completed(){
return (timer == null);
}
</script>
</head>
<body>
<img id="bgimage" class="size" %s />
<img id="image" class="size" %s />
%s
<div class="lyricstable"><div id="lyricsmain" style="opacity:1" class="lyricscell lyricsmain"></div></div>
<div id="footer" class="footer"></div>
<div id="black" class="size"></div>
</body>
</html>
"""
padding-bottom: ${bottom};
padding-left: ${left}px;
width: ${width}px;
height: ${height}px;${font_style}${font_weight}
""")
def build_html(item, screen, is_live, background, image=None, plugins=None):
@ -582,18 +626,17 @@ def build_html(item, screen, is_live, background, image=None, plugins=None):
css_additions += plugin.get_display_css()
js_additions += plugin.get_display_javascript()
html_additions += plugin.get_display_html()
html = HTMLSRC % (
build_background_css(item, width),
css_additions,
build_footer_css(item, height),
build_lyrics_css(item),
'true' if theme_data and theme_data.display_slide_transition and is_live else 'false',
js_additions,
bgimage_src,
image_src,
html_additions
)
return html
return HTML_SRC.substitute(bg_css=build_background_css(item, width),
css_additions=css_additions,
footer_css=build_footer_css(item, height),
lyrics_css=build_lyrics_css(item),
transitions='true' if (theme_data and
theme_data.display_slide_transition and
is_live) else 'false',
js_additions=js_additions,
bg_image=bgimage_src,
image=image_src,
html_additions=html_additions)
def webkit_version():
@ -650,24 +693,6 @@ def build_lyrics_css(item):
:param item: Service Item containing theme and location information
"""
# TODO: Verify this before converting to python3
style = """
.lyricstable {
z-index: 5;
position: absolute;
display: table;
%s
}
.lyricscell {
display: table-cell;
word-wrap: break-word;
-webkit-transition: opacity 0.4s ease;
%s
}
.lyricsmain {
%s
}
"""
theme_data = item.theme_data
lyricstable = ''
lyrics = ''
@ -680,8 +705,7 @@ def build_lyrics_css(item):
lyricsmain += ' text-shadow: {theme} {shadow}px ' \
'{shadow}px;'.format(theme=theme_data.font_main_shadow_color,
shadow=theme_data.font_main_shadow_size)
lyrics_css = style % (lyricstable, lyrics, lyricsmain)
return lyrics_css
return LYRICS_SRC.substitute(stable=lyricstable, lyrics=lyrics, main=lyricsmain)
def build_lyrics_outline_css(theme_data):
@ -710,38 +734,23 @@ def build_lyrics_format_css(theme_data, width, height):
"""
align = HorizontalType.Names[theme_data.display_horizontal_align]
valign = VerticalType.Names[theme_data.display_vertical_align]
if theme_data.font_main_outline:
left_margin = int(theme_data.font_main_outline_size) * 2
else:
left_margin = 0
justify = 'white-space:pre-wrap;'
left_margin = (int(theme_data.font_main_outline_size) * 2) if theme_data.font_main_outline else 0
# fix tag incompatibilities
if theme_data.display_horizontal_align == HorizontalType.Justify:
justify = ''
if theme_data.display_vertical_align == VerticalType.Bottom:
padding_bottom = '0.5em'
else:
padding_bottom = '0'
lyrics = '{justify} word-wrap: break-word; ' \
'text-align: {align}; vertical-align: {valign}; font-family: {font}; ' \
'font-size: {size}pt; color: {color}; line-height: {line:d}%; margin: 0;' \
'padding: 0; padding-bottom: {bottom}; padding-left: {left}px; width: {width}px; ' \
'height: {height}px; '.format(justify=justify,
align=align,
valign=valign,
font=theme_data.font_main_name,
size=theme_data.font_main_size,
color=theme_data.font_main_color,
line=100 + int(theme_data.font_main_line_adjustment),
bottom=padding_bottom,
left=left_margin,
width=width,
height=height)
if theme_data.font_main_italics:
lyrics += 'font-style:italic; '
if theme_data.font_main_bold:
lyrics += 'font-weight:bold; '
return lyrics
justify = '' if (theme_data.display_horizontal_align == HorizontalType.Justify) else ' white-space: pre-wrap;\n'
padding_bottom = '0.5em' if (theme_data.display_vertical_align == VerticalType.Bottom) else '0'
return LYRICS_FORMAT_SRC.substitute(justify=justify,
align=align,
valign=valign,
font=theme_data.font_main_name,
size=theme_data.font_main_size,
color=theme_data.font_main_color,
line='{line:d}'.format(line=100 + int(theme_data.font_main_line_adjustment)),
bottom=padding_bottom,
left=left_margin,
width=width,
height=height,
font_style='\n font-style: italic;' if theme_data.font_main_italics else '',
font_weight='\n font-weight: bold;' if theme_data.font_main_bold else '')
def build_footer_css(item, height):
@ -751,22 +760,11 @@ def build_footer_css(item, height):
:param item: Service Item to be processed.
:param height:
"""
style = """
left: {left}px;
bottom: {bottom}px;
width: {width}px;
font-family: {family};
font-size: {size}pt;
color: {color};
text-align: left;
white-space: {space};
"""
theme = item.theme_data
if not theme or not item.footer:
return ''
bottom = height - int(item.footer.y()) - int(item.footer.height())
whitespace = 'normal' if Settings().value('themes/wrap footer') else 'nowrap'
lyrics_html = style.format(left=item.footer.x(), bottom=bottom, width=item.footer.width(),
family=theme.font_footer_name, size=theme.font_footer_size,
color=theme.font_footer_color, space=whitespace)
return lyrics_html
return FOOTER_SRC.substitute(left=item.footer.x(), bottom=bottom, width=item.footer.width(),
family=theme.font_footer_name, size=theme.font_footer_size,
color=theme.font_footer_color, space=whitespace)

View File

@ -565,13 +565,13 @@ class FirstTimeForm(QtWidgets.QWizard, UiFirstTimeWizard, RegistryProperties):
if self.has_run_wizard:
text = translate('OpenLP.FirstTimeWizard',
'Download complete. Click the {button} button to return to OpenLP.'
).format(text=clean_button_text(self.buttonText(QtWidgets.QWizard.FinishButton)))
).format(button=clean_button_text(self.buttonText(QtWidgets.QWizard.FinishButton)))
self.progress_label.setText(text)
else:
text = translate('OpenLP.FirstTimeWizard',
'Download complete. Click the {button} button to start OpenLP.'
).format(button=clean_button_text(self.buttonText(QtWidgets.QWizard.FinishButton)))
self.progress_label.setText()
self.progress_label.setText(text)
else:
if self.has_run_wizard:
text = translate('OpenLP.FirstTimeWizard',
@ -582,7 +582,7 @@ class FirstTimeForm(QtWidgets.QWizard, UiFirstTimeWizard, RegistryProperties):
text = translate('OpenLP.FirstTimeWizard',
'Click the {button} button to start OpenLP.'
).format(button=clean_button_text(self.buttonText(QtWidgets.QWizard.FinishButton)))
self.progress_label.setText()
self.progress_label.setText(text)
self.finish_button.setVisible(True)
self.finish_button.setEnabled(True)
self.cancel_button.setVisible(False)

View File

@ -104,7 +104,7 @@ class Ui_EditBibleDialog(object):
for book in BiblesResourcesDB.get_books():
self.book_name_label[book['abbreviation']] = QtWidgets.QLabel(self.book_name_widget)
self.book_name_label[book['abbreviation']].setObjectName(
'book_name_label[{name}]'.format(book=book['abbreviation']))
'book_name_label[{book}]'.format(book=book['abbreviation']))
self.book_name_edit[book['abbreviation']] = QtWidgets.QLineEdit(self.book_name_widget)
self.book_name_edit[book['abbreviation']].setObjectName(
'book_name_edit[{name}]'.format(name=book['abbreviation']))

View File

@ -24,15 +24,17 @@ The :mod:`mediashout` module provides the functionality for importing
a MediaShout database into the OpenLP database.
"""
# WARNING: See https://docs.python.org/2/library/sqlite3.html for value substitution
# WARNING: See https://docs.python.org/3/library/sqlite3.html for value substitution
# in SQL statements
import pyodbc
import logging
from openlp.core.lib import translate
from openlp.plugins.songs.lib.importers.songimport import SongImport
VERSE_TAGS = ['V', 'C', 'B', 'O', 'P', 'I', 'E']
log = logging.getLogger(__name__)
class MediaShoutImport(SongImport):
@ -44,17 +46,18 @@ class MediaShoutImport(SongImport):
"""
Initialise the MediaShout importer.
"""
SongImport.__init__(self, manager, **kwargs)
super(MediaShoutImport, self).__init__(manager, **kwargs)
def do_import(self):
"""
Receive a single file to import.
"""
try:
conn = pyodbc.connect('DRIVER={Microsoft Access Driver (*.mdb)};DBQ={source};'
'PWD=6NOZ4eHK7k'.format(sorce=self.import_source))
except:
conn = pyodbc.connect('DRIVER={{Microsoft Access Driver (*.mdb)}};DBQ={source};'
'PWD=6NOZ4eHK7k'.format(source=self.import_source))
except Exception as e:
# Unfortunately no specific exception type
log.exception(e)
self.log_error(self.import_source, translate('SongsPlugin.MediaShoutImport',
'Unable to open the MediaShout database.'))
return
@ -63,17 +66,21 @@ class MediaShoutImport(SongImport):
songs = cursor.fetchall()
self.import_wizard.progress_bar.setMaximum(len(songs))
for song in songs:
topics = []
if self.stop_import_flag:
break
cursor.execute('SELECT Type, Number, Text FROM Verses WHERE Record = ? ORDER BY Type, Number', song.Record)
cursor.execute('SELECT Type, Number, Text FROM Verses WHERE Record = ? ORDER BY Type, Number',
float(song.Record))
verses = cursor.fetchall()
cursor.execute('SELECT Type, Number, POrder FROM PlayOrder WHERE Record = ? ORDER BY POrder', song.Record)
cursor.execute('SELECT Type, Number, POrder FROM PlayOrder WHERE Record = ? ORDER BY POrder',
float(song.Record))
verse_order = cursor.fetchall()
cursor.execute('SELECT Name FROM Themes INNER JOIN SongThemes ON SongThemes.ThemeId = Themes.ThemeId '
'WHERE SongThemes.Record = ?', song.Record)
topics = cursor.fetchall()
if cursor.tables(table='TableName', tableType='TABLE').fetchone():
cursor.execute('SELECT Name FROM Themes INNER JOIN SongThemes ON SongThemes.ThemeId = Themes.ThemeId '
'WHERE SongThemes.Record = ?', float(song.Record))
topics = cursor.fetchall()
cursor.execute('SELECT Name FROM Groups INNER JOIN SongGroups ON SongGroups.GroupId = Groups.GroupId '
'WHERE SongGroups.Record = ?', song.Record)
'WHERE SongGroups.Record = ?', float(song.Record))
topics += cursor.fetchall()
self.process_song(song, verses, verse_order, topics)

View File

@ -156,8 +156,8 @@ class OpenSongImport(SongImport):
ustring = str(root.__getattr__(attr))
if isinstance(fn_or_string, str):
if attr in ['ccli']:
ustring = ''.join(re.findall('\d+', ustring))
if ustring:
ustring = ''.join(re.findall('\d+', ustring))
setattr(self, fn_or_string, int(ustring))
else:
setattr(self, fn_or_string, None)

View File

@ -55,7 +55,7 @@ class OPSProImport(SongImport):
"""
password = self.extract_mdb_password()
try:
conn = pyodbc.connect('DRIVER={Microsoft Access Driver (*.mdb)};DBQ={source};'
conn = pyodbc.connect('DRIVER={{Microsoft Access Driver (*.mdb)}};DBQ={source};'
'PWD={password}'.format(source=self.import_source, password=password))
except (pyodbc.DatabaseError, pyodbc.IntegrityError, pyodbc.InternalError, pyodbc.OperationalError) as e:
log.warning('Unable to connect the OPS Pro database {source}. {error}'.format(source=self.import_source,
@ -74,11 +74,11 @@ class OPSProImport(SongImport):
break
# Type means: 0=Original, 1=Projection, 2=Own
cursor.execute('SELECT Lyrics, Type, IsDualLanguage FROM Lyrics WHERE SongID = ? AND Type < 2 '
'ORDER BY Type DESC', song.ID)
'ORDER BY Type DESC', float(song.ID))
lyrics = cursor.fetchone()
cursor.execute('SELECT CategoryName FROM Category INNER JOIN SongCategory '
'ON Category.ID = SongCategory.CategoryID WHERE SongCategory.SongID = ? '
'ORDER BY CategoryName', song.ID)
'ORDER BY CategoryName', float(song.ID))
topics = cursor.fetchall()
try:
self.process_song(song, lyrics, topics)

View File

@ -56,7 +56,13 @@ class PresentationManagerImport(SongImport):
# Open file with detected encoding and remove encoding declaration
text = open(file_path, mode='r', encoding=encoding).read()
text = re.sub('.+\?>\n', '', text)
tree = etree.fromstring(text, parser=etree.XMLParser(recover=True))
try:
tree = etree.fromstring(text, parser=etree.XMLParser(recover=True))
except ValueError:
self.log_error(file_path,
translate('SongsPlugin.PresentationManagerImport',
'File is not in XML-format, which is the only format supported.'))
continue
root = objectify.fromstring(etree.tostring(tree))
self.process_song(root)

View File

@ -72,7 +72,7 @@ class SongProImport(SongImport):
Receive a single file or a list of files to import.
"""
self.encoding = None
with open(self.import_source, 'rt') as songs_file:
with open(self.import_source, 'rt', errors='ignore') as songs_file:
self.import_wizard.progress_bar.setMaximum(0)
tag = ''
text = ''

View File

@ -106,6 +106,7 @@ class SongShowPlusImport(SongImport):
song_data = open(file, 'rb')
while True:
block_key, = struct.unpack("I", song_data.read(4))
log.debug('block_key: %d' % block_key)
# The file ends with 4 NULL's
if block_key == 0:
break
@ -117,7 +118,13 @@ class SongShowPlusImport(SongImport):
null, verse_name_length, = struct.unpack("BB", song_data.read(2))
verse_name = self.decode(song_data.read(verse_name_length))
length_descriptor_size, = struct.unpack("B", song_data.read(1))
log.debug(length_descriptor_size)
log.debug('length_descriptor_size: %d' % length_descriptor_size)
# In the case of song_numbers the number is in the data from the
# current position to the next block starts
if block_key == SONG_NUMBER:
sn_bytes = song_data.read(length_descriptor_size - 1)
self.song_number = int.from_bytes(sn_bytes, byteorder='little')
continue
# Detect if/how long the length descriptor is
if length_descriptor_size == 12 or length_descriptor_size == 20:
length_descriptor, = struct.unpack("I", song_data.read(4))
@ -127,8 +134,9 @@ class SongShowPlusImport(SongImport):
length_descriptor = 0
else:
length_descriptor, = struct.unpack("B", song_data.read(1))
log.debug(length_descriptor_size)
log.debug('length_descriptor: %d' % length_descriptor)
data = song_data.read(length_descriptor)
log.debug(data)
if block_key == TITLE:
self.title = self.decode(data)
elif block_key == AUTHOR:
@ -168,8 +176,6 @@ class SongShowPlusImport(SongImport):
self.ssp_verse_order_list.append(verse_tag)
elif block_key == SONG_BOOK:
self.song_book_name = self.decode(data)
elif block_key == SONG_NUMBER:
self.song_number = ord(data)
elif block_key == CUSTOM_VERSE:
verse_tag = self.to_openlp_verse_tag(verse_name)
self.add_verse(self.decode(data), verse_tag)

View File

@ -117,6 +117,4 @@ class VideoPsalmImport(SongImport):
if not self.finish():
self.log_error('Could not import {title}'.format(title=self.title))
except Exception as e:
self.log_error(translate('SongsPlugin.VideoPsalmImport', 'File {name}').format(name=file.name),
translate('SongsPlugin.VideoPsalmImport', 'Error: {error}').format(error=e))
song_file.close()
self.log_error(song_file.name, translate('SongsPlugin.VideoPsalmImport', 'Error: {error}').format(error=e))

View File

@ -49,7 +49,7 @@ class WorshipCenterProImport(SongImport):
Receive a single file to import.
"""
try:
conn = pyodbc.connect('DRIVER={Microsoft Access Driver (*.mdb)};'
conn = pyodbc.connect('DRIVER={{Microsoft Access Driver (*.mdb)}};'
'DBQ={source}'.format(source=self.import_source))
except (pyodbc.DatabaseError, pyodbc.IntegrityError, pyodbc.InternalError, pyodbc.OperationalError) as e:
log.warning('Unable to connect the WorshipCenter Pro '

View File

@ -603,7 +603,7 @@ class SongMediaItem(MediaManagerItem):
else:
verse_index = VerseType.from_tag(verse[0]['type'])
verse_tag = VerseType.translated_tags[verse_index]
verse_def = '{tag}{label}'.format(tzg=verse_tag, text=verse[0]['label'])
verse_def = '{tag}{text}'.format(tag=verse_tag, text=verse[0]['label'])
service_item.add_from_text(verse[1], verse_def)
service_item.title = song.title
author_list = self.generate_footer(service_item, song)

View File

@ -14,177 +14,190 @@ from tests.functional import MagicMock, patch
from tests.helpers.testmixin import TestMixin
HTML = """
<!DOCTYPE html>
<html>
<head>
<title>OpenLP Display</title>
<style>
*{
margin: 0;
padding: 0;
border: 0;
overflow: hidden;
-webkit-user-select: none;
}
body {
;
}
.size {
position: absolute;
left: 0px;
top: 0px;
width: 100%;
height: 100%;
}
#black {
z-index: 8;
background-color: black;
display: none;
}
#bgimage {
z-index: 1;
}
#image {
z-index: 2;
}
plugin CSS
#footer {
position: absolute;
z-index: 6;
dummy: dummy;
}
/* lyric css */
sup {
font-size: 0.6em;
vertical-align: top;
position: relative;
top: -0.3em;
}
</style>
<script>
var timer = null;
var transition = false;
plugin JS
function show_image(src){
var img = document.getElementById('image');
img.src = src;
if(src == '')
img.style.display = 'none';
else
img.style.display = 'block';
<!DOCTYPE html>
<html>
<head>
<title>OpenLP Display</title>
<style>
*{
margin: 0;
padding: 0;
border: 0;
overflow: hidden;
-webkit-user-select: none;
}
body {
;
}
.size {
position: absolute;
left: 0px;
top: 0px;
width: 100%;
height: 100%;
}
#black {
z-index: 8;
background-color: black;
display: none;
}
#bgimage {
z-index: 1;
}
#image {
z-index: 2;
}
plugin CSS
#footer {
position: absolute;
z-index: 6;
dummy: dummy;
}
/* lyric css */
sup {
font-size: 0.6em;
vertical-align: top;
position: relative;
top: -0.3em;
}
</style>
<script>
var timer = null;
var transition = false;
plugin JS
function show_blank(state){
var black = 'none';
var lyrics = '';
switch(state){
case 'theme':
lyrics = 'hidden';
break;
case 'black':
black = 'block';
break;
case 'desktop':
break;
function show_image(src){
var img = document.getElementById('image');
img.src = src;
if(src == '')
img.style.display = 'none';
else
img.style.display = 'block';
}
document.getElementById('black').style.display = black;
document.getElementById('lyricsmain').style.visibility = lyrics;
document.getElementById('image').style.visibility = lyrics;
document.getElementById('footer').style.visibility = lyrics;
}
function show_footer(footertext){
document.getElementById('footer').innerHTML = footertext;
}
function show_text(new_text){
var match = /-webkit-text-fill-color:[^;"]+/gi;
if(timer != null)
clearTimeout(timer);
/*
QtWebkit bug with outlines and justify causing outline alignment
problems. (Bug 859950) Surround each word with a <span> to workaround,
but only in this scenario.
*/
var txt = document.getElementById('lyricsmain');
if(window.getComputedStyle(txt).textAlign == 'justify'){
if(window.getComputedStyle(txt).webkitTextStrokeWidth != '0px'){
new_text = new_text.replace(/(\s|&nbsp;)+(?![^<]*>)/g,
function(match) {
return '</span>' + match + '<span>';
});
new_text = '<span>' + new_text + '</span>';
function show_blank(state){
var black = 'none';
var lyrics = '';
switch(state){
case 'theme':
lyrics = 'hidden';
break;
case 'black':
black = 'block';
break;
case 'desktop':
break;
}
document.getElementById('black').style.display = black;
document.getElementById('lyricsmain').style.visibility = lyrics;
document.getElementById('image').style.visibility = lyrics;
document.getElementById('footer').style.visibility = lyrics;
}
text_fade('lyricsmain', new_text);
}
function text_fade(id, new_text){
/*
Show the text.
*/
var text = document.getElementById(id);
if(text == null) return;
if(!transition){
function show_footer(footertext){
document.getElementById('footer').innerHTML = footertext;
}
function show_text(new_text){
var match = /-webkit-text-fill-color:[^;"]+/gi;
if(timer != null)
clearTimeout(timer);
/*
QtWebkit bug with outlines and justify causing outline alignment
problems. (Bug 859950) Surround each word with a <span> to workaround,
but only in this scenario.
*/
var txt = document.getElementById('lyricsmain');
if(window.getComputedStyle(txt).textAlign == 'justify'){
if(window.getComputedStyle(txt).webkitTextStrokeWidth != '0px'){
new_text = new_text.replace(/(\s|&nbsp;)+(?![^<]*>)/g,
function(match) {
return '</span>' + match + '<span>';
});
new_text = '<span>' + new_text + '</span>';
}
}
text_fade('lyricsmain', new_text);
}
function text_fade(id, new_text){
/*
Show the text.
*/
var text = document.getElementById(id);
if(text == null) return;
if(!transition){
text.innerHTML = new_text;
return;
}
// Fade text out. 0.1 to minimize the time "nothing" is shown on the screen.
text.style.opacity = '0.1';
// Fade new text in after the old text has finished fading out.
timer = window.setTimeout(function(){_show_text(text, new_text)}, 400);
}
function _show_text(text, new_text) {
/*
Helper function to show the new_text delayed.
*/
text.innerHTML = new_text;
return;
text.style.opacity = '1';
// Wait until the text is completely visible. We want to save the timer id, to be able to call
// clearTimeout(timer) when the text has changed before finishing fading.
timer = window.setTimeout(function(){timer = null;}, 400);
}
// Fade text out. 0.1 to minimize the time "nothing" is shown on the screen.
text.style.opacity = '0.1';
// Fade new text in after the old text has finished fading out.
timer = window.setTimeout(function(){_show_text(text, new_text)}, 400);
}
function _show_text(text, new_text) {
/*
Helper function to show the new_text delayed.
*/
text.innerHTML = new_text;
text.style.opacity = '1';
// Wait until the text is completely visible. We want to save the timer id, to be able to call
// clearTimeout(timer) when the text has changed before finishing fading.
timer = window.setTimeout(function(){timer = null;}, 400);
}
function show_text_completed(){
return (timer == null);
}
</script>
</head>
<body>
<img id="bgimage" class="size" style="display:none;" />
<img id="image" class="size" style="display:none;" />
plugin HTML
<div class="lyricstable"><div id="lyricsmain" style="opacity:1" class="lyricscell lyricsmain"></div></div>
<div id="footer" class="footer"></div>
<div id="black" class="size"></div>
</body>
</html>
"""
function show_text_completed(){
return (timer == null);
}
</script>
</head>
<body>
<img id="bgimage" class="size" style="display:none;" />
<img id="image" class="size" style="display:none;" />
plugin HTML
<div class="lyricstable"><div id="lyricsmain" style="opacity:1" class="lyricscell lyricsmain"></div></div>
<div id="footer" class="footer"></div>
<div id="black" class="size"></div>
</body>
</html>
"""
BACKGROUND_CSS_RADIAL = 'background: -webkit-gradient(radial, 5 50%, 100, 5 50%, 5, from(#000000), to(#FFFFFF)) fixed'
LYRICS_CSS = """
.lyricstable {
z-index: 5;
position: absolute;
display: table;
left: 10px; top: 20px;
}
.lyricscell {
display: table-cell;
word-wrap: break-word;
-webkit-transition: opacity 0.4s ease;
lyrics_format_css
}
.lyricsmain {
text-shadow: #000000 5px 5px;
}
"""
.lyricstable {
z-index: 5;
position: absolute;
display: table;
left: 10px; top: 20px;
}
.lyricscell {
display: table-cell;
word-wrap: break-word;
-webkit-transition: opacity 0.4s ease;
lyrics_format_css
}
.lyricsmain {
text-shadow: #000000 5px 5px;
}
"""
LYRICS_OUTLINE_CSS = ' -webkit-text-stroke: 0.125em #000000; -webkit-text-fill-color: #FFFFFF; '
LYRICS_FORMAT_CSS = ' word-wrap: break-word; text-align: justify; vertical-align: bottom; ' + \
'font-family: Arial; font-size: 40pt; color: #FFFFFF; line-height: 108%; margin: 0;padding: 0; ' + \
'padding-bottom: 0.5em; padding-left: 2px; width: 1580px; height: 810px; font-style:italic; font-weight:bold; '
LYRICS_FORMAT_CSS = """
word-wrap: break-word;
text-align: justify;
vertical-align: bottom;
font-family: Arial;
font-size: 40pt;
color: #FFFFFF;
line-height: 108%;
margin: 0;
padding: 0;
padding-bottom: 0.5em;
padding-left: 2px;
width: 1580px;
height: 810px;
font-style: italic;
font-weight: bold;
"""
FOOTER_CSS_BASE = """
left: 10px;
bottom: 0px;

View File

@ -22,13 +22,13 @@
"""
This module contains tests for the OpenSong song importer.
"""
import os
from unittest import TestCase
from tests.helpers.songfileimport import SongImportTestHelper
from openlp.plugins.songs.lib.importers.opensong import OpenSongImport
from openlp.core.common import Registry
from tests.helpers.songfileimport import SongImportTestHelper
from tests.functional import patch, MagicMock
TEST_PATH = os.path.abspath(
@ -54,6 +54,8 @@ class TestOpenSongFileImport(SongImportTestHelper):
self.load_external_result_data(os.path.join(TEST_PATH, 'One, Two, Three, Four, Five.json')))
self.file_import([os.path.join(TEST_PATH, 'Amazing Grace2')],
self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json')))
self.file_import([os.path.join(TEST_PATH, 'Amazing Grace with bad CCLI')],
self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace without CCLI.json')))
class TestOpenSongImport(TestCase):

View File

@ -52,6 +52,8 @@ class TestSongShowPlusFileImport(SongImportTestHelper):
self.load_external_result_data(os.path.join(TEST_PATH, 'Beautiful Garden Of Prayer.json')))
self.file_import([os.path.join(TEST_PATH, 'a mighty fortress is our god.sbsong')],
self.load_external_result_data(os.path.join(TEST_PATH, 'a mighty fortress is our god.json')))
self.file_import([os.path.join(TEST_PATH, 'cleanse-me.sbsong')],
self.load_external_result_data(os.path.join(TEST_PATH, 'cleanse-me.json')))
class TestSongShowPlusImport(TestCase):

View File

@ -29,6 +29,7 @@ from unittest import TestCase
from openlp.plugins.songs.lib.importers.opensong import OpenSongImport
from openlp.core.common import Registry
from tests.functional import patch, MagicMock, call
log = logging.getLogger(__name__)
@ -36,7 +37,7 @@ log = logging.getLogger(__name__)
class SongImportTestHelper(TestCase):
"""
This class is designed to be a helper class to reduce repition when testing the import of song files.
This class is designed to be a helper class to reduce repetition when testing the import of song files.
"""
def __init__(self, *args, **kwargs):
super(SongImportTestHelper, self).__init__(*args, **kwargs)

View File

@ -53,4 +53,4 @@
<key_line></key_line>
<time_sig></time_sig>
<style index="default_style"></style>
</song>
</song>

View File

@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8"?>
<song>
<title>Amazing Grace (Demonstration)</title>
<author>John Newton, Edwin Excell &amp; John P. Rees</author>
<copyright>Public Domain </copyright>
<presentation>V1 V2 V3 V4 V5</presentation>
<capo print="false"></capo>
<tempo></tempo>
<ccli>GE</ccli>
<theme>God: Assurance/Grace/Salvation</theme>
<alttheme>Worship: Praise</alttheme>
<user1> </user1>
<user2> </user2>
<user3> </user3>
<lyrics>[V]
;Test the chords format
;Chords beging with .
;Verses begin with their verse number
;Link words with _
;Comments begin with ;
. D D7 G D
1A______ma________zing grace! How sweet the sound!
2'Twas grace that taught my heart to fear,
3The Lord has pro____mised good to me,
4Thro' ma________ny dan____gers, toils and snares
5When we've been there ten thou__sand years,
. Bm E A A7
1That saved a wretch like me!
2And grace my fears re___lieved.
3His Word my hope se___cures.
4I have al___rea____dy come.
5Bright shi___ning as the sun,
. D D7 G D
1I once was lost, but now am found;
2How pre___cious did that grace ap____pear,
3He will my shield and por___tion be
4'Tis grace that brought me safe thus far,
5We've no less days to sing God's praise,
. Bm A G D
1Was blind, but now I see.
2The hour I first be_lieved.
3As long as life en_dures.
4And grace will lead me home.
5Than when we first be_gun.
</lyrics>
<hymn_number>Demonstration Songs 0</hymn_number>
<key></key>
<aka></aka>
<key_line></key_line>
<time_sig></time_sig>
<style index="default_style"></style>
</song>

View File

@ -0,0 +1,42 @@
{
"authors": [
"John Newton",
"Edwin Excell",
"John P. Rees"
],
"ccli_number": null,
"comments": "\n\n\n",
"copyright": "Public Domain ",
"song_book_name": "Demonstration Songs",
"song_number": 0,
"title": "Amazing Grace (Demonstration)",
"topics": [
"Assurance",
"Grace",
"Praise",
"Salvation"
],
"verse_order_list": [],
"verses": [
[
"Amazing grace! How sweet the sound!\nThat saved a wretch like me!\nI once was lost, but now am found;\nWas blind, but now I see.",
"v1"
],
[
"'Twas grace that taught my heart to fear,\nAnd grace my fears relieved.\nHow precious did that grace appear,\nThe hour I first believed.",
"v2"
],
[
"The Lord has promised good to me,\nHis Word my hope secures.\nHe will my shield and portion be\nAs long as life endures.",
"v3"
],
[
"Thro' many dangers, toils and snares\nI have already come.\n'Tis grace that brought me safe thus far,\nAnd grace will lead me home.",
"v4"
],
[
"When we've been there ten thousand years,\nBright shining as the sun,\nWe've no less days to sing God's praise,\nThan when we first begun.",
"v5"
]
]
}

View File

@ -0,0 +1,38 @@
{
"authors": [
"J. Edwin Orr"
],
"ccli_number": 56307,
"comments": "",
"copyright": "Public Domain ",
"song_book_name": "",
"song_number": 438,
"title": "Cleanse Me [438]",
"topics": [
"Cleansing",
"Communion",
"Consecration",
"Holiness",
"Holy Spirit",
"Revival"
],
"verse_order_list": [],
"verses": [
[
"Search me, O God,\r\nAnd know my heart today;\r\nTry me, O Savior,\r\nKnow my thoughts, I pray.\r\nSee if there be\r\nSome wicked way in me;\r\nCleanse me from every sin\r\nAnd set me free.",
"v1"
],
[
"I praise Thee, Lord,\r\nFor cleansing me from sin;\r\nFulfill Thy Word,\r\nAnd make me pure within.\r\nFill me with fire\r\nWhere once I burned with shame;\r\nGrant my desire\r\nTo magnify Thy name.",
"v2"
],
[
"Lord, take my life,\r\nAnd make it wholly Thine;\r\nFill my poor heart\r\nWith Thy great love divine.\r\nTake all my will,\r\nMy passion, self and pride;\r\nI now surrender, Lord\r\nIn me abide.",
"v3"
],
[
"O Holy Ghost,\r\nRevival comes from Thee;\r\nSend a revival,\r\nStart the work in me.\r\nThy Word declares\r\nThou wilt supply our need;\r\nFor blessings now,\r\nO Lord, I humbly plead.",
"v4"
]
]
}

Binary file not shown.