forked from openlp/openlp
Merge trunk fix conflict
This commit is contained in:
commit
c8754c86aa
@ -129,7 +129,7 @@ class Settings(QtCore.QSettings):
|
|||||||
'advanced/recent file count': 4,
|
'advanced/recent file count': 4,
|
||||||
'advanced/save current plugin': False,
|
'advanced/save current plugin': False,
|
||||||
'advanced/slide limits': SlideLimits.End,
|
'advanced/slide limits': SlideLimits.End,
|
||||||
'advanced/slide max height': 0,
|
'advanced/slide max height': -4,
|
||||||
'advanced/single click preview': False,
|
'advanced/single click preview': False,
|
||||||
'advanced/single click service preview': False,
|
'advanced/single click service preview': False,
|
||||||
'advanced/x11 bypass wm': X11_BYPASS_DEFAULT,
|
'advanced/x11 bypass wm': X11_BYPASS_DEFAULT,
|
||||||
|
@ -390,163 +390,207 @@ is the function which has to be called from outside. The generated and returned
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from PyQt5 import QtWebKit
|
from PyQt5 import QtWebKit
|
||||||
|
from string import Template
|
||||||
|
|
||||||
from openlp.core.common import Settings
|
from openlp.core.common import Settings
|
||||||
from openlp.core.lib.theme import BackgroundType, BackgroundGradientType, VerticalType, HorizontalType
|
from openlp.core.lib.theme import BackgroundType, BackgroundGradientType, VerticalType, HorizontalType
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
# TODO: Verify where this is used before converting to python3
|
HTML_SRC = Template("""
|
||||||
HTMLSRC = """
|
<!DOCTYPE html>
|
||||||
<!DOCTYPE html>
|
<html>
|
||||||
<html>
|
<head>
|
||||||
<head>
|
<title>OpenLP Display</title>
|
||||||
<title>OpenLP Display</title>
|
<style>
|
||||||
<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| )+(?![^<]*>)/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;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
border: 0;
|
padding-bottom: ${bottom};
|
||||||
overflow: hidden;
|
padding-left: ${left}px;
|
||||||
-webkit-user-select: none;
|
width: ${width}px;
|
||||||
}
|
height: ${height}px;${font_style}${font_weight}
|
||||||
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| )+(?![^<]*>)/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>
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def build_html(item, screen, is_live, background, image=None, plugins=None):
|
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()
|
css_additions += plugin.get_display_css()
|
||||||
js_additions += plugin.get_display_javascript()
|
js_additions += plugin.get_display_javascript()
|
||||||
html_additions += plugin.get_display_html()
|
html_additions += plugin.get_display_html()
|
||||||
html = HTMLSRC % (
|
return HTML_SRC.substitute(bg_css=build_background_css(item, width),
|
||||||
build_background_css(item, width),
|
css_additions=css_additions,
|
||||||
css_additions,
|
footer_css=build_footer_css(item, height),
|
||||||
build_footer_css(item, height),
|
lyrics_css=build_lyrics_css(item),
|
||||||
build_lyrics_css(item),
|
transitions='true' if (theme_data and
|
||||||
'true' if theme_data and theme_data.display_slide_transition and is_live else 'false',
|
theme_data.display_slide_transition and
|
||||||
js_additions,
|
is_live) else 'false',
|
||||||
bgimage_src,
|
js_additions=js_additions,
|
||||||
image_src,
|
bg_image=bgimage_src,
|
||||||
html_additions
|
image=image_src,
|
||||||
)
|
html_additions=html_additions)
|
||||||
return html
|
|
||||||
|
|
||||||
|
|
||||||
def webkit_version():
|
def webkit_version():
|
||||||
@ -650,24 +693,6 @@ def build_lyrics_css(item):
|
|||||||
|
|
||||||
:param item: Service Item containing theme and location information
|
: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
|
theme_data = item.theme_data
|
||||||
lyricstable = ''
|
lyricstable = ''
|
||||||
lyrics = ''
|
lyrics = ''
|
||||||
@ -680,8 +705,7 @@ def build_lyrics_css(item):
|
|||||||
lyricsmain += ' text-shadow: {theme} {shadow}px ' \
|
lyricsmain += ' text-shadow: {theme} {shadow}px ' \
|
||||||
'{shadow}px;'.format(theme=theme_data.font_main_shadow_color,
|
'{shadow}px;'.format(theme=theme_data.font_main_shadow_color,
|
||||||
shadow=theme_data.font_main_shadow_size)
|
shadow=theme_data.font_main_shadow_size)
|
||||||
lyrics_css = style % (lyricstable, lyrics, lyricsmain)
|
return LYRICS_SRC.substitute(stable=lyricstable, lyrics=lyrics, main=lyricsmain)
|
||||||
return lyrics_css
|
|
||||||
|
|
||||||
|
|
||||||
def build_lyrics_outline_css(theme_data):
|
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]
|
align = HorizontalType.Names[theme_data.display_horizontal_align]
|
||||||
valign = VerticalType.Names[theme_data.display_vertical_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) if theme_data.font_main_outline else 0
|
||||||
left_margin = int(theme_data.font_main_outline_size) * 2
|
|
||||||
else:
|
|
||||||
left_margin = 0
|
|
||||||
justify = 'white-space:pre-wrap;'
|
|
||||||
# fix tag incompatibilities
|
# fix tag incompatibilities
|
||||||
if theme_data.display_horizontal_align == HorizontalType.Justify:
|
justify = '' if (theme_data.display_horizontal_align == HorizontalType.Justify) else ' white-space: pre-wrap;\n'
|
||||||
justify = ''
|
padding_bottom = '0.5em' if (theme_data.display_vertical_align == VerticalType.Bottom) else '0'
|
||||||
if theme_data.display_vertical_align == VerticalType.Bottom:
|
return LYRICS_FORMAT_SRC.substitute(justify=justify,
|
||||||
padding_bottom = '0.5em'
|
align=align,
|
||||||
else:
|
valign=valign,
|
||||||
padding_bottom = '0'
|
font=theme_data.font_main_name,
|
||||||
lyrics = '{justify} word-wrap: break-word; ' \
|
size=theme_data.font_main_size,
|
||||||
'text-align: {align}; vertical-align: {valign}; font-family: {font}; ' \
|
color=theme_data.font_main_color,
|
||||||
'font-size: {size}pt; color: {color}; line-height: {line:d}%; margin: 0;' \
|
line='{line:d}'.format(line=100 + int(theme_data.font_main_line_adjustment)),
|
||||||
'padding: 0; padding-bottom: {bottom}; padding-left: {left}px; width: {width}px; ' \
|
bottom=padding_bottom,
|
||||||
'height: {height}px; '.format(justify=justify,
|
left=left_margin,
|
||||||
align=align,
|
width=width,
|
||||||
valign=valign,
|
height=height,
|
||||||
font=theme_data.font_main_name,
|
font_style='\n font-style: italic;' if theme_data.font_main_italics else '',
|
||||||
size=theme_data.font_main_size,
|
font_weight='\n font-weight: bold;' if theme_data.font_main_bold else '')
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
def build_footer_css(item, height):
|
def build_footer_css(item, height):
|
||||||
@ -751,22 +760,11 @@ def build_footer_css(item, height):
|
|||||||
:param item: Service Item to be processed.
|
:param item: Service Item to be processed.
|
||||||
:param height:
|
: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
|
theme = item.theme_data
|
||||||
if not theme or not item.footer:
|
if not theme or not item.footer:
|
||||||
return ''
|
return ''
|
||||||
bottom = height - int(item.footer.y()) - int(item.footer.height())
|
bottom = height - int(item.footer.y()) - int(item.footer.height())
|
||||||
whitespace = 'normal' if Settings().value('themes/wrap footer') else 'nowrap'
|
whitespace = 'normal' if Settings().value('themes/wrap footer') else 'nowrap'
|
||||||
lyrics_html = style.format(left=item.footer.x(), bottom=bottom, width=item.footer.width(),
|
return FOOTER_SRC.substitute(left=item.footer.x(), bottom=bottom, width=item.footer.width(),
|
||||||
family=theme.font_footer_name, size=theme.font_footer_size,
|
family=theme.font_footer_name, size=theme.font_footer_size,
|
||||||
color=theme.font_footer_color, space=whitespace)
|
color=theme.font_footer_color, space=whitespace)
|
||||||
return lyrics_html
|
|
||||||
|
@ -87,11 +87,14 @@ class AdvancedTab(SettingsTab):
|
|||||||
self.ui_layout.addRow(self.expand_service_item_check_box)
|
self.ui_layout.addRow(self.expand_service_item_check_box)
|
||||||
self.slide_max_height_label = QtWidgets.QLabel(self.ui_group_box)
|
self.slide_max_height_label = QtWidgets.QLabel(self.ui_group_box)
|
||||||
self.slide_max_height_label.setObjectName('slide_max_height_label')
|
self.slide_max_height_label.setObjectName('slide_max_height_label')
|
||||||
self.slide_max_height_spin_box = QtWidgets.QSpinBox(self.ui_group_box)
|
self.slide_max_height_combo_box = QtWidgets.QComboBox(self.ui_group_box)
|
||||||
self.slide_max_height_spin_box.setObjectName('slide_max_height_spin_box')
|
self.slide_max_height_combo_box.addItem('', userData=0)
|
||||||
self.slide_max_height_spin_box.setRange(0, 1000)
|
self.slide_max_height_combo_box.addItem('', userData=-4)
|
||||||
self.slide_max_height_spin_box.setSingleStep(20)
|
# Generate numeric values for combo box dynamically
|
||||||
self.ui_layout.addRow(self.slide_max_height_label, self.slide_max_height_spin_box)
|
for px in range(60, 801, 5):
|
||||||
|
self.slide_max_height_combo_box.addItem(str(px) + 'px', userData=px)
|
||||||
|
self.slide_max_height_combo_box.setObjectName('slide_max_height_combo_box')
|
||||||
|
self.ui_layout.addRow(self.slide_max_height_label, self.slide_max_height_combo_box)
|
||||||
self.autoscroll_label = QtWidgets.QLabel(self.ui_group_box)
|
self.autoscroll_label = QtWidgets.QLabel(self.ui_group_box)
|
||||||
self.autoscroll_label.setObjectName('autoscroll_label')
|
self.autoscroll_label.setObjectName('autoscroll_label')
|
||||||
self.autoscroll_combo_box = QtWidgets.QComboBox(self.ui_group_box)
|
self.autoscroll_combo_box = QtWidgets.QComboBox(self.ui_group_box)
|
||||||
@ -265,7 +268,8 @@ class AdvancedTab(SettingsTab):
|
|||||||
'Expand new service items on creation'))
|
'Expand new service items on creation'))
|
||||||
self.slide_max_height_label.setText(translate('OpenLP.AdvancedTab',
|
self.slide_max_height_label.setText(translate('OpenLP.AdvancedTab',
|
||||||
'Max height for non-text slides\nin slide controller:'))
|
'Max height for non-text slides\nin slide controller:'))
|
||||||
self.slide_max_height_spin_box.setSpecialValueText(translate('OpenLP.AdvancedTab', 'Disabled'))
|
self.slide_max_height_combo_box.setItemText(0, translate('OpenLP.AdvancedTab', 'Disabled'))
|
||||||
|
self.slide_max_height_combo_box.setItemText(1, translate('OpenLP.AdvancedTab', 'Automatic'))
|
||||||
self.autoscroll_label.setText(translate('OpenLP.AdvancedTab',
|
self.autoscroll_label.setText(translate('OpenLP.AdvancedTab',
|
||||||
'When changing slides:'))
|
'When changing slides:'))
|
||||||
self.autoscroll_combo_box.setItemText(0, translate('OpenLP.AdvancedTab', 'Do not auto-scroll'))
|
self.autoscroll_combo_box.setItemText(0, translate('OpenLP.AdvancedTab', 'Do not auto-scroll'))
|
||||||
@ -355,10 +359,13 @@ class AdvancedTab(SettingsTab):
|
|||||||
self.single_click_preview_check_box.setChecked(settings.value('single click preview'))
|
self.single_click_preview_check_box.setChecked(settings.value('single click preview'))
|
||||||
self.single_click_service_preview_check_box.setChecked(settings.value('single click service preview'))
|
self.single_click_service_preview_check_box.setChecked(settings.value('single click service preview'))
|
||||||
self.expand_service_item_check_box.setChecked(settings.value('expand service item'))
|
self.expand_service_item_check_box.setChecked(settings.value('expand service item'))
|
||||||
self.slide_max_height_spin_box.setValue(settings.value('slide max height'))
|
slide_max_height_value = settings.value('slide max height')
|
||||||
|
for i in range(0, self.slide_max_height_combo_box.count()):
|
||||||
|
if self.slide_max_height_combo_box.itemData(i) == slide_max_height_value:
|
||||||
|
self.slide_max_height_combo_box.setCurrentIndex(i)
|
||||||
autoscroll_value = settings.value('autoscrolling')
|
autoscroll_value = settings.value('autoscrolling')
|
||||||
for i in range(0, len(self.autoscroll_map)):
|
for i in range(0, len(self.autoscroll_map)):
|
||||||
if self.autoscroll_map[i] == autoscroll_value:
|
if self.autoscroll_map[i] == autoscroll_value and i < self.autoscroll_combo_box.count():
|
||||||
self.autoscroll_combo_box.setCurrentIndex(i)
|
self.autoscroll_combo_box.setCurrentIndex(i)
|
||||||
self.enable_auto_close_check_box.setChecked(settings.value('enable exit confirmation'))
|
self.enable_auto_close_check_box.setChecked(settings.value('enable exit confirmation'))
|
||||||
self.hide_mouse_check_box.setChecked(settings.value('hide mouse'))
|
self.hide_mouse_check_box.setChecked(settings.value('hide mouse'))
|
||||||
@ -439,7 +446,9 @@ class AdvancedTab(SettingsTab):
|
|||||||
settings.setValue('single click preview', self.single_click_preview_check_box.isChecked())
|
settings.setValue('single click preview', self.single_click_preview_check_box.isChecked())
|
||||||
settings.setValue('single click service preview', self.single_click_service_preview_check_box.isChecked())
|
settings.setValue('single click service preview', self.single_click_service_preview_check_box.isChecked())
|
||||||
settings.setValue('expand service item', self.expand_service_item_check_box.isChecked())
|
settings.setValue('expand service item', self.expand_service_item_check_box.isChecked())
|
||||||
settings.setValue('slide max height', self.slide_max_height_spin_box.value())
|
slide_max_height_index = self.slide_max_height_combo_box.currentIndex()
|
||||||
|
slide_max_height_value = self.slide_max_height_combo_box.itemData(slide_max_height_index)
|
||||||
|
settings.setValue('slide max height', slide_max_height_value)
|
||||||
settings.setValue('autoscrolling', self.autoscroll_map[self.autoscroll_combo_box.currentIndex()])
|
settings.setValue('autoscrolling', self.autoscroll_map[self.autoscroll_combo_box.currentIndex()])
|
||||||
settings.setValue('enable exit confirmation', self.enable_auto_close_check_box.isChecked())
|
settings.setValue('enable exit confirmation', self.enable_auto_close_check_box.isChecked())
|
||||||
settings.setValue('hide mouse', self.hide_mouse_check_box.isChecked())
|
settings.setValue('hide mouse', self.hide_mouse_check_box.isChecked())
|
||||||
|
@ -564,14 +564,14 @@ class FirstTimeForm(QtWidgets.QWizard, UiFirstTimeWizard, RegistryProperties):
|
|||||||
self.progress_bar.setValue(self.progress_bar.maximum())
|
self.progress_bar.setValue(self.progress_bar.maximum())
|
||||||
if self.has_run_wizard:
|
if self.has_run_wizard:
|
||||||
text = translate('OpenLP.FirstTimeWizard',
|
text = translate('OpenLP.FirstTimeWizard',
|
||||||
'Download complete. Click the {text} button to return to OpenLP.'
|
'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)
|
self.progress_label.setText(text)
|
||||||
else:
|
else:
|
||||||
text = translate('OpenLP.FirstTimeWizard',
|
text = translate('OpenLP.FirstTimeWizard',
|
||||||
'Download complete. Click the {button} button to start OpenLP.'
|
'Download complete. Click the {button} button to start OpenLP.'
|
||||||
).format(button=clean_button_text(self.buttonText(QtWidgets.QWizard.FinishButton)))
|
).format(button=clean_button_text(self.buttonText(QtWidgets.QWizard.FinishButton)))
|
||||||
self.progress_label.setText()
|
self.progress_label.setText(text)
|
||||||
else:
|
else:
|
||||||
if self.has_run_wizard:
|
if self.has_run_wizard:
|
||||||
text = translate('OpenLP.FirstTimeWizard',
|
text = translate('OpenLP.FirstTimeWizard',
|
||||||
@ -582,7 +582,7 @@ class FirstTimeForm(QtWidgets.QWizard, UiFirstTimeWizard, RegistryProperties):
|
|||||||
text = translate('OpenLP.FirstTimeWizard',
|
text = translate('OpenLP.FirstTimeWizard',
|
||||||
'Click the {button} button to start OpenLP.'
|
'Click the {button} button to start OpenLP.'
|
||||||
).format(button=clean_button_text(self.buttonText(QtWidgets.QWizard.FinishButton)))
|
).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.setVisible(True)
|
||||||
self.finish_button.setEnabled(True)
|
self.finish_button.setEnabled(True)
|
||||||
self.cancel_button.setVisible(False)
|
self.cancel_button.setVisible(False)
|
||||||
|
@ -63,6 +63,7 @@ class ListPreviewWidget(QtWidgets.QTableWidget, RegistryProperties):
|
|||||||
# Initialize variables.
|
# Initialize variables.
|
||||||
self.service_item = ServiceItem()
|
self.service_item = ServiceItem()
|
||||||
self.screen_ratio = screen_ratio
|
self.screen_ratio = screen_ratio
|
||||||
|
self.auto_row_height = 100
|
||||||
# Connect signals
|
# Connect signals
|
||||||
self.verticalHeader().sectionResized.connect(self.row_resized)
|
self.verticalHeader().sectionResized.connect(self.row_resized)
|
||||||
|
|
||||||
@ -87,8 +88,14 @@ class ListPreviewWidget(QtWidgets.QTableWidget, RegistryProperties):
|
|||||||
height = self.viewport().width() // self.screen_ratio
|
height = self.viewport().width() // self.screen_ratio
|
||||||
max_img_row_height = Settings().value('advanced/slide max height')
|
max_img_row_height = Settings().value('advanced/slide max height')
|
||||||
# Adjust for row height cap if in use.
|
# Adjust for row height cap if in use.
|
||||||
if isinstance(max_img_row_height, int) and max_img_row_height > 0 and height > max_img_row_height:
|
if isinstance(max_img_row_height, int):
|
||||||
height = max_img_row_height
|
if max_img_row_height > 0 and height > max_img_row_height:
|
||||||
|
height = max_img_row_height
|
||||||
|
elif max_img_row_height < 0:
|
||||||
|
# If auto setting, show that number of slides, or if the resulting slides too small, 100px.
|
||||||
|
# E.g. If setting is -4, 4 slides will be visible, unless those slides are < 100px high.
|
||||||
|
self.auto_row_height = max(self.viewport().height() / (-1 * max_img_row_height), 100)
|
||||||
|
height = min(height, self.auto_row_height)
|
||||||
# Apply new height to slides
|
# Apply new height to slides
|
||||||
for frame_number in range(len(self.service_item.get_frames())):
|
for frame_number in range(len(self.service_item.get_frames())):
|
||||||
self.setRowHeight(frame_number, height)
|
self.setRowHeight(frame_number, height)
|
||||||
@ -99,7 +106,7 @@ class ListPreviewWidget(QtWidgets.QTableWidget, RegistryProperties):
|
|||||||
"""
|
"""
|
||||||
# Only for non-text slides when row height cap in use
|
# Only for non-text slides when row height cap in use
|
||||||
max_img_row_height = Settings().value('advanced/slide max height')
|
max_img_row_height = Settings().value('advanced/slide max height')
|
||||||
if self.service_item.is_text() or not isinstance(max_img_row_height, int) or max_img_row_height <= 0:
|
if self.service_item.is_text() or not isinstance(max_img_row_height, int) or max_img_row_height == 0:
|
||||||
return
|
return
|
||||||
# Get and validate label widget containing slide & adjust max width
|
# Get and validate label widget containing slide & adjust max width
|
||||||
try:
|
try:
|
||||||
@ -165,11 +172,15 @@ class ListPreviewWidget(QtWidgets.QTableWidget, RegistryProperties):
|
|||||||
slide_height = width // self.screen_ratio
|
slide_height = width // self.screen_ratio
|
||||||
# Setup and validate row height cap if in use.
|
# Setup and validate row height cap if in use.
|
||||||
max_img_row_height = Settings().value('advanced/slide max height')
|
max_img_row_height = Settings().value('advanced/slide max height')
|
||||||
if isinstance(max_img_row_height, int) and max_img_row_height > 0:
|
if isinstance(max_img_row_height, int) and max_img_row_height != 0:
|
||||||
if slide_height > max_img_row_height:
|
if max_img_row_height > 0 and slide_height > max_img_row_height:
|
||||||
|
# Manual Setting
|
||||||
slide_height = max_img_row_height
|
slide_height = max_img_row_height
|
||||||
label.setMaximumWidth(max_img_row_height * self.screen_ratio)
|
elif max_img_row_height < 0 and slide_height > self.auto_row_height:
|
||||||
label.resize(max_img_row_height * self.screen_ratio, max_img_row_height)
|
# Auto Setting
|
||||||
|
slide_height = self.auto_row_height
|
||||||
|
label.setMaximumWidth(slide_height * self.screen_ratio)
|
||||||
|
label.resize(slide_height * self.screen_ratio, slide_height)
|
||||||
# Build widget with stretch padding
|
# Build widget with stretch padding
|
||||||
container = QtWidgets.QWidget()
|
container = QtWidgets.QWidget()
|
||||||
hbox = QtWidgets.QHBoxLayout()
|
hbox = QtWidgets.QHBoxLayout()
|
||||||
|
@ -104,7 +104,7 @@ class Ui_EditBibleDialog(object):
|
|||||||
for book in BiblesResourcesDB.get_books():
|
for book in BiblesResourcesDB.get_books():
|
||||||
self.book_name_label[book['abbreviation']] = QtWidgets.QLabel(self.book_name_widget)
|
self.book_name_label[book['abbreviation']] = QtWidgets.QLabel(self.book_name_widget)
|
||||||
self.book_name_label[book['abbreviation']].setObjectName(
|
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']] = QtWidgets.QLineEdit(self.book_name_widget)
|
||||||
self.book_name_edit[book['abbreviation']].setObjectName(
|
self.book_name_edit[book['abbreviation']].setObjectName(
|
||||||
'book_name_edit[{name}]'.format(name=book['abbreviation']))
|
'book_name_edit[{name}]'.format(name=book['abbreviation']))
|
||||||
|
@ -24,15 +24,17 @@ The :mod:`mediashout` module provides the functionality for importing
|
|||||||
a MediaShout database into the OpenLP database.
|
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
|
# in SQL statements
|
||||||
|
|
||||||
import pyodbc
|
import pyodbc
|
||||||
|
import logging
|
||||||
|
|
||||||
from openlp.core.lib import translate
|
from openlp.core.lib import translate
|
||||||
from openlp.plugins.songs.lib.importers.songimport import SongImport
|
from openlp.plugins.songs.lib.importers.songimport import SongImport
|
||||||
|
|
||||||
VERSE_TAGS = ['V', 'C', 'B', 'O', 'P', 'I', 'E']
|
VERSE_TAGS = ['V', 'C', 'B', 'O', 'P', 'I', 'E']
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class MediaShoutImport(SongImport):
|
class MediaShoutImport(SongImport):
|
||||||
@ -44,17 +46,18 @@ class MediaShoutImport(SongImport):
|
|||||||
"""
|
"""
|
||||||
Initialise the MediaShout importer.
|
Initialise the MediaShout importer.
|
||||||
"""
|
"""
|
||||||
SongImport.__init__(self, manager, **kwargs)
|
super(MediaShoutImport, self).__init__(manager, **kwargs)
|
||||||
|
|
||||||
def do_import(self):
|
def do_import(self):
|
||||||
"""
|
"""
|
||||||
Receive a single file to import.
|
Receive a single file to import.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
conn = pyodbc.connect('DRIVER={Microsoft Access Driver (*.mdb)};DBQ={source};'
|
conn = pyodbc.connect('DRIVER={{Microsoft Access Driver (*.mdb)}};DBQ={source};'
|
||||||
'PWD=6NOZ4eHK7k'.format(sorce=self.import_source))
|
'PWD=6NOZ4eHK7k'.format(source=self.import_source))
|
||||||
except:
|
except Exception as e:
|
||||||
# Unfortunately no specific exception type
|
# Unfortunately no specific exception type
|
||||||
|
log.exception(e)
|
||||||
self.log_error(self.import_source, translate('SongsPlugin.MediaShoutImport',
|
self.log_error(self.import_source, translate('SongsPlugin.MediaShoutImport',
|
||||||
'Unable to open the MediaShout database.'))
|
'Unable to open the MediaShout database.'))
|
||||||
return
|
return
|
||||||
@ -63,17 +66,21 @@ class MediaShoutImport(SongImport):
|
|||||||
songs = cursor.fetchall()
|
songs = cursor.fetchall()
|
||||||
self.import_wizard.progress_bar.setMaximum(len(songs))
|
self.import_wizard.progress_bar.setMaximum(len(songs))
|
||||||
for song in songs:
|
for song in songs:
|
||||||
|
topics = []
|
||||||
if self.stop_import_flag:
|
if self.stop_import_flag:
|
||||||
break
|
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()
|
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()
|
verse_order = cursor.fetchall()
|
||||||
cursor.execute('SELECT Name FROM Themes INNER JOIN SongThemes ON SongThemes.ThemeId = Themes.ThemeId '
|
if cursor.tables(table='TableName', tableType='TABLE').fetchone():
|
||||||
'WHERE SongThemes.Record = ?', song.Record)
|
cursor.execute('SELECT Name FROM Themes INNER JOIN SongThemes ON SongThemes.ThemeId = Themes.ThemeId '
|
||||||
topics = cursor.fetchall()
|
'WHERE SongThemes.Record = ?', float(song.Record))
|
||||||
|
topics = cursor.fetchall()
|
||||||
cursor.execute('SELECT Name FROM Groups INNER JOIN SongGroups ON SongGroups.GroupId = Groups.GroupId '
|
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()
|
topics += cursor.fetchall()
|
||||||
self.process_song(song, verses, verse_order, topics)
|
self.process_song(song, verses, verse_order, topics)
|
||||||
|
|
||||||
|
@ -156,8 +156,8 @@ class OpenSongImport(SongImport):
|
|||||||
ustring = str(root.__getattr__(attr))
|
ustring = str(root.__getattr__(attr))
|
||||||
if isinstance(fn_or_string, str):
|
if isinstance(fn_or_string, str):
|
||||||
if attr in ['ccli']:
|
if attr in ['ccli']:
|
||||||
|
ustring = ''.join(re.findall('\d+', ustring))
|
||||||
if ustring:
|
if ustring:
|
||||||
ustring = ''.join(re.findall('\d+', ustring))
|
|
||||||
setattr(self, fn_or_string, int(ustring))
|
setattr(self, fn_or_string, int(ustring))
|
||||||
else:
|
else:
|
||||||
setattr(self, fn_or_string, None)
|
setattr(self, fn_or_string, None)
|
||||||
|
@ -55,7 +55,7 @@ class OPSProImport(SongImport):
|
|||||||
"""
|
"""
|
||||||
password = self.extract_mdb_password()
|
password = self.extract_mdb_password()
|
||||||
try:
|
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))
|
'PWD={password}'.format(source=self.import_source, password=password))
|
||||||
except (pyodbc.DatabaseError, pyodbc.IntegrityError, pyodbc.InternalError, pyodbc.OperationalError) as e:
|
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,
|
log.warning('Unable to connect the OPS Pro database {source}. {error}'.format(source=self.import_source,
|
||||||
@ -74,11 +74,11 @@ class OPSProImport(SongImport):
|
|||||||
break
|
break
|
||||||
# Type means: 0=Original, 1=Projection, 2=Own
|
# Type means: 0=Original, 1=Projection, 2=Own
|
||||||
cursor.execute('SELECT Lyrics, Type, IsDualLanguage FROM Lyrics WHERE SongID = ? AND Type < 2 '
|
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()
|
lyrics = cursor.fetchone()
|
||||||
cursor.execute('SELECT CategoryName FROM Category INNER JOIN SongCategory '
|
cursor.execute('SELECT CategoryName FROM Category INNER JOIN SongCategory '
|
||||||
'ON Category.ID = SongCategory.CategoryID WHERE SongCategory.SongID = ? '
|
'ON Category.ID = SongCategory.CategoryID WHERE SongCategory.SongID = ? '
|
||||||
'ORDER BY CategoryName', song.ID)
|
'ORDER BY CategoryName', float(song.ID))
|
||||||
topics = cursor.fetchall()
|
topics = cursor.fetchall()
|
||||||
try:
|
try:
|
||||||
self.process_song(song, lyrics, topics)
|
self.process_song(song, lyrics, topics)
|
||||||
|
@ -56,7 +56,13 @@ class PresentationManagerImport(SongImport):
|
|||||||
# Open file with detected encoding and remove encoding declaration
|
# Open file with detected encoding and remove encoding declaration
|
||||||
text = open(file_path, mode='r', encoding=encoding).read()
|
text = open(file_path, mode='r', encoding=encoding).read()
|
||||||
text = re.sub('.+\?>\n', '', text)
|
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))
|
root = objectify.fromstring(etree.tostring(tree))
|
||||||
self.process_song(root)
|
self.process_song(root)
|
||||||
|
|
||||||
|
@ -72,7 +72,7 @@ class SongProImport(SongImport):
|
|||||||
Receive a single file or a list of files to import.
|
Receive a single file or a list of files to import.
|
||||||
"""
|
"""
|
||||||
self.encoding = None
|
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)
|
self.import_wizard.progress_bar.setMaximum(0)
|
||||||
tag = ''
|
tag = ''
|
||||||
text = ''
|
text = ''
|
||||||
|
@ -106,6 +106,7 @@ class SongShowPlusImport(SongImport):
|
|||||||
song_data = open(file, 'rb')
|
song_data = open(file, 'rb')
|
||||||
while True:
|
while True:
|
||||||
block_key, = struct.unpack("I", song_data.read(4))
|
block_key, = struct.unpack("I", song_data.read(4))
|
||||||
|
log.debug('block_key: %d' % block_key)
|
||||||
# The file ends with 4 NULL's
|
# The file ends with 4 NULL's
|
||||||
if block_key == 0:
|
if block_key == 0:
|
||||||
break
|
break
|
||||||
@ -117,7 +118,13 @@ class SongShowPlusImport(SongImport):
|
|||||||
null, verse_name_length, = struct.unpack("BB", song_data.read(2))
|
null, verse_name_length, = struct.unpack("BB", song_data.read(2))
|
||||||
verse_name = self.decode(song_data.read(verse_name_length))
|
verse_name = self.decode(song_data.read(verse_name_length))
|
||||||
length_descriptor_size, = struct.unpack("B", song_data.read(1))
|
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
|
# Detect if/how long the length descriptor is
|
||||||
if length_descriptor_size == 12 or length_descriptor_size == 20:
|
if length_descriptor_size == 12 or length_descriptor_size == 20:
|
||||||
length_descriptor, = struct.unpack("I", song_data.read(4))
|
length_descriptor, = struct.unpack("I", song_data.read(4))
|
||||||
@ -127,8 +134,9 @@ class SongShowPlusImport(SongImport):
|
|||||||
length_descriptor = 0
|
length_descriptor = 0
|
||||||
else:
|
else:
|
||||||
length_descriptor, = struct.unpack("B", song_data.read(1))
|
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)
|
data = song_data.read(length_descriptor)
|
||||||
|
log.debug(data)
|
||||||
if block_key == TITLE:
|
if block_key == TITLE:
|
||||||
self.title = self.decode(data)
|
self.title = self.decode(data)
|
||||||
elif block_key == AUTHOR:
|
elif block_key == AUTHOR:
|
||||||
@ -168,8 +176,6 @@ class SongShowPlusImport(SongImport):
|
|||||||
self.ssp_verse_order_list.append(verse_tag)
|
self.ssp_verse_order_list.append(verse_tag)
|
||||||
elif block_key == SONG_BOOK:
|
elif block_key == SONG_BOOK:
|
||||||
self.song_book_name = self.decode(data)
|
self.song_book_name = self.decode(data)
|
||||||
elif block_key == SONG_NUMBER:
|
|
||||||
self.song_number = ord(data)
|
|
||||||
elif block_key == CUSTOM_VERSE:
|
elif block_key == CUSTOM_VERSE:
|
||||||
verse_tag = self.to_openlp_verse_tag(verse_name)
|
verse_tag = self.to_openlp_verse_tag(verse_name)
|
||||||
self.add_verse(self.decode(data), verse_tag)
|
self.add_verse(self.decode(data), verse_tag)
|
||||||
|
@ -117,6 +117,4 @@ class VideoPsalmImport(SongImport):
|
|||||||
if not self.finish():
|
if not self.finish():
|
||||||
self.log_error('Could not import {title}'.format(title=self.title))
|
self.log_error('Could not import {title}'.format(title=self.title))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.log_error(translate('SongsPlugin.VideoPsalmImport', 'File {name}').format(name=file.name),
|
self.log_error(song_file.name, translate('SongsPlugin.VideoPsalmImport', 'Error: {error}').format(error=e))
|
||||||
translate('SongsPlugin.VideoPsalmImport', 'Error: {error}').format(error=e))
|
|
||||||
song_file.close()
|
|
||||||
|
@ -49,7 +49,7 @@ class WorshipCenterProImport(SongImport):
|
|||||||
Receive a single file to import.
|
Receive a single file to import.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
conn = pyodbc.connect('DRIVER={Microsoft Access Driver (*.mdb)};'
|
conn = pyodbc.connect('DRIVER={{Microsoft Access Driver (*.mdb)}};'
|
||||||
'DBQ={source}'.format(source=self.import_source))
|
'DBQ={source}'.format(source=self.import_source))
|
||||||
except (pyodbc.DatabaseError, pyodbc.IntegrityError, pyodbc.InternalError, pyodbc.OperationalError) as e:
|
except (pyodbc.DatabaseError, pyodbc.IntegrityError, pyodbc.InternalError, pyodbc.OperationalError) as e:
|
||||||
log.warning('Unable to connect the WorshipCenter Pro '
|
log.warning('Unable to connect the WorshipCenter Pro '
|
||||||
|
@ -603,7 +603,7 @@ class SongMediaItem(MediaManagerItem):
|
|||||||
else:
|
else:
|
||||||
verse_index = VerseType.from_tag(verse[0]['type'])
|
verse_index = VerseType.from_tag(verse[0]['type'])
|
||||||
verse_tag = VerseType.translated_tags[verse_index]
|
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.add_from_text(verse[1], verse_def)
|
||||||
service_item.title = song.title
|
service_item.title = song.title
|
||||||
author_list = self.generate_footer(service_item, song)
|
author_list = self.generate_footer(service_item, song)
|
||||||
|
@ -14,177 +14,190 @@ from tests.functional import MagicMock, patch
|
|||||||
from tests.helpers.testmixin import TestMixin
|
from tests.helpers.testmixin import TestMixin
|
||||||
|
|
||||||
HTML = """
|
HTML = """
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>OpenLP Display</title>
|
<title>OpenLP Display</title>
|
||||||
<style>
|
<style>
|
||||||
*{
|
*{
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
border: 0;
|
border: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
-webkit-user-select: none;
|
-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';
|
|
||||||
}
|
}
|
||||||
|
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){
|
function show_image(src){
|
||||||
var black = 'none';
|
var img = document.getElementById('image');
|
||||||
var lyrics = '';
|
img.src = src;
|
||||||
switch(state){
|
if(src == '')
|
||||||
case 'theme':
|
img.style.display = 'none';
|
||||||
lyrics = 'hidden';
|
else
|
||||||
break;
|
img.style.display = 'block';
|
||||||
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){
|
function show_blank(state){
|
||||||
document.getElementById('footer').innerHTML = footertext;
|
var black = 'none';
|
||||||
}
|
var lyrics = '';
|
||||||
|
switch(state){
|
||||||
function show_text(new_text){
|
case 'theme':
|
||||||
var match = /-webkit-text-fill-color:[^;"]+/gi;
|
lyrics = 'hidden';
|
||||||
if(timer != null)
|
break;
|
||||||
clearTimeout(timer);
|
case 'black':
|
||||||
/*
|
black = 'block';
|
||||||
QtWebkit bug with outlines and justify causing outline alignment
|
break;
|
||||||
problems. (Bug 859950) Surround each word with a <span> to workaround,
|
case 'desktop':
|
||||||
but only in this scenario.
|
break;
|
||||||
*/
|
|
||||||
var txt = document.getElementById('lyricsmain');
|
|
||||||
if(window.getComputedStyle(txt).textAlign == 'justify'){
|
|
||||||
if(window.getComputedStyle(txt).webkitTextStrokeWidth != '0px'){
|
|
||||||
new_text = new_text.replace(/(\s| )+(?![^<]*>)/g,
|
|
||||||
function(match) {
|
|
||||||
return '</span>' + match + '<span>';
|
|
||||||
});
|
|
||||||
new_text = '<span>' + new_text + '</span>';
|
|
||||||
}
|
}
|
||||||
|
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){
|
function show_footer(footertext){
|
||||||
/*
|
document.getElementById('footer').innerHTML = footertext;
|
||||||
Show the text.
|
}
|
||||||
*/
|
|
||||||
var text = document.getElementById(id);
|
function show_text(new_text){
|
||||||
if(text == null) return;
|
var match = /-webkit-text-fill-color:[^;"]+/gi;
|
||||||
if(!transition){
|
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| )+(?![^<]*>)/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.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) {
|
function show_text_completed(){
|
||||||
/*
|
return (timer == null);
|
||||||
Helper function to show the new_text delayed.
|
}
|
||||||
*/
|
</script>
|
||||||
text.innerHTML = new_text;
|
</head>
|
||||||
text.style.opacity = '1';
|
<body>
|
||||||
// Wait until the text is completely visible. We want to save the timer id, to be able to call
|
<img id="bgimage" class="size" style="display:none;" />
|
||||||
// clearTimeout(timer) when the text has changed before finishing fading.
|
<img id="image" class="size" style="display:none;" />
|
||||||
timer = window.setTimeout(function(){timer = null;}, 400);
|
plugin HTML
|
||||||
}
|
<div class="lyricstable"><div id="lyricsmain" style="opacity:1" class="lyricscell lyricsmain"></div></div>
|
||||||
|
<div id="footer" class="footer"></div>
|
||||||
function show_text_completed(){
|
<div id="black" class="size"></div>
|
||||||
return (timer == null);
|
</body>
|
||||||
}
|
</html>
|
||||||
</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'
|
BACKGROUND_CSS_RADIAL = 'background: -webkit-gradient(radial, 5 50%, 100, 5 50%, 5, from(#000000), to(#FFFFFF)) fixed'
|
||||||
LYRICS_CSS = """
|
LYRICS_CSS = """
|
||||||
.lyricstable {
|
.lyricstable {
|
||||||
z-index: 5;
|
z-index: 5;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
display: table;
|
display: table;
|
||||||
left: 10px; top: 20px;
|
left: 10px; top: 20px;
|
||||||
}
|
}
|
||||||
.lyricscell {
|
.lyricscell {
|
||||||
display: table-cell;
|
display: table-cell;
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
-webkit-transition: opacity 0.4s ease;
|
-webkit-transition: opacity 0.4s ease;
|
||||||
lyrics_format_css
|
lyrics_format_css
|
||||||
}
|
}
|
||||||
.lyricsmain {
|
.lyricsmain {
|
||||||
text-shadow: #000000 5px 5px;
|
text-shadow: #000000 5px 5px;
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
LYRICS_OUTLINE_CSS = ' -webkit-text-stroke: 0.125em #000000; -webkit-text-fill-color: #FFFFFF; '
|
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; ' + \
|
LYRICS_FORMAT_CSS = """
|
||||||
'font-family: Arial; font-size: 40pt; color: #FFFFFF; line-height: 108%; margin: 0;padding: 0; ' + \
|
word-wrap: break-word;
|
||||||
'padding-bottom: 0.5em; padding-left: 2px; width: 1580px; height: 810px; font-style:italic; font-weight:bold; '
|
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 = """
|
FOOTER_CSS_BASE = """
|
||||||
left: 10px;
|
left: 10px;
|
||||||
bottom: 0px;
|
bottom: 0px;
|
||||||
|
@ -225,6 +225,44 @@ class TestListPreviewWidget(TestCase):
|
|||||||
calls = [call(0, 100), call(1, 100), call(0, 100), call(1, 100)]
|
calls = [call(0, 100), call(1, 100), call(0, 100), call(1, 100)]
|
||||||
mocked_setRowHeight.assert_has_calls(calls)
|
mocked_setRowHeight.assert_has_calls(calls)
|
||||||
|
|
||||||
|
@patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.resizeRowsToContents')
|
||||||
|
@patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.setRowHeight')
|
||||||
|
def test_replace_recalculate_layout_img_auto(self, mocked_setRowHeight, mocked_resizeRowsToContents):
|
||||||
|
"""
|
||||||
|
Test if "Max height for non-text slides..." auto, img slides resized in replace_service_item & __recalc...
|
||||||
|
"""
|
||||||
|
# GIVEN: A setting to adjust "Max height for non-text slides in slide controller",
|
||||||
|
# an image ServiceItem and a ListPreviewWidget.
|
||||||
|
|
||||||
|
# Mock Settings().value('advanced/slide max height')
|
||||||
|
self.mocked_Settings_obj.value.return_value = -4
|
||||||
|
# Mock self.viewport().width()
|
||||||
|
self.mocked_viewport_obj.width.return_value = 200
|
||||||
|
self.mocked_viewport_obj.height.return_value = 600
|
||||||
|
# Mock image service item
|
||||||
|
service_item = MagicMock()
|
||||||
|
service_item.is_text.return_value = False
|
||||||
|
service_item.is_capable.return_value = False
|
||||||
|
service_item.get_frames.return_value = [{'title': None, 'path': None, 'image': None},
|
||||||
|
{'title': None, 'path': None, 'image': None}]
|
||||||
|
# init ListPreviewWidget and load service item
|
||||||
|
list_preview_widget = ListPreviewWidget(None, 1)
|
||||||
|
list_preview_widget.replace_service_item(service_item, 200, 0)
|
||||||
|
# Change viewport width before forcing a resize
|
||||||
|
self.mocked_viewport_obj.width.return_value = 400
|
||||||
|
|
||||||
|
# WHEN: __recalculate_layout() is called (via screen_size_changed)
|
||||||
|
list_preview_widget.screen_size_changed(1)
|
||||||
|
self.mocked_viewport_obj.height.return_value = 200
|
||||||
|
list_preview_widget.screen_size_changed(1)
|
||||||
|
|
||||||
|
# THEN: resizeRowsToContents() should not be called, while setRowHeight() should be called
|
||||||
|
# twice for each slide.
|
||||||
|
self.assertEquals(mocked_resizeRowsToContents.call_count, 0, 'Should not be called')
|
||||||
|
self.assertEquals(mocked_setRowHeight.call_count, 6, 'Should be called 3 times for each slide')
|
||||||
|
calls = [call(0, 100), call(1, 100), call(0, 150), call(1, 150), call(0, 100), call(1, 100)]
|
||||||
|
mocked_setRowHeight.assert_has_calls(calls)
|
||||||
|
|
||||||
@patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.resizeRowsToContents')
|
@patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.resizeRowsToContents')
|
||||||
@patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.setRowHeight')
|
@patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.setRowHeight')
|
||||||
@patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.cellWidget')
|
@patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.cellWidget')
|
||||||
@ -331,6 +369,41 @@ class TestListPreviewWidget(TestCase):
|
|||||||
# THEN: self.cellWidget(row, 0).children()[1].setMaximumWidth() should be called
|
# THEN: self.cellWidget(row, 0).children()[1].setMaximumWidth() should be called
|
||||||
mocked_cellWidget_child.setMaximumWidth.assert_called_once_with(150)
|
mocked_cellWidget_child.setMaximumWidth.assert_called_once_with(150)
|
||||||
|
|
||||||
|
@patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.resizeRowsToContents')
|
||||||
|
@patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.setRowHeight')
|
||||||
|
@patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.cellWidget')
|
||||||
|
def test_row_resized_setting_changed(self, mocked_cellWidget, mocked_setRowHeight, mocked_resizeRowsToContents):
|
||||||
|
"""
|
||||||
|
Test if "Max height for non-text slides..." enabled while item live, program doesn't crash on row_resized.
|
||||||
|
"""
|
||||||
|
# GIVEN: A setting to adjust "Max height for non-text slides in slide controller",
|
||||||
|
# an image ServiceItem and a ListPreviewWidget.
|
||||||
|
|
||||||
|
# Mock Settings().value('advanced/slide max height')
|
||||||
|
self.mocked_Settings_obj.value.return_value = 0
|
||||||
|
# Mock self.viewport().width()
|
||||||
|
self.mocked_viewport_obj.width.return_value = 200
|
||||||
|
# Mock image service item
|
||||||
|
service_item = MagicMock()
|
||||||
|
service_item.is_text.return_value = False
|
||||||
|
service_item.is_capable.return_value = False
|
||||||
|
service_item.get_frames.return_value = [{'title': None, 'path': None, 'image': None},
|
||||||
|
{'title': None, 'path': None, 'image': None}]
|
||||||
|
# Mock self.cellWidget().children()
|
||||||
|
mocked_cellWidget_obj = MagicMock()
|
||||||
|
mocked_cellWidget_obj.children.return_value = None
|
||||||
|
mocked_cellWidget.return_value = mocked_cellWidget_obj
|
||||||
|
# init ListPreviewWidget and load service item
|
||||||
|
list_preview_widget = ListPreviewWidget(None, 1)
|
||||||
|
list_preview_widget.replace_service_item(service_item, 200, 0)
|
||||||
|
self.mocked_Settings_obj.value.return_value = 100
|
||||||
|
|
||||||
|
# WHEN: row_resized() is called
|
||||||
|
list_preview_widget.row_resized(0, 100, 150)
|
||||||
|
|
||||||
|
# THEN: self.cellWidget(row, 0).children()[1].setMaximumWidth() should fail
|
||||||
|
self.assertRaises(Exception)
|
||||||
|
|
||||||
@patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.selectRow')
|
@patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.selectRow')
|
||||||
@patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.scrollToItem')
|
@patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.scrollToItem')
|
||||||
@patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.item')
|
@patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.item')
|
||||||
|
@ -22,13 +22,13 @@
|
|||||||
"""
|
"""
|
||||||
This module contains tests for the OpenSong song importer.
|
This module contains tests for the OpenSong song importer.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from unittest import TestCase
|
from unittest import TestCase
|
||||||
|
|
||||||
from tests.helpers.songfileimport import SongImportTestHelper
|
|
||||||
from openlp.plugins.songs.lib.importers.opensong import OpenSongImport
|
from openlp.plugins.songs.lib.importers.opensong import OpenSongImport
|
||||||
from openlp.core.common import Registry
|
from openlp.core.common import Registry
|
||||||
|
|
||||||
|
from tests.helpers.songfileimport import SongImportTestHelper
|
||||||
from tests.functional import patch, MagicMock
|
from tests.functional import patch, MagicMock
|
||||||
|
|
||||||
TEST_PATH = os.path.abspath(
|
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.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.file_import([os.path.join(TEST_PATH, 'Amazing Grace2')],
|
||||||
self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json')))
|
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):
|
class TestOpenSongImport(TestCase):
|
||||||
|
@ -52,6 +52,8 @@ class TestSongShowPlusFileImport(SongImportTestHelper):
|
|||||||
self.load_external_result_data(os.path.join(TEST_PATH, 'Beautiful Garden Of Prayer.json')))
|
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.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.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):
|
class TestSongShowPlusImport(TestCase):
|
||||||
|
@ -29,6 +29,7 @@ from unittest import TestCase
|
|||||||
|
|
||||||
from openlp.plugins.songs.lib.importers.opensong import OpenSongImport
|
from openlp.plugins.songs.lib.importers.opensong import OpenSongImport
|
||||||
from openlp.core.common import Registry
|
from openlp.core.common import Registry
|
||||||
|
|
||||||
from tests.functional import patch, MagicMock, call
|
from tests.functional import patch, MagicMock, call
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
@ -36,7 +37,7 @@ log = logging.getLogger(__name__)
|
|||||||
|
|
||||||
class SongImportTestHelper(TestCase):
|
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):
|
def __init__(self, *args, **kwargs):
|
||||||
super(SongImportTestHelper, self).__init__(*args, **kwargs)
|
super(SongImportTestHelper, self).__init__(*args, **kwargs)
|
||||||
|
@ -53,4 +53,4 @@
|
|||||||
<key_line></key_line>
|
<key_line></key_line>
|
||||||
<time_sig></time_sig>
|
<time_sig></time_sig>
|
||||||
<style index="default_style"></style>
|
<style index="default_style"></style>
|
||||||
</song>
|
</song>
|
||||||
|
56
tests/resources/opensongsongs/Amazing Grace with bad CCLI
Normal file
56
tests/resources/opensongsongs/Amazing Grace with bad CCLI
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<song>
|
||||||
|
<title>Amazing Grace (Demonstration)</title>
|
||||||
|
<author>John Newton, Edwin Excell & 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>
|
@ -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"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
38
tests/resources/songshowplussongs/cleanse-me.json
Normal file
38
tests/resources/songshowplussongs/cleanse-me.json
Normal 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"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
BIN
tests/resources/songshowplussongs/cleanse-me.sbsong
Normal file
BIN
tests/resources/songshowplussongs/cleanse-me.sbsong
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user