forked from openlp/openlp
merged
This commit is contained in:
commit
53dc52c7c8
@ -129,11 +129,11 @@ class OpenLP(QtGui.QApplication):
|
||||
screens = ScreenList()
|
||||
# Decide how many screens we have and their size
|
||||
for screen in xrange(0, self.desktop().numScreens()):
|
||||
size = self.desktop().screenGeometry(screen);
|
||||
screens.add_screen({u'number': screen,
|
||||
u'size': self.desktop().availableGeometry(screen),
|
||||
u'size': size,
|
||||
u'primary': (self.desktop().primaryScreen() == screen)})
|
||||
log.info(u'Screen %d found with resolution %s',
|
||||
screen, self.desktop().availableGeometry(screen))
|
||||
log.info(u'Screen %d found with resolution %s', screen, size)
|
||||
# start the main app window
|
||||
self.mainWindow = MainWindow(screens, app_version)
|
||||
self.mainWindow.show()
|
||||
|
@ -39,40 +39,40 @@ log = logging.getLogger(__name__)
|
||||
html_expands = []
|
||||
|
||||
html_expands.append({u'desc':u'Red', u'start tag':u'{r}', \
|
||||
u'start html':u'<font color=red>', \
|
||||
u'end tag':u'{/r}', u'end html':u'</font>', \
|
||||
u'start html':u'<span style="-webkit-text-fill-color:red">', \
|
||||
u'end tag':u'{/r}', u'end html':u'</span>', \
|
||||
u'protected':False})
|
||||
html_expands.append({u'desc':u'Black', u'start tag':u'{b}', \
|
||||
u'start html':u'<font color=black>', \
|
||||
u'end tag':u'{/b}', u'end html':u'</font>', \
|
||||
u'start html':u'<span style="-webkit-text-fill-color:black">', \
|
||||
u'end tag':u'{/b}', u'end html':u'</span>', \
|
||||
u'protected':False})
|
||||
html_expands.append({u'desc':u'Blue', u'start tag':u'{bl}', \
|
||||
u'start html':u'<font color=blue>', \
|
||||
u'end tag':u'{/bl}', u'end html':u'</font>', \
|
||||
u'start html':u'<span style="-webkit-text-fill-color:blue">', \
|
||||
u'end tag':u'{/bl}', u'end html':u'</span>', \
|
||||
u'protected':False})
|
||||
html_expands.append({u'desc':u'Yellow', u'start tag':u'{y}', \
|
||||
u'start html':u'<font color=yellow>', \
|
||||
u'end tag':u'{/y}', u'end html':u'</font>', \
|
||||
u'start html':u'<span style="-webkit-text-fill-color:yellow">', \
|
||||
u'end tag':u'{/y}', u'end html':u'</span>', \
|
||||
u'protected':False})
|
||||
html_expands.append({u'desc':u'Green', u'start tag':u'{g}', \
|
||||
u'start html':u'<font color=green>', \
|
||||
u'end tag':u'{/g}', u'end html':u'</font>', \
|
||||
u'start html':u'<span style="-webkit-text-fill-color:green">', \
|
||||
u'end tag':u'{/g}', u'end html':u'</span>', \
|
||||
u'protected':False})
|
||||
html_expands.append({u'desc':u'Pink', u'start tag':u'{pk}', \
|
||||
u'start html':u'<font color=#CC33CC>', \
|
||||
u'end tag':u'{/pk}', u'end html':u'</font>', \
|
||||
u'start html':u'<span style="-webkit-text-fill-color:#CC33CC">', \
|
||||
u'end tag':u'{/pk}', u'end html':u'</span>', \
|
||||
u'protected':False})
|
||||
html_expands.append({u'desc':u'Orange', u'start tag':u'{o}', \
|
||||
u'start html':u'<font color=#CC0033>', \
|
||||
u'end tag':u'{/o}', u'end html':u'</font>', \
|
||||
u'start html':u'<span style="-webkit-text-fill-color:#CC0033">', \
|
||||
u'end tag':u'{/o}', u'end html':u'</span>', \
|
||||
u'protected':False})
|
||||
html_expands.append({u'desc':u'Purple', u'start tag':u'{pp}', \
|
||||
u'start html':u'<font color=#9900FF>', \
|
||||
u'end tag':u'{/pp}', u'end html':u'</font>', \
|
||||
u'start html':u'<span style="-webkit-text-fill-color:#9900FF">', \
|
||||
u'end tag':u'{/pp}', u'end html':u'</span>', \
|
||||
u'protected':False})
|
||||
html_expands.append({u'desc':u'White', u'start tag':u'{w}', \
|
||||
u'start html':u'<font color=white>', \
|
||||
u'end tag':u'{/w}', u'end html':u'</font>', \
|
||||
u'start html':u'<span style="-webkit-text-fill-color:white">', \
|
||||
u'end tag':u'{/w}', u'end html':u'</span>', \
|
||||
u'protected':False})
|
||||
html_expands.append({u'desc':u'Superscript', u'start tag':u'{su}', \
|
||||
u'start html':u'<sup>', \
|
||||
@ -316,6 +316,7 @@ def expand_tags(text):
|
||||
text = text.replace(tag[u'end tag'], tag[u'end html'])
|
||||
return text
|
||||
|
||||
from spelltextedit import SpellTextEdit
|
||||
from eventreceiver import Receiver
|
||||
from settingsmanager import SettingsManager
|
||||
from plugin import PluginStatus, Plugin
|
||||
@ -324,7 +325,8 @@ from settingstab import SettingsTab
|
||||
from serviceitem import ServiceItem
|
||||
from serviceitem import ServiceItemType
|
||||
from serviceitem import ItemCapabilities
|
||||
from htmlbuilder import build_html
|
||||
from htmlbuilder import build_html, build_lyrics_format_css, \
|
||||
build_lyrics_outline_css
|
||||
from toolbar import OpenLPToolbar
|
||||
from dockwidget import OpenLPDockWidget
|
||||
from theme import ThemeLevel, ThemeXML
|
||||
|
@ -24,8 +24,13 @@
|
||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||
###############################################################################
|
||||
|
||||
import logging
|
||||
from PyQt4 import QtWebKit
|
||||
|
||||
from openlp.core.lib import image_to_byte
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
HTMLSRC = u"""
|
||||
<html>
|
||||
<head>
|
||||
@ -35,11 +40,12 @@ HTMLSRC = u"""
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
body {
|
||||
background-color: black;
|
||||
%s;
|
||||
}
|
||||
.dim {
|
||||
.size {
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
@ -51,6 +57,9 @@ body {
|
||||
background-color: black;
|
||||
display: none;
|
||||
}
|
||||
#image {
|
||||
z-index:1;
|
||||
}
|
||||
#video {
|
||||
z-index:2;
|
||||
}
|
||||
@ -136,8 +145,13 @@ body {
|
||||
}
|
||||
document.getElementById('black').style.display = black;
|
||||
document.getElementById('lyricsmain').style.visibility = lyrics;
|
||||
document.getElementById('lyricsoutline').style.visibility = lyrics;
|
||||
document.getElementById('lyricsshadow').style.visibility = lyrics;
|
||||
document.getElementById('image').style.visibility = lyrics;
|
||||
outline = document.getElementById('lyricsoutline')
|
||||
if(outline!=null)
|
||||
outline.style.visibility = lyrics;
|
||||
shadow = document.getElementById('lyricsshadow')
|
||||
if(shadow!=null)
|
||||
shadow.style.visibility = lyrics;
|
||||
document.getElementById('footer').style.visibility = lyrics;
|
||||
var vid = document.getElementById('video');
|
||||
if(vid.src != ''){
|
||||
@ -156,7 +170,7 @@ body {
|
||||
return 0;
|
||||
}
|
||||
if(position == ''){
|
||||
position = window.getComputedStyle(text, '').verticalAlign;
|
||||
position = getComputedStyle(text, '').verticalAlign;
|
||||
}
|
||||
switch(position)
|
||||
{
|
||||
@ -181,111 +195,62 @@ body {
|
||||
}
|
||||
|
||||
function show_text(newtext){
|
||||
var text1 = document.getElementById('lyricsmain');
|
||||
var texto1 = document.getElementById('lyricsoutline');
|
||||
var texts1 = document.getElementById('lyricsshadow');
|
||||
if(!transition){
|
||||
text1.innerHTML = newtext;
|
||||
texto1.innerHTML = newtext;
|
||||
texts1.innerHTML = newtext;
|
||||
return;
|
||||
}
|
||||
var text2 = document.getElementById('lyricsmain2');
|
||||
var texto2 = document.getElementById('lyricsoutline2');
|
||||
var texts2 = document.getElementById('lyricsshadow2');
|
||||
if((text2.style.opacity == '')||(parseFloat(text2.style.opacity) < 0.5))
|
||||
{
|
||||
text2.innerHTML = text1.innerHTML;
|
||||
text2.style.opacity = text1.style.opacity;
|
||||
texto2.innerHTML = text1.innerHTML;
|
||||
texto2.style.opacity = text1.style.opacity;
|
||||
texts2.innerHTML = text1.innerHTML;
|
||||
texts2.style.opacity = text1.style.opacity;
|
||||
}
|
||||
text1.style.opacity = 0;
|
||||
text1.innerHTML = newtext;
|
||||
texto1.style.opacity = 0;
|
||||
texto1.innerHTML = newtext;
|
||||
texts1.style.opacity = 0;
|
||||
texts1.innerHTML = newtext;
|
||||
// For performance reasons, we'll not animate the shadow for now
|
||||
texts2.style.opacity = 0;
|
||||
if(timer != null)
|
||||
clearTimeout(timer);
|
||||
timer = setTimeout('text_fade()', 50);
|
||||
text_fade('lyricsmain', newtext);
|
||||
text_fade('lyricsoutline', newtext);
|
||||
text_fade('lyricsshadow', newtext);
|
||||
if(text_opacity()==1) return;
|
||||
timer = setTimeout(function(){
|
||||
show_text(newtext);
|
||||
}, 100);
|
||||
}
|
||||
|
||||
function text_fade(){
|
||||
var text1 = document.getElementById('lyricsmain');
|
||||
var texto1 = document.getElementById('lyricsoutline');
|
||||
var texts1 = document.getElementById('lyricsshadow');
|
||||
var text2 = document.getElementById('lyricsmain2');
|
||||
var texto2 = document.getElementById('lyricsoutline2');
|
||||
var texts2 = document.getElementById('lyricsshadow2');
|
||||
if(parseFloat(text1.style.opacity) < 1){
|
||||
text1.style.opacity = parseFloat(text1.style.opacity) + 0.1;
|
||||
texto1.style.opacity = parseFloat(texto1.style.opacity) + 0.1;
|
||||
// Don't animate shadow (performance)
|
||||
//texts1.style.opacity = parseFloat(texts1.style.opacity) + 0.1;
|
||||
function text_fade(id, newtext){
|
||||
/*
|
||||
Using -webkit-transition: opacity 1s linear; would have been preferred
|
||||
but it isn't currently quick enough when animating multiple layers of
|
||||
large areas of large text. Therefore do it manually as best we can.
|
||||
Hopefully in the future we can revisit and do more interesting
|
||||
transitions using -webkit-transition and -webkit-transform.
|
||||
However we need to ensure interrupted transitions (quickly change 2
|
||||
slides) still looks pretty and is zippy.
|
||||
*/
|
||||
var text = document.getElementById(id);
|
||||
if(text==null) return;
|
||||
if(!transition){
|
||||
text.innerHTML = newtext;
|
||||
return;
|
||||
}
|
||||
if(parseFloat(text2.style.opacity) > 0){
|
||||
text2.style.opacity = parseFloat(text2.style.opacity) - 0.1;
|
||||
texto2.style.opacity = parseFloat(texto2.style.opacity) - 0.1;
|
||||
// Don't animate shadow (performance)
|
||||
//texts2.style.opacity = parseFloat(texts2.style.opacity) - 0.1;
|
||||
}
|
||||
if((parseFloat(text1.style.opacity) < 1) ||
|
||||
(parseFloat(text2.style.opacity) > 0)){
|
||||
t = setTimeout('text_fade()', 50);
|
||||
if(newtext==text.innerHTML){
|
||||
text.style.opacity = parseFloat(text.style.opacity) + 0.3;
|
||||
if(text.style.opacity>0.7)
|
||||
text.style.opacity = 1;
|
||||
} else {
|
||||
text1.style.opacity = 1;
|
||||
texto1.style.opacity = 1;
|
||||
texts1.style.opacity = 1;
|
||||
text2.style.opacity = 0;
|
||||
texto2.style.opacity = 0;
|
||||
texts2.style.opacity = 0;
|
||||
text.style.opacity = parseFloat(text.style.opacity) - 0.3;
|
||||
if(text.style.opacity<=0.1){
|
||||
text.innerHTML = newtext;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function text_opacity(){
|
||||
var text = document.getElementById('lyricsmain');
|
||||
return getComputedStyle(text, '').opacity;
|
||||
}
|
||||
|
||||
function show_text_complete(){
|
||||
return (document.getElementById('lyricsmain').style.opacity == 1);
|
||||
return (text_opacity()==1);
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<!--
|
||||
Using tables, rather than div's to make use of the vertical-align style that
|
||||
doesn't work on div's. This avoids the need to do positioning manually which
|
||||
could get messy when changing verses esp. with transitions
|
||||
|
||||
Would prefer to use a single table and make use of -webkit-text-fill-color
|
||||
-webkit-text-stroke and text-shadow styles, but they have problems working/
|
||||
co-operating in qwebkit. https://bugs.webkit.org/show_bug.cgi?id=43187
|
||||
Therefore one table for text, one for outline and one for shadow.
|
||||
-->
|
||||
<table class="lyricstable lyricscommon">
|
||||
<tr><td id="lyricsmain" class="lyrics"></td></tr>
|
||||
</table>
|
||||
<table class="lyricsoutlinetable lyricscommon">
|
||||
<tr><td id="lyricsoutline" class="lyricsoutline lyrics"></td></tr>
|
||||
</table>
|
||||
<table class="lyricsshadowtable lyricscommon">
|
||||
<tr><td id="lyricsshadow" class="lyricsshadow lyrics"></td></tr>
|
||||
</table>
|
||||
<table class="lyricstable lyricscommon">
|
||||
<tr><td id="lyricsmain2" class="lyrics"></td></tr>
|
||||
</table>
|
||||
<table class="lyricsoutlinetable lyricscommon">
|
||||
<tr><td id="lyricsoutline2" class="lyricsoutline lyrics"></td></tr>
|
||||
</table>
|
||||
<table class="lyricsshadowtable lyricscommon">
|
||||
<tr><td id="lyricsshadow2" class="lyricsshadow lyrics"></td></tr>
|
||||
</table>
|
||||
<div id="alert" style="visibility:hidden;"></div>
|
||||
<img id="image" class="size" src="%s" />
|
||||
<video id="video" class="size"></video>
|
||||
%s
|
||||
<div id="footer" class="footer"></div>
|
||||
<video class="dim" id="video"></video>
|
||||
<div class="dim" id="black"></div>
|
||||
<img class="dim" id="image" src="%s" />
|
||||
<div id="black" class="size"></div>
|
||||
<div id="alert" style="visibility:hidden;"></div>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
@ -300,92 +265,245 @@ def build_html(item, screen, alert, islive):
|
||||
Current display information
|
||||
`alert`
|
||||
Alert display display information
|
||||
`islive`
|
||||
Item is going live, rather than preview/theme building
|
||||
"""
|
||||
width = screen[u'size'].width()
|
||||
height = screen[u'size'].height()
|
||||
theme = item.themedata
|
||||
webkitvers = webkit_version()
|
||||
if item.bg_frame:
|
||||
image = u'data:image/png;base64,%s' % image_to_byte(item.bg_frame)
|
||||
else:
|
||||
image = u''
|
||||
html = HTMLSRC % (width, height,
|
||||
build_alert(alert, width),
|
||||
build_footer(item),
|
||||
build_lyrics(item),
|
||||
html = HTMLSRC % (build_background_css(item, width, height),
|
||||
width, height,
|
||||
build_alert_css(alert, width),
|
||||
build_footer_css(item),
|
||||
build_lyrics_css(item, webkitvers),
|
||||
u'true' if theme and theme.display_slideTransition and islive \
|
||||
else u'false',
|
||||
image)
|
||||
image,
|
||||
build_lyrics_html(item, webkitvers))
|
||||
return html
|
||||
|
||||
def build_lyrics(item):
|
||||
def webkit_version():
|
||||
"""
|
||||
Build the video display div
|
||||
Return the Webkit version in use.
|
||||
Note method added relatively recently, so return 0 if prior to this
|
||||
"""
|
||||
try:
|
||||
webkitvers = float(QtWebKit.qWebKitVersion())
|
||||
log.debug(u'Webkit version = %s' % webkitvers)
|
||||
except AttributeError:
|
||||
webkitvers = 0
|
||||
return webkitvers
|
||||
|
||||
def build_background_css(item, width, height):
|
||||
"""
|
||||
Build the background css
|
||||
|
||||
`item`
|
||||
Service Item containing theme and location information
|
||||
|
||||
"""
|
||||
width = int(width) / 2
|
||||
theme = item.themedata
|
||||
background = u'background-color: black'
|
||||
if theme:
|
||||
if theme.background_type == u'solid':
|
||||
background = u'background-color: %s' % theme.background_color
|
||||
else:
|
||||
if theme.background_direction == u'horizontal':
|
||||
background = \
|
||||
u'background: ' \
|
||||
u'-webkit-gradient(linear, left top, left bottom, ' \
|
||||
'from(%s), to(%s))' % (theme.background_startColor,
|
||||
theme.background_endColor)
|
||||
elif theme.background_direction == u'vertical':
|
||||
background = \
|
||||
u'background: -webkit-gradient(linear, left top, ' \
|
||||
u'right top, from(%s), to(%s))' % \
|
||||
(theme.background_startColor, theme.background_endColor)
|
||||
else:
|
||||
background = \
|
||||
u'background: -webkit-gradient(radial, %s 50%%, 100, %s ' \
|
||||
u'50%%, %s, from(%s), to(%s))' % (width, width, width,
|
||||
theme.background_startColor, theme.background_endColor)
|
||||
return background
|
||||
|
||||
def build_lyrics_css(item, webkitvers):
|
||||
"""
|
||||
Build the lyrics display css
|
||||
|
||||
`item`
|
||||
Service Item containing theme and location information
|
||||
|
||||
`webkitvers`
|
||||
The version of qtwebkit we're using
|
||||
|
||||
"""
|
||||
style = """
|
||||
.lyricscommon { position: absolute; %s }
|
||||
.lyricstable { z-index:4; %s }
|
||||
.lyricsoutlinetable { z-index:3; %s }
|
||||
.lyricsshadowtable { z-index:2; %s }
|
||||
.lyrics { %s }
|
||||
.lyricsoutline { %s }
|
||||
.lyricsshadow { %s }
|
||||
.lyricstable {
|
||||
z-index:4;
|
||||
position: absolute;
|
||||
display: table;
|
||||
%s
|
||||
}
|
||||
.lyricscell {
|
||||
display:table-cell;
|
||||
word-wrap: break-word;
|
||||
%s
|
||||
}
|
||||
.lyricsmain {
|
||||
%s
|
||||
}
|
||||
.lyricsoutline {
|
||||
%s
|
||||
}
|
||||
.lyricsshadow {
|
||||
%s
|
||||
}
|
||||
"""
|
||||
theme = item.themedata
|
||||
lyricscommon = u''
|
||||
lyricstable = u''
|
||||
outlinetable = u''
|
||||
shadowtable = u''
|
||||
lyrics = u''
|
||||
outline = u'display: none;'
|
||||
shadow = u'display: none;'
|
||||
if theme:
|
||||
lyricscommon = u'width: %spx; height: %spx; word-wrap: break-word; ' \
|
||||
u'font-family: %s; font-size: %spt; color: %s; line-height: %d%%;' \
|
||||
% (item.main.width(), item.main.height(), theme.font_main_name,
|
||||
theme.font_main_proportion, theme.font_main_color,
|
||||
100 + int(theme.font_main_line_adjustment))
|
||||
lyricsmain = u''
|
||||
outline = u''
|
||||
shadow = u''
|
||||
if theme and item.main:
|
||||
lyricstable = u'left: %spx; top: %spx;' % \
|
||||
(item.main.x(), item.main.y())
|
||||
outlinetable = u'left: %spx; top: %spx;' % \
|
||||
(item.main.x(), item.main.y())
|
||||
shadowtable = u'left: %spx; top: %spx;' % \
|
||||
(item.main.x() + float(theme.display_shadow_size),
|
||||
item.main.y() + float(theme.display_shadow_size))
|
||||
align = u''
|
||||
if theme.display_horizontalAlign == 2:
|
||||
align = u'text-align:center;'
|
||||
elif theme.display_horizontalAlign == 1:
|
||||
align = u'text-align:right;'
|
||||
lyrics = build_lyrics_format_css(theme, item.main.width(),
|
||||
item.main.height())
|
||||
# For performance reasons we want to show as few DIV's as possible,
|
||||
# especially when animating/transitions.
|
||||
# However some bugs in older versions of qtwebkit mean we need to
|
||||
# perform workarounds and add extra divs. Only do these when needed.
|
||||
#
|
||||
# Before 533.3 the webkit-text-fill colour wasn't displayed, only the
|
||||
# stroke (outline) color. So put stroke layer underneath the main text.
|
||||
#
|
||||
# Before 534.4 the webkit-text-stroke was sometimes out of alignment
|
||||
# with the fill, or normal text. letter-spacing=1 is workaround
|
||||
# https://bugs.webkit.org/show_bug.cgi?id=44403
|
||||
#
|
||||
# Before 534.4 the text-shadow didn't get displayed when
|
||||
# webkit-text-stroke was used. So use an offset text layer underneath.
|
||||
# https://bugs.webkit.org/show_bug.cgi?id=19728
|
||||
if webkitvers >= 533.3:
|
||||
lyricsmain += build_lyrics_outline_css(theme)
|
||||
else:
|
||||
align = u'text-align:left;'
|
||||
if theme.display_verticalAlign == 2:
|
||||
valign = u'vertical-align:bottom;'
|
||||
elif theme.display_verticalAlign == 1:
|
||||
valign = u'vertical-align:middle;'
|
||||
else:
|
||||
valign = u'vertical-align:top;'
|
||||
lyrics = u'%s %s' % (align, valign)
|
||||
if theme.display_outline:
|
||||
lyricscommon += u' letter-spacing: 1px;'
|
||||
outline = u'-webkit-text-stroke: %sem %s; ' % \
|
||||
(float(theme.display_outline_size) / 16,
|
||||
theme.display_outline_color)
|
||||
if theme.display_shadow:
|
||||
shadow = u'-webkit-text-stroke: %sem %s; ' \
|
||||
u'-webkit-text-fill-color: %s; ' % \
|
||||
(float(theme.display_outline_size) / 16,
|
||||
theme.display_shadow_color, theme.display_shadow_color)
|
||||
else:
|
||||
if theme.display_shadow:
|
||||
shadow = u'color: %s;' % (theme.display_shadow_color)
|
||||
lyrics_html = style % (lyricscommon, lyricstable, outlinetable,
|
||||
shadowtable, lyrics, outline, shadow)
|
||||
return lyrics_html
|
||||
outline = build_lyrics_outline_css(theme)
|
||||
if theme.display_shadow:
|
||||
if theme.display_outline and webkitvers < 534.3:
|
||||
shadow = u'padding-left: %spx; padding-top: %spx ' % \
|
||||
(theme.display_shadow_size, theme.display_shadow_size)
|
||||
shadow += build_lyrics_outline_css(theme, True)
|
||||
else:
|
||||
lyricsmain += u' text-shadow: %s %spx %spx;' % \
|
||||
(theme.display_shadow_color, theme.display_shadow_size,
|
||||
theme.display_shadow_size)
|
||||
lyrics_css = style % (lyricstable, lyrics, lyricsmain, outline, shadow)
|
||||
return lyrics_css
|
||||
|
||||
def build_footer(item):
|
||||
def build_lyrics_outline_css(theme, is_shadow=False):
|
||||
"""
|
||||
Build the css which controls the theme outline
|
||||
Also used by renderer for splitting verses
|
||||
|
||||
`theme`
|
||||
Object containing theme information
|
||||
|
||||
`is_shadow`
|
||||
If true, use the shadow colors instead
|
||||
"""
|
||||
if theme.display_outline:
|
||||
size = float(theme.display_outline_size) / 16
|
||||
if is_shadow:
|
||||
fill_color = theme.display_shadow_color
|
||||
outline_color = theme.display_shadow_color
|
||||
else:
|
||||
fill_color = theme.font_main_color
|
||||
outline_color = theme.display_outline_color
|
||||
return u' -webkit-text-stroke: %sem %s; ' \
|
||||
u'-webkit-text-fill-color: %s; ' % (size, outline_color, fill_color)
|
||||
else:
|
||||
return u''
|
||||
|
||||
def build_lyrics_format_css(theme, width, height):
|
||||
"""
|
||||
Build the css which controls the theme format
|
||||
Also used by renderer for splitting verses
|
||||
|
||||
`theme`
|
||||
Object containing theme information
|
||||
|
||||
`width`
|
||||
Width of the lyrics block
|
||||
|
||||
`height`
|
||||
Height of the lyrics block
|
||||
|
||||
"""
|
||||
if theme.display_horizontalAlign == 2:
|
||||
align = u'center'
|
||||
elif theme.display_horizontalAlign == 1:
|
||||
align = u'right'
|
||||
else:
|
||||
align = u'left'
|
||||
if theme.display_verticalAlign == 2:
|
||||
valign = u'bottom'
|
||||
elif theme.display_verticalAlign == 1:
|
||||
valign = u'middle'
|
||||
else:
|
||||
valign = u'top'
|
||||
lyrics = u'white-space:pre-wrap; word-wrap: break-word; ' \
|
||||
'text-align: %s; vertical-align: %s; font-family: %s; ' \
|
||||
'font-size: %spt; color: %s; line-height: %d%%; ' \
|
||||
'margin:0; padding:0; width: %spx; height: %spx; ' % \
|
||||
(align, valign, theme.font_main_name, theme.font_main_proportion,
|
||||
theme.font_main_color, 100 + int(theme.font_main_line_adjustment),
|
||||
width, height)
|
||||
if theme.display_outline:
|
||||
if webkit_version() < 534.3:
|
||||
lyrics += u' letter-spacing: 1px;'
|
||||
if theme.font_main_italics:
|
||||
lyrics += u' font-style:italic; '
|
||||
if theme.font_main_weight == u'Bold':
|
||||
lyrics += u' font-weight:bold; '
|
||||
return lyrics
|
||||
|
||||
def build_lyrics_html(item, webkitvers):
|
||||
"""
|
||||
Build the HTML required to show the lyrics
|
||||
|
||||
`item`
|
||||
Service Item containing theme and location information
|
||||
|
||||
`webkitvers`
|
||||
The version of qtwebkit we're using
|
||||
"""
|
||||
# Bugs in some versions of QtWebKit mean we sometimes need additional
|
||||
# divs for outline and shadow, since the CSS doesn't work.
|
||||
# To support vertical alignment middle and bottom, nested div's using
|
||||
# display:table/display:table-cell are required for each lyric block.
|
||||
lyrics = u''
|
||||
theme = item.themedata
|
||||
if webkitvers < 534.4 and theme and theme.display_outline:
|
||||
lyrics += u'<div class="lyricstable">' \
|
||||
u'<div id="lyricsshadow" style="opacity:1" ' \
|
||||
u'class="lyricscell lyricsshadow"></div></div>'
|
||||
if webkitvers < 533.3:
|
||||
lyrics += u'<div class="lyricstable">' \
|
||||
u'<div id="lyricsoutline" style="opacity:1" ' \
|
||||
u'class="lyricscell lyricsoutline"></div></div>'
|
||||
lyrics += u'<div class="lyricstable">' \
|
||||
u'<div id="lyricsmain" style="opacity:1" ' \
|
||||
u'class="lyricscell lyricsmain"></div></div>'
|
||||
return lyrics
|
||||
|
||||
def build_footer_css(item):
|
||||
"""
|
||||
Build the display of the item footer
|
||||
|
||||
@ -403,7 +521,7 @@ def build_footer(item):
|
||||
text-align: %s;
|
||||
"""
|
||||
theme = item.themedata
|
||||
if not theme:
|
||||
if not theme or not item.footer:
|
||||
return u''
|
||||
if theme.display_horizontalAlign == 2:
|
||||
align = u'center'
|
||||
@ -416,7 +534,7 @@ def build_footer(item):
|
||||
theme.font_footer_proportion, theme.font_footer_color, align)
|
||||
return lyrics_html
|
||||
|
||||
def build_alert(alertTab, width):
|
||||
def build_alert_css(alertTab, width):
|
||||
"""
|
||||
Build the display of the footer
|
||||
|
||||
@ -424,7 +542,7 @@ def build_alert(alertTab, width):
|
||||
Details from the Alert tab for fonts etc
|
||||
"""
|
||||
style = """
|
||||
width: %s;
|
||||
width: %spx;
|
||||
vertical-align: %s;
|
||||
font-family: %s;
|
||||
font-size: %spt;
|
||||
|
@ -29,9 +29,10 @@ format it for the output display.
|
||||
"""
|
||||
import logging
|
||||
|
||||
from PyQt4 import QtGui, QtCore
|
||||
from PyQt4 import QtGui, QtCore, QtWebKit
|
||||
|
||||
from openlp.core.lib import resize_image, expand_tags
|
||||
from openlp.core.lib import resize_image, expand_tags, \
|
||||
build_lyrics_format_css, build_lyrics_outline_css
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@ -47,26 +48,13 @@ class Renderer(object):
|
||||
Initialise the renderer.
|
||||
"""
|
||||
self._rect = None
|
||||
self._debug = False
|
||||
self._display_shadow_size_footer = 0
|
||||
self._display_outline_size_footer = 0
|
||||
self.theme_name = None
|
||||
self._theme = None
|
||||
self._bg_image_filename = None
|
||||
self.frame = None
|
||||
self.frame_opaque = None
|
||||
self.bg_frame = None
|
||||
self.bg_image = None
|
||||
|
||||
def set_debug(self, debug):
|
||||
"""
|
||||
Set the debug mode of the renderer.
|
||||
|
||||
``debug``
|
||||
The debug mode.
|
||||
"""
|
||||
self._debug = debug
|
||||
|
||||
def set_theme(self, theme):
|
||||
"""
|
||||
Set the theme to be used.
|
||||
@ -82,21 +70,11 @@ class Renderer(object):
|
||||
self.theme_name = theme.theme_name
|
||||
if theme.background_type == u'image':
|
||||
if theme.background_filename:
|
||||
self.set_bg_image(theme.background_filename)
|
||||
|
||||
def set_bg_image(self, filename):
|
||||
"""
|
||||
Set a background image.
|
||||
|
||||
``filename``
|
||||
The name of the image file.
|
||||
"""
|
||||
log.debug(u'set bg image %s', filename)
|
||||
self._bg_image_filename = unicode(filename)
|
||||
if self.frame:
|
||||
self.bg_image = resize_image(self._bg_image_filename,
|
||||
self.frame.width(),
|
||||
self.frame.height())
|
||||
self._bg_image_filename = unicode(theme.background_filename)
|
||||
if self.frame:
|
||||
self.bg_image = resize_image(self._bg_image_filename,
|
||||
self.frame.width(),
|
||||
self.frame.height())
|
||||
|
||||
def set_text_rectangle(self, rect_main, rect_footer):
|
||||
"""
|
||||
@ -112,7 +90,7 @@ class Renderer(object):
|
||||
self._rect = rect_main
|
||||
self._rect_footer = rect_footer
|
||||
|
||||
def set_frame_dest(self, frame_width, frame_height, preview=False):
|
||||
def set_frame_dest(self, frame_width, frame_height):
|
||||
"""
|
||||
Set the size of the slide.
|
||||
|
||||
@ -122,11 +100,7 @@ class Renderer(object):
|
||||
``frame_height``
|
||||
The height of the slide.
|
||||
|
||||
``preview``
|
||||
Defaults to *False*. Whether or not to generate a preview.
|
||||
"""
|
||||
if preview:
|
||||
self.bg_frame = None
|
||||
log.debug(u'set frame dest (frame) w %d h %d', frame_width,
|
||||
frame_height)
|
||||
self.frame = QtGui.QImage(frame_width, frame_height,
|
||||
@ -134,8 +108,17 @@ class Renderer(object):
|
||||
if self._bg_image_filename and not self.bg_image:
|
||||
self.bg_image = resize_image(self._bg_image_filename,
|
||||
self.frame.width(), self.frame.height())
|
||||
if self.bg_frame is None:
|
||||
self._generate_background_frame()
|
||||
if self._theme.background_type == u'image':
|
||||
self.bg_frame = QtGui.QImage(self.frame.width(),
|
||||
self.frame.height(), QtGui.QImage.Format_ARGB32_Premultiplied)
|
||||
painter = QtGui.QPainter()
|
||||
painter.begin(self.bg_frame)
|
||||
painter.fillRect(self.frame.rect(), QtCore.Qt.black)
|
||||
if self.bg_image:
|
||||
painter.drawImage(0, 0, self.bg_image)
|
||||
painter.end()
|
||||
else:
|
||||
self.bg_frame = None
|
||||
|
||||
def format_slide(self, words, line_break):
|
||||
"""
|
||||
@ -156,91 +139,33 @@ class Renderer(object):
|
||||
lines = verse.split(u'\n')
|
||||
for line in lines:
|
||||
text.append(line)
|
||||
doc = QtGui.QTextDocument()
|
||||
doc.setPageSize(QtCore.QSizeF(self._rect.width(), self._rect.height()))
|
||||
df = doc.defaultFont()
|
||||
df.setPixelSize(self._theme.font_main_proportion)
|
||||
df.setFamily(self._theme.font_main_name)
|
||||
main_weight = 50
|
||||
if self._theme.font_main_weight == u'Bold':
|
||||
main_weight = 75
|
||||
df.setWeight(main_weight)
|
||||
doc.setDefaultFont(df)
|
||||
layout = doc.documentLayout()
|
||||
web = QtWebKit.QWebView()
|
||||
web.resize(self._rect.width(), self._rect.height())
|
||||
web.setVisible(False)
|
||||
frame = web.page().mainFrame()
|
||||
# Adjust width and height to account for shadow. outline done in css
|
||||
width = self._rect.width() - int(self._theme.display_shadow_size)
|
||||
height = self._rect.height() - int(self._theme.display_shadow_size)
|
||||
shell = u'<html><head><style>#main {%s %s}</style><body>' \
|
||||
u'<div id="main">' % \
|
||||
(build_lyrics_format_css(self._theme, width, height),
|
||||
build_lyrics_outline_css(self._theme))
|
||||
formatted = []
|
||||
if self._theme.font_main_weight == u'Bold' and \
|
||||
self._theme.font_main_italics:
|
||||
shell = u'{p}{st}{it}%s{/it}{/st}{/p}'
|
||||
elif self._theme.font_main_weight == u'Bold' and \
|
||||
not self._theme.font_main_italics:
|
||||
shell = u'{p}{st}%s{/st}{/p}'
|
||||
elif self._theme.font_main_italics:
|
||||
shell = u'{p}{it}%s{/it}{/p}'
|
||||
else:
|
||||
shell = u'{p}%s{/p}'
|
||||
temp_text = u''
|
||||
old_html_text = u''
|
||||
html_text = u''
|
||||
styled_text = u''
|
||||
js_height = 'document.getElementById("main").scrollHeight'
|
||||
for line in text:
|
||||
# mark line ends
|
||||
temp_text = temp_text + line + line_end
|
||||
html_text = shell % expand_tags(temp_text)
|
||||
doc.setHtml(html_text)
|
||||
# Text too long so gone to next mage
|
||||
if layout.pageCount() != 1:
|
||||
formatted.append(shell % old_html_text)
|
||||
temp_text = line
|
||||
old_html_text = temp_text
|
||||
formatted.append(shell % old_html_text)
|
||||
styled_line = expand_tags(line) + line_end
|
||||
styled_text += styled_line
|
||||
html = shell + styled_text + u'</div></body></html>'
|
||||
web.setHtml(html)
|
||||
# Text too long so go to next page
|
||||
text_height = int(frame.evaluateJavaScript(js_height).toString())
|
||||
if text_height > height:
|
||||
formatted.append(html_text)
|
||||
html_text = u''
|
||||
styled_text = styled_line
|
||||
html_text += line + line_end
|
||||
formatted.append(html_text)
|
||||
log.debug(u'format_slide - End')
|
||||
return formatted
|
||||
|
||||
def _generate_background_frame(self):
|
||||
"""
|
||||
Generate a background frame to the same size as the frame to be used.
|
||||
Results are cached for performance reasons.
|
||||
"""
|
||||
assert(self._theme)
|
||||
self.bg_frame = QtGui.QImage(self.frame.width(),
|
||||
self.frame.height(), QtGui.QImage.Format_ARGB32_Premultiplied)
|
||||
log.debug(u'render background %s start', self._theme.background_type)
|
||||
painter = QtGui.QPainter()
|
||||
painter.begin(self.bg_frame)
|
||||
if self._theme.background_type == u'solid':
|
||||
painter.fillRect(self.frame.rect(),
|
||||
QtGui.QColor(self._theme.background_color))
|
||||
elif self._theme.background_type == u'gradient':
|
||||
# gradient
|
||||
gradient = None
|
||||
if self._theme.background_direction == u'horizontal':
|
||||
w = int(self.frame.width()) / 2
|
||||
# vertical
|
||||
gradient = QtGui.QLinearGradient(w, 0, w, self.frame.height())
|
||||
elif self._theme.background_direction == u'vertical':
|
||||
h = int(self.frame.height()) / 2
|
||||
# Horizontal
|
||||
gradient = QtGui.QLinearGradient(0, h, self.frame.width(), h)
|
||||
else:
|
||||
w = int(self.frame.width()) / 2
|
||||
h = int(self.frame.height()) / 2
|
||||
# Circular
|
||||
gradient = QtGui.QRadialGradient(w, h, w)
|
||||
gradient.setColorAt(0,
|
||||
QtGui.QColor(self._theme.background_startColor))
|
||||
gradient.setColorAt(1,
|
||||
QtGui.QColor(self._theme.background_endColor))
|
||||
painter.setBrush(QtGui.QBrush(gradient))
|
||||
rect_path = QtGui.QPainterPath()
|
||||
max_x = self.frame.width()
|
||||
max_y = self.frame.height()
|
||||
rect_path.moveTo(0, 0)
|
||||
rect_path.lineTo(0, max_y)
|
||||
rect_path.lineTo(max_x, max_y)
|
||||
rect_path.lineTo(max_x, 0)
|
||||
rect_path.closeSubpath()
|
||||
painter.drawPath(rect_path)
|
||||
elif self._theme.background_type == u'image':
|
||||
# image
|
||||
painter.fillRect(self.frame.rect(), QtCore.Qt.black)
|
||||
if self.bg_image:
|
||||
painter.drawImage(0, 0, self.bg_image)
|
||||
painter.end()
|
||||
|
@ -93,6 +93,9 @@ class RenderManager(object):
|
||||
"""
|
||||
self.global_theme = global_theme
|
||||
self.theme_level = theme_level
|
||||
self.global_theme_data = \
|
||||
self.theme_manager.getThemeData(self.global_theme)
|
||||
self.themedata = None
|
||||
|
||||
def set_service_theme(self, service_theme):
|
||||
"""
|
||||
@ -102,6 +105,7 @@ class RenderManager(object):
|
||||
The service-level theme to be set.
|
||||
"""
|
||||
self.service_theme = service_theme
|
||||
self.themedata = None
|
||||
|
||||
def set_override_theme(self, theme, overrideLevels=False):
|
||||
"""
|
||||
@ -111,6 +115,10 @@ class RenderManager(object):
|
||||
``theme``
|
||||
The name of the song-level theme. None means the service
|
||||
item wants to use the given value.
|
||||
|
||||
``overrideLevels``
|
||||
Used to force the theme data passed in to be used.
|
||||
|
||||
"""
|
||||
log.debug(u'set override theme to %s', theme)
|
||||
theme_level = self.theme_level
|
||||
@ -137,6 +145,7 @@ class RenderManager(object):
|
||||
if self.theme != self.renderer.theme_name or self.themedata is None \
|
||||
or overrideLevels:
|
||||
log.debug(u'theme is now %s', self.theme)
|
||||
# Force the theme to be the one passed in.
|
||||
if overrideLevels:
|
||||
self.themedata = theme
|
||||
else:
|
||||
|
@ -160,9 +160,9 @@ class ServiceItem(object):
|
||||
self.themedata = self.render_manager.renderer._theme
|
||||
for slide in self._raw_frames:
|
||||
before = time.time()
|
||||
formated = self.render_manager \
|
||||
formatted = self.render_manager \
|
||||
.format_slide(slide[u'raw_slide'], line_break)
|
||||
for page in formated:
|
||||
for page in formatted:
|
||||
self._display_frames.append(
|
||||
{u'title': clean_tags(page),
|
||||
u'text': clean_tags(page.rstrip()),
|
||||
@ -170,6 +170,7 @@ class ServiceItem(object):
|
||||
u'verseTag': slide[u'verseTag'] })
|
||||
log.log(15, u'Formatting took %4s' % (time.time() - before))
|
||||
elif self.service_item_type == ServiceItemType.Image:
|
||||
self.themedata = self.render_manager.global_theme_data
|
||||
for slide in self._raw_frames:
|
||||
slide[u'image'] = resize_image(slide[u'image'],
|
||||
self.render_manager.width, self.render_manager.height)
|
||||
|
155
openlp/core/lib/spelltextedit.py
Normal file
155
openlp/core/lib/spelltextedit.py
Normal file
@ -0,0 +1,155 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
|
||||
|
||||
###############################################################################
|
||||
# OpenLP - Open Source Lyrics Projection #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Copyright (c) 2008-2010 Raoul Snyman #
|
||||
# Portions copyright (c) 2008-2010 Tim Bentley, Jonathan Corwin, Michael #
|
||||
# Gorven, Scott Guerrieri, Meinert Jordan, Andreas Preikschat, Christian #
|
||||
# Richter, Philip Ridout, Maikel Stuivenberg, Martin Thompson, Jon Tibble, #
|
||||
# Carsten Tinggaard, Frode Woldsund #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# This program is free software; you can redistribute it and/or modify it #
|
||||
# under the terms of the GNU General Public License as published by the Free #
|
||||
# Software Foundation; version 2 of the License. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT #
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
|
||||
# more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License along #
|
||||
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||
###############################################################################
|
||||
|
||||
import re
|
||||
import sys
|
||||
try:
|
||||
import enchant
|
||||
enchant_available = True
|
||||
except ImportError:
|
||||
enchant_available = False
|
||||
|
||||
# based on code from
|
||||
# http://john.nachtimwald.com/2009/08/22/qplaintextedit-with-in-line-spell-check/
|
||||
|
||||
from PyQt4 import QtCore, QtGui
|
||||
from openlp.core.lib import html_expands, translate, context_menu_action
|
||||
|
||||
class SpellTextEdit(QtGui.QPlainTextEdit):
|
||||
def __init__(self, *args):
|
||||
QtGui.QPlainTextEdit.__init__(self, *args)
|
||||
# Default dictionary based on the current locale.
|
||||
if enchant_available:
|
||||
self.dict = enchant.Dict()
|
||||
self.highlighter = Highlighter(self.document())
|
||||
self.highlighter.setDict(self.dict)
|
||||
|
||||
def mousePressEvent(self, event):
|
||||
if event.button() == QtCore.Qt.RightButton:
|
||||
# Rewrite the mouse event to a left button event so the cursor is
|
||||
# moved to the location of the pointer.
|
||||
event = QtGui.QMouseEvent(QtCore.QEvent.MouseButtonPress,
|
||||
event.pos(), QtCore.Qt.LeftButton, QtCore.Qt.LeftButton,
|
||||
QtCore.Qt.NoModifier)
|
||||
QtGui.QPlainTextEdit.mousePressEvent(self, event)
|
||||
|
||||
def contextMenuEvent(self, event):
|
||||
popup_menu = self.createStandardContextMenu()
|
||||
# Select the word under the cursor.
|
||||
cursor = self.textCursor()
|
||||
# only select text if not already selected
|
||||
if not cursor.hasSelection():
|
||||
cursor.select(QtGui.QTextCursor.WordUnderCursor)
|
||||
self.setTextCursor(cursor)
|
||||
# Check if the selected word is misspelled and offer spelling
|
||||
# suggestions if it is.
|
||||
if enchant_available and self.textCursor().hasSelection():
|
||||
text = unicode(self.textCursor().selectedText())
|
||||
if not self.dict.check(text):
|
||||
spell_menu = QtGui.QMenu(translate('OpenLP.SpellTextEdit',
|
||||
'Spelling Suggestions'))
|
||||
for word in self.dict.suggest(text):
|
||||
action = SpellAction(word, spell_menu)
|
||||
action.correct.connect(self.correctWord)
|
||||
spell_menu.addAction(action)
|
||||
# Only add the spelling suggests to the menu if there are
|
||||
# suggestions.
|
||||
if len(spell_menu.actions()) != 0:
|
||||
popup_menu.insertSeparator(popup_menu.actions()[0])
|
||||
popup_menu.insertMenu(popup_menu.actions()[0], spell_menu)
|
||||
tag_menu = QtGui.QMenu(translate('OpenLP.SpellTextEdit',
|
||||
'Formatting Tags'))
|
||||
for html in html_expands:
|
||||
action = SpellAction( html[u'desc'], tag_menu)
|
||||
action.correct.connect(self.htmlTag)
|
||||
tag_menu.addAction(action)
|
||||
popup_menu.insertSeparator(popup_menu.actions()[0])
|
||||
popup_menu.insertMenu(popup_menu.actions()[0], tag_menu)
|
||||
popup_menu.exec_(event.globalPos())
|
||||
|
||||
def correctWord(self, word):
|
||||
"""
|
||||
Replaces the selected text with word.
|
||||
"""
|
||||
cursor = self.textCursor()
|
||||
cursor.beginEditBlock()
|
||||
cursor.removeSelectedText()
|
||||
cursor.insertText(word)
|
||||
cursor.endEditBlock()
|
||||
|
||||
def htmlTag(self, tag):
|
||||
"""
|
||||
Replaces the selected text with word.
|
||||
"""
|
||||
for html in html_expands:
|
||||
if tag == html[u'desc']:
|
||||
cursor = self.textCursor()
|
||||
if self.textCursor().hasSelection():
|
||||
text = cursor.selectedText()
|
||||
cursor.beginEditBlock()
|
||||
cursor.removeSelectedText()
|
||||
cursor.insertText(html[u'start tag'])
|
||||
cursor.insertText(text)
|
||||
cursor.insertText(html[u'end tag'])
|
||||
cursor.endEditBlock()
|
||||
else:
|
||||
cursor = self.textCursor()
|
||||
cursor.insertText(html[u'start tag'])
|
||||
cursor.insertText(html[u'end tag'])
|
||||
|
||||
class Highlighter(QtGui.QSyntaxHighlighter):
|
||||
|
||||
WORDS = u'(?iu)[\w\']+'
|
||||
|
||||
def __init__(self, *args):
|
||||
QtGui.QSyntaxHighlighter.__init__(self, *args)
|
||||
self.dict = None
|
||||
|
||||
def setDict(self, dict):
|
||||
self.dict = dict
|
||||
|
||||
def highlightBlock(self, text):
|
||||
if not self.dict:
|
||||
return
|
||||
text = unicode(text)
|
||||
format = QtGui.QTextCharFormat()
|
||||
format.setUnderlineColor(QtCore.Qt.red)
|
||||
format.setUnderlineStyle(QtGui.QTextCharFormat.SpellCheckUnderline)
|
||||
for word_object in re.finditer(self.WORDS, text):
|
||||
if not self.dict.check(word_object.group()):
|
||||
self.setFormat(word_object.start(),
|
||||
word_object.end() - word_object.start(), format)
|
||||
|
||||
class SpellAction(QtGui.QAction):
|
||||
"""
|
||||
A special QAction that returns the text in a signal.
|
||||
"""
|
||||
correct = QtCore.pyqtSignal(unicode)
|
||||
|
||||
def __init__(self, *args):
|
||||
QtGui.QAction.__init__(self, *args)
|
||||
self.triggered.connect(lambda x: self.correct.emit(
|
||||
unicode(self.text())))
|
@ -27,141 +27,6 @@
|
||||
The :mod:`ui` module provides the core user interface for OpenLP
|
||||
"""
|
||||
|
||||
# http://john.nachtimwald.com/2009/08/22/qplaintextedit-with-in-line-spell-check/
|
||||
|
||||
import re
|
||||
import sys
|
||||
try:
|
||||
import enchant
|
||||
enchant_available = True
|
||||
except ImportError:
|
||||
enchant_available = False
|
||||
|
||||
from PyQt4 import QtCore, QtGui
|
||||
from openlp.core.lib import html_expands, translate, context_menu_action
|
||||
|
||||
class SpellTextEdit(QtGui.QPlainTextEdit):
|
||||
def __init__(self, *args):
|
||||
QtGui.QPlainTextEdit.__init__(self, *args)
|
||||
# Default dictionary based on the current locale.
|
||||
if enchant_available:
|
||||
self.dict = enchant.Dict()
|
||||
self.highlighter = Highlighter(self.document())
|
||||
self.highlighter.setDict(self.dict)
|
||||
|
||||
def mousePressEvent(self, event):
|
||||
if event.button() == QtCore.Qt.RightButton:
|
||||
# Rewrite the mouse event to a left button event so the cursor is
|
||||
# moved to the location of the pointer.
|
||||
event = QtGui.QMouseEvent(QtCore.QEvent.MouseButtonPress,
|
||||
event.pos(), QtCore.Qt.LeftButton, QtCore.Qt.LeftButton,
|
||||
QtCore.Qt.NoModifier)
|
||||
QtGui.QPlainTextEdit.mousePressEvent(self, event)
|
||||
|
||||
def contextMenuEvent(self, event):
|
||||
popup_menu = self.createStandardContextMenu()
|
||||
# Select the word under the cursor.
|
||||
cursor = self.textCursor()
|
||||
cursor.select(QtGui.QTextCursor.WordUnderCursor)
|
||||
self.setTextCursor(cursor)
|
||||
# Check if the selected word is misspelled and offer spelling
|
||||
# suggestions if it is.
|
||||
if enchant_available and self.textCursor().hasSelection():
|
||||
text = unicode(self.textCursor().selectedText())
|
||||
if not self.dict.check(text):
|
||||
spell_menu = QtGui.QMenu(translate('OpenLP.SpellTextEdit',
|
||||
'Spelling Suggestions'))
|
||||
for word in self.dict.suggest(text):
|
||||
action = SpellAction(word, spell_menu)
|
||||
action.correct.connect(self.correctWord)
|
||||
spell_menu.addAction(action)
|
||||
# Only add the spelling suggests to the menu if there are
|
||||
# suggestions.
|
||||
if len(spell_menu.actions()) != 0:
|
||||
popup_menu.insertSeparator(popup_menu.actions()[0])
|
||||
popup_menu.insertMenu(popup_menu.actions()[0], spell_menu)
|
||||
tag_menu = QtGui.QMenu(translate('OpenLP.SpellTextEdit',
|
||||
'Formatting Tags'))
|
||||
for html in html_expands:
|
||||
action = SpellAction( html[u'desc'], tag_menu)
|
||||
action.correct.connect(self.htmlTag)
|
||||
tag_menu.addAction(action)
|
||||
popup_menu.insertSeparator(popup_menu.actions()[0])
|
||||
popup_menu.insertMenu(popup_menu.actions()[0], tag_menu)
|
||||
|
||||
popup_menu.exec_(event.globalPos())
|
||||
|
||||
def correctWord(self, word):
|
||||
"""
|
||||
Replaces the selected text with word.
|
||||
"""
|
||||
cursor = self.textCursor()
|
||||
cursor.beginEditBlock()
|
||||
|
||||
cursor.removeSelectedText()
|
||||
cursor.insertText(word)
|
||||
|
||||
cursor.endEditBlock()
|
||||
|
||||
def htmlTag(self, tag):
|
||||
"""
|
||||
Replaces the selected text with word.
|
||||
"""
|
||||
for html in html_expands:
|
||||
if tag == html[u'desc']:
|
||||
cursor = self.textCursor()
|
||||
if self.textCursor().hasSelection():
|
||||
text = cursor.selectedText()
|
||||
cursor.beginEditBlock()
|
||||
cursor.removeSelectedText()
|
||||
cursor.insertText(html[u'start tag'])
|
||||
cursor.insertText(text)
|
||||
cursor.insertText(html[u'end tag'])
|
||||
cursor.endEditBlock()
|
||||
else:
|
||||
cursor = self.textCursor()
|
||||
cursor.insertText(html[u'start tag'])
|
||||
cursor.insertText(html[u'end tag'])
|
||||
|
||||
class Highlighter(QtGui.QSyntaxHighlighter):
|
||||
|
||||
WORDS = u'(?iu)[\w\']+'
|
||||
|
||||
def __init__(self, *args):
|
||||
QtGui.QSyntaxHighlighter.__init__(self, *args)
|
||||
|
||||
self.dict = None
|
||||
|
||||
def setDict(self, dict):
|
||||
self.dict = dict
|
||||
|
||||
def highlightBlock(self, text):
|
||||
if not self.dict:
|
||||
return
|
||||
|
||||
text = unicode(text)
|
||||
|
||||
format = QtGui.QTextCharFormat()
|
||||
format.setUnderlineColor(QtCore.Qt.red)
|
||||
format.setUnderlineStyle(QtGui.QTextCharFormat.SpellCheckUnderline)
|
||||
|
||||
for word_object in re.finditer(self.WORDS, text):
|
||||
if not self.dict.check(word_object.group()):
|
||||
self.setFormat(word_object.start(),
|
||||
word_object.end() - word_object.start(), format)
|
||||
|
||||
class SpellAction(QtGui.QAction):
|
||||
"""
|
||||
A special QAction that returns the text in a signal.
|
||||
"""
|
||||
correct = QtCore.pyqtSignal(unicode)
|
||||
|
||||
def __init__(self, *args):
|
||||
QtGui.QAction.__init__(self, *args)
|
||||
|
||||
self.triggered.connect(lambda x: self.correct.emit(
|
||||
unicode(self.text())))
|
||||
|
||||
class HideMode(object):
|
||||
"""
|
||||
This is basically an enumeration class which specifies the mode of a Bible.
|
||||
|
@ -195,6 +195,19 @@ class GeneralTab(SettingsTab):
|
||||
self.currentYValueLabel.setObjectName(u'currentYValueLabel')
|
||||
self.currentYLayout.addWidget(self.currentYValueLabel)
|
||||
self.currentLayout.addLayout(self.currentYLayout)
|
||||
self.currentWidthLayout = QtGui.QVBoxLayout()
|
||||
self.currentWidthLayout.setSpacing(0)
|
||||
self.currentWidthLayout.setMargin(0)
|
||||
self.currentWidthLayout.setObjectName(u'currentWidthLayout')
|
||||
self.currentWidthLabel = QtGui.QLabel(self.displayGroupBox)
|
||||
self.currentWidthLabel.setAlignment(QtCore.Qt.AlignCenter)
|
||||
self.currentWidthLabel.setObjectName(u'currentWidthLabel')
|
||||
self.currentWidthLayout.addWidget(self.currentWidthLabel)
|
||||
self.currentWidthValueLabel = QtGui.QLabel(self.displayGroupBox)
|
||||
self.currentWidthValueLabel.setAlignment(QtCore.Qt.AlignCenter)
|
||||
self.currentWidthValueLabel.setObjectName(u'currentWidthValueLabel')
|
||||
self.currentWidthLayout.addWidget(self.currentWidthValueLabel)
|
||||
self.currentLayout.addLayout(self.currentWidthLayout)
|
||||
self.currentHeightLayout = QtGui.QVBoxLayout()
|
||||
self.currentHeightLayout.setSpacing(0)
|
||||
self.currentHeightLayout.setMargin(0)
|
||||
@ -209,19 +222,6 @@ class GeneralTab(SettingsTab):
|
||||
self.currentHeightValueLabel.setObjectName(u'Height')
|
||||
self.currentHeightLayout.addWidget(self.currentHeightValueLabel)
|
||||
self.currentLayout.addLayout(self.currentHeightLayout)
|
||||
self.currentWidthLayout = QtGui.QVBoxLayout()
|
||||
self.currentWidthLayout.setSpacing(0)
|
||||
self.currentWidthLayout.setMargin(0)
|
||||
self.currentWidthLayout.setObjectName(u'currentWidthLayout')
|
||||
self.currentWidthLabel = QtGui.QLabel(self.displayGroupBox)
|
||||
self.currentWidthLabel.setAlignment(QtCore.Qt.AlignCenter)
|
||||
self.currentWidthLabel.setObjectName(u'currentWidthLabel')
|
||||
self.currentWidthLayout.addWidget(self.currentWidthLabel)
|
||||
self.currentWidthValueLabel = QtGui.QLabel(self.displayGroupBox)
|
||||
self.currentWidthValueLabel.setAlignment(QtCore.Qt.AlignCenter)
|
||||
self.currentWidthValueLabel.setObjectName(u'currentWidthValueLabel')
|
||||
self.currentWidthLayout.addWidget(self.currentWidthValueLabel)
|
||||
self.currentLayout.addLayout(self.currentWidthLayout)
|
||||
self.displayLayout.addLayout(self.currentLayout)
|
||||
self.overrideCheckBox = QtGui.QCheckBox(self.displayGroupBox)
|
||||
self.overrideCheckBox.setObjectName(u'overrideCheckBox')
|
||||
@ -256,18 +256,6 @@ class GeneralTab(SettingsTab):
|
||||
self.customYValueEdit.setObjectName(u'customYValueEdit')
|
||||
self.customYLayout.addWidget(self.customYValueEdit)
|
||||
self.customLayout.addLayout(self.customYLayout)
|
||||
self.customHeightLayout = QtGui.QVBoxLayout()
|
||||
self.customHeightLayout.setSpacing(0)
|
||||
self.customHeightLayout.setMargin(0)
|
||||
self.customHeightLayout.setObjectName(u'customHeightLayout')
|
||||
self.customHeightLabel = QtGui.QLabel(self.displayGroupBox)
|
||||
self.customHeightLabel.setAlignment(QtCore.Qt.AlignCenter)
|
||||
self.customHeightLabel.setObjectName(u'customHeightLabel')
|
||||
self.customHeightLayout.addWidget(self.customHeightLabel)
|
||||
self.customHeightValueEdit = QtGui.QLineEdit(self.displayGroupBox)
|
||||
self.customHeightValueEdit.setObjectName(u'customHeightValueEdit')
|
||||
self.customHeightLayout.addWidget(self.customHeightValueEdit)
|
||||
self.customLayout.addLayout(self.customHeightLayout)
|
||||
self.customWidthLayout = QtGui.QVBoxLayout()
|
||||
self.customWidthLayout.setSpacing(0)
|
||||
self.customWidthLayout.setMargin(0)
|
||||
@ -281,6 +269,18 @@ class GeneralTab(SettingsTab):
|
||||
self.customWidthValueEdit.setObjectName(u'customWidthValueEdit')
|
||||
self.customWidthLayout.addWidget(self.customWidthValueEdit)
|
||||
self.customLayout.addLayout(self.customWidthLayout)
|
||||
self.customHeightLayout = QtGui.QVBoxLayout()
|
||||
self.customHeightLayout.setSpacing(0)
|
||||
self.customHeightLayout.setMargin(0)
|
||||
self.customHeightLayout.setObjectName(u'customHeightLayout')
|
||||
self.customHeightLabel = QtGui.QLabel(self.displayGroupBox)
|
||||
self.customHeightLabel.setAlignment(QtCore.Qt.AlignCenter)
|
||||
self.customHeightLabel.setObjectName(u'customHeightLabel')
|
||||
self.customHeightLayout.addWidget(self.customHeightLabel)
|
||||
self.customHeightValueEdit = QtGui.QLineEdit(self.displayGroupBox)
|
||||
self.customHeightValueEdit.setObjectName(u'customHeightValueEdit')
|
||||
self.customHeightLayout.addWidget(self.customHeightValueEdit)
|
||||
self.customLayout.addLayout(self.customHeightLayout)
|
||||
self.displayLayout.addLayout(self.customLayout)
|
||||
# Bottom spacer
|
||||
self.generalRightSpacer = QtGui.QSpacerItem(20, 40,
|
||||
@ -476,7 +476,6 @@ class GeneralTab(SettingsTab):
|
||||
# Order is important so be careful if you change
|
||||
if self.overrideChanged or postUpdate:
|
||||
Receiver.send_message(u'config_screen_changed')
|
||||
Receiver.send_message(u'config_updated')
|
||||
self.overrideChanged = False
|
||||
|
||||
def onOverrideCheckBoxToggled(self, checked):
|
||||
|
@ -97,6 +97,7 @@ class MainDisplay(DisplayWidget):
|
||||
self.screens = screens
|
||||
self.isLive = live
|
||||
self.alertTab = None
|
||||
self.hide_mode = None
|
||||
self.setWindowTitle(u'OpenLP Display')
|
||||
self.setWindowFlags(QtCore.Qt.FramelessWindowHint |
|
||||
QtCore.Qt.WindowStaysOnTopHint)
|
||||
@ -115,13 +116,18 @@ class MainDisplay(DisplayWidget):
|
||||
self.screen = self.screens.current
|
||||
self.setVisible(False)
|
||||
self.setGeometry(self.screen[u'size'])
|
||||
self.webView = QtWebKit.QWebView(self)
|
||||
self.webView.setGeometry(0, 0, self.screen[u'size'].width(), \
|
||||
self.scene = QtGui.QGraphicsScene()
|
||||
self.setScene(self.scene)
|
||||
self.webView = QtWebKit.QGraphicsWebView()
|
||||
self.scene.addItem(self.webView)
|
||||
self.webView.resize(self.screen[u'size'].width(), \
|
||||
self.screen[u'size'].height())
|
||||
self.page = self.webView.page()
|
||||
self.frame = self.page.mainFrame()
|
||||
QtCore.QObject.connect(self.webView,
|
||||
QtCore.SIGNAL(u'loadFinished(bool)'), self.isLoaded)
|
||||
self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
|
||||
self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
|
||||
self.frame.setScrollBarPolicy(QtCore.Qt.Vertical,
|
||||
QtCore.Qt.ScrollBarAlwaysOff)
|
||||
self.frame.setScrollBarPolicy(QtCore.Qt.Horizontal,
|
||||
@ -135,7 +141,7 @@ class MainDisplay(DisplayWidget):
|
||||
painter_image = QtGui.QPainter()
|
||||
painter_image.begin(self.black)
|
||||
painter_image.fillRect(self.black.rect(), QtCore.Qt.black)
|
||||
#Build the initial frame.
|
||||
# Build the initial frame.
|
||||
initialFrame = QtGui.QImage(
|
||||
self.screens.current[u'size'].width(),
|
||||
self.screens.current[u'size'].height(),
|
||||
@ -319,9 +325,6 @@ class MainDisplay(DisplayWidget):
|
||||
# Make display show up if in single screen mode
|
||||
if self.isLive:
|
||||
self.setVisible(True)
|
||||
# save preview for debugging
|
||||
if log.isEnabledFor(logging.DEBUG):
|
||||
preview.save(u'temp.png', u'png')
|
||||
return preview
|
||||
|
||||
def buildHtml(self, serviceItem):
|
||||
@ -338,6 +341,9 @@ class MainDisplay(DisplayWidget):
|
||||
self.webView.setHtml(html)
|
||||
if serviceItem.foot_text and serviceItem.foot_text:
|
||||
self.footer(serviceItem.foot_text)
|
||||
# if was hidden keep it hidden
|
||||
if self.hide_mode and self.isLive:
|
||||
self.hideDisplay(self.hide_mode)
|
||||
|
||||
def footer(self, text):
|
||||
"""
|
||||
@ -363,6 +369,7 @@ class MainDisplay(DisplayWidget):
|
||||
self.frame.evaluateJavaScript(u'show_blank("theme");')
|
||||
if mode != HideMode.Screen and self.isHidden():
|
||||
self.setVisible(True)
|
||||
self.hide_mode = mode
|
||||
|
||||
def showDisplay(self):
|
||||
"""
|
||||
@ -376,6 +383,7 @@ class MainDisplay(DisplayWidget):
|
||||
self.setVisible(True)
|
||||
# Trigger actions when display is active again
|
||||
Receiver.send_message(u'maindisplay_active')
|
||||
self.hide_mode = None
|
||||
|
||||
class AudioPlayer(QtCore.QObject):
|
||||
"""
|
||||
|
@ -574,7 +574,7 @@ class ServiceManager(QtGui.QWidget):
|
||||
* An osd which is a pickle of the service items
|
||||
* All image, presentation and video files needed to run the service.
|
||||
"""
|
||||
log.debug(u'onSaveService')
|
||||
log.debug(u'onSaveService %s' % quick)
|
||||
if not quick or self.isNew:
|
||||
filename = QtGui.QFileDialog.getSaveFileName(self,
|
||||
translate('OpenLP.ServiceManager', 'Save Service'),
|
||||
@ -755,6 +755,7 @@ class ServiceManager(QtGui.QWidget):
|
||||
"""
|
||||
Set the theme for the current service
|
||||
"""
|
||||
log.debug(u'onThemeComboBoxSelected')
|
||||
self.service_theme = unicode(self.themeComboBox.currentText())
|
||||
self.parent.RenderManager.set_service_theme(self.service_theme)
|
||||
QtCore.QSettings().setValue(
|
||||
@ -767,6 +768,7 @@ class ServiceManager(QtGui.QWidget):
|
||||
The theme may have changed in the settings dialog so make
|
||||
sure the theme combo box is in the correct state.
|
||||
"""
|
||||
log.debug(u'themeChange')
|
||||
if self.parent.RenderManager.theme_level == ThemeLevel.Global:
|
||||
self.toolbar.actions[u'ThemeLabel'].setVisible(False)
|
||||
self.toolbar.actions[u'ThemeWidget'].setVisible(False)
|
||||
@ -779,6 +781,7 @@ class ServiceManager(QtGui.QWidget):
|
||||
Rebuild the service list as things have changed and a
|
||||
repaint is the easiest way to do this.
|
||||
"""
|
||||
log.debug(u'regenerateServiceItems')
|
||||
# force reset of renderer as theme data has changed
|
||||
self.parent.RenderManager.themedata = None
|
||||
if self.serviceItems:
|
||||
@ -800,6 +803,7 @@ class ServiceManager(QtGui.QWidget):
|
||||
``item``
|
||||
Service Item to be added
|
||||
"""
|
||||
log.debug(u'addServiceItem')
|
||||
sitem = self.findServiceItem()[0]
|
||||
item.render()
|
||||
if replace:
|
||||
|
@ -30,6 +30,7 @@ import logging
|
||||
|
||||
from PyQt4 import QtGui
|
||||
|
||||
from openlp.core.lib import Receiver
|
||||
from openlp.core.ui import AdvancedTab, GeneralTab, ThemesTab
|
||||
from settingsdialog import Ui_SettingsDialog
|
||||
|
||||
@ -87,6 +88,8 @@ class SettingsForm(QtGui.QDialog, Ui_SettingsDialog):
|
||||
"""
|
||||
for tabIndex in range(0, self.settingsTabWidget.count()):
|
||||
self.settingsTabWidget.widget(tabIndex).save()
|
||||
# Must go after all settings are save
|
||||
Receiver.send_message(u'config_updated')
|
||||
return QtGui.QDialog.accept(self)
|
||||
|
||||
def postSetUp(self):
|
||||
|
@ -196,10 +196,10 @@ class BiblesTab(SettingsTab):
|
||||
self.show_new_chapters = True
|
||||
|
||||
def onBibleDualCheckBox(self, check_state):
|
||||
self.duel_bibles = False
|
||||
self.dual_bibles = False
|
||||
# we have a set value convert to True/False
|
||||
if check_state == QtCore.Qt.Checked:
|
||||
self.duel_bibles = True
|
||||
self.dual_bibles = True
|
||||
|
||||
def load(self):
|
||||
settings = QtCore.QSettings()
|
||||
@ -212,12 +212,12 @@ class BiblesTab(SettingsTab):
|
||||
u'verse layout style', QtCore.QVariant(0)).toInt()[0]
|
||||
self.bible_theme = unicode(
|
||||
settings.value(u'bible theme', QtCore.QVariant(u'')).toString())
|
||||
self.duel_bibles = settings.value(
|
||||
self.dual_bibles = settings.value(
|
||||
u'dual bibles', QtCore.QVariant(True)).toBool()
|
||||
self.NewChaptersCheckBox.setChecked(self.show_new_chapters)
|
||||
self.DisplayStyleComboBox.setCurrentIndex(self.display_style)
|
||||
self.LayoutStyleComboBox.setCurrentIndex(self.layout_style)
|
||||
self.BibleDualCheckBox.setChecked(self.duel_bibles)
|
||||
self.BibleDualCheckBox.setChecked(self.dual_bibles)
|
||||
settings.endGroup()
|
||||
|
||||
def save(self):
|
||||
@ -229,7 +229,7 @@ class BiblesTab(SettingsTab):
|
||||
QtCore.QVariant(self.display_style))
|
||||
settings.setValue(u'verse layout style',
|
||||
QtCore.QVariant(self.layout_style))
|
||||
settings.setValue(u'dual bibles', QtCore.QVariant(self.duel_bibles))
|
||||
settings.setValue(u'dual bibles', QtCore.QVariant(self.dual_bibles))
|
||||
settings.setValue(u'bible theme', QtCore.QVariant(self.bible_theme))
|
||||
settings.endGroup()
|
||||
|
||||
|
@ -51,9 +51,9 @@ class CSVBible(BibleDB):
|
||||
if u'booksfile' not in kwargs:
|
||||
raise KeyError(u'You have to supply a file to import books from.')
|
||||
self.booksfile = kwargs[u'booksfile']
|
||||
if u'versesfile' not in kwargs:
|
||||
if u'versefile' not in kwargs:
|
||||
raise KeyError(u'You have to supply a file to import verses from.')
|
||||
self.versesfile = kwargs[u'versesfile']
|
||||
self.versesfile = kwargs[u'versefile']
|
||||
QtCore.QObject.connect(Receiver.get_receiver(),
|
||||
QtCore.SIGNAL(u'bibles_stop_import'), self.stop_import)
|
||||
|
||||
|
@ -47,7 +47,6 @@ class BibleListView(BaseListWithDnD):
|
||||
self.parent().onListViewResize(event.size().width(),
|
||||
event.size().width())
|
||||
|
||||
|
||||
class BibleMediaItem(MediaManagerItem):
|
||||
"""
|
||||
This is the custom media manager item for Bibles.
|
||||
@ -276,8 +275,9 @@ class BibleMediaItem(MediaManagerItem):
|
||||
self.SearchProgress.setObjectName(u'SearchProgress')
|
||||
|
||||
def configUpdated(self):
|
||||
log.debug(u'configUpdated')
|
||||
if QtCore.QSettings().value(self.settingsSection + u'/dual bibles',
|
||||
QtCore.QVariant(False)).toBool():
|
||||
QtCore.QVariant(True)).toBool():
|
||||
self.AdvancedSecondBibleLabel.setVisible(True)
|
||||
self.AdvancedSecondBibleComboBox.setVisible(True)
|
||||
self.QuickSecondVersionLabel.setVisible(True)
|
||||
@ -466,19 +466,27 @@ class BibleMediaItem(MediaManagerItem):
|
||||
|
||||
def generateSlideData(self, service_item, item=None):
|
||||
"""
|
||||
Generates and formats the slides for the service item.
|
||||
Generates and formats the slides for the service item as well as the
|
||||
service item's title.
|
||||
"""
|
||||
log.debug(u'generating slide data')
|
||||
items = self.listView.selectedIndexes()
|
||||
if len(items) == 0:
|
||||
return False
|
||||
has_dual_bible = False
|
||||
bible_text = u''
|
||||
old_chapter = u''
|
||||
raw_footer = []
|
||||
raw_slides = []
|
||||
service_item.add_capability(ItemCapabilities.AllowsPreview)
|
||||
service_item.add_capability(ItemCapabilities.AllowsLoop)
|
||||
service_item.add_capability(ItemCapabilities.AllowsAdditions)
|
||||
for item in items:
|
||||
bitem = self.listView.item(item.row())
|
||||
reference = bitem.data(QtCore.Qt.UserRole)
|
||||
if isinstance(reference, QtCore.QVariant):
|
||||
reference = reference.toPyObject()
|
||||
dual_bible = self._decodeQtObject(reference, 'dual_bible')
|
||||
if dual_bible:
|
||||
has_dual_bible = True
|
||||
break
|
||||
# Let's loop through the main lot, and assemble our verses.
|
||||
for item in items:
|
||||
bitem = self.listView.item(item.row())
|
||||
@ -491,7 +499,7 @@ class BibleMediaItem(MediaManagerItem):
|
||||
bible = self._decodeQtObject(reference, 'bible')
|
||||
version = self._decodeQtObject(reference, 'version')
|
||||
copyright = self._decodeQtObject(reference, 'copyright')
|
||||
#permission = self._decodeQtObject(reference, 'permission')
|
||||
permission = self._decodeQtObject(reference, 'permission')
|
||||
text = self._decodeQtObject(reference, 'text')
|
||||
dual_bible = self._decodeQtObject(reference, 'dual_bible')
|
||||
if dual_bible:
|
||||
@ -499,70 +507,57 @@ class BibleMediaItem(MediaManagerItem):
|
||||
'dual_version')
|
||||
dual_copyright = self._decodeQtObject(reference,
|
||||
'dual_copyright')
|
||||
#dual_permission = self._decodeQtObject(reference,
|
||||
# 'dual_permission')
|
||||
dual_permission = self._decodeQtObject(reference,
|
||||
'dual_permission')
|
||||
dual_text = self._decodeQtObject(reference, 'dual_text')
|
||||
if self.parent.settings_tab.display_style == 1:
|
||||
verse_text = self.formatVerse(old_chapter, chapter, verse,
|
||||
u'{su}(', u'){/su}')
|
||||
elif self.parent.settings_tab.display_style == 2:
|
||||
verse_text = self.formatVerse(old_chapter, chapter, verse,
|
||||
u'{su}{', u'}{/su}')
|
||||
elif self.parent.settings_tab.display_style == 3:
|
||||
verse_text = self.formatVerse(old_chapter, chapter, verse,
|
||||
u'{su}[', u']{/su}')
|
||||
else:
|
||||
verse_text = self.formatVerse(old_chapter, chapter, verse,
|
||||
u'{su}', u'{/su}')
|
||||
old_chapter = chapter
|
||||
footer = u'%s (%s %s)' % (book, version, copyright)
|
||||
# If not found add to footer
|
||||
verse_text = self.formatVerse(old_chapter, chapter, verse)
|
||||
footer = u'%s (%s %s %s)' % (book, version, copyright, permission)
|
||||
if footer not in raw_footer:
|
||||
raw_footer.append(footer)
|
||||
if dual_bible:
|
||||
footer = u'%s (%s %s)' % (book, dual_version,
|
||||
dual_copyright)
|
||||
# If not found add second version and copyright to footer.
|
||||
if footer not in raw_footer:
|
||||
raw_footer.append(footer)
|
||||
bible_text = u'%s %s \n\n %s %s' % (verse_text, text,
|
||||
verse_text, dual_text)
|
||||
raw_slides.append(bible_text)
|
||||
bible_text = u''
|
||||
else:
|
||||
# If we are 'Verse Per Line' then force a new line.
|
||||
if self.parent.settings_tab.layout_style == 1:
|
||||
text = text + u'\n'
|
||||
else:
|
||||
# split the line but do not replace line breaks in renderer
|
||||
service_item.add_capability(ItemCapabilities.NoLineBreaks)
|
||||
text = text + u'\n'
|
||||
bible_text = u'%s %s %s' % (bible_text, verse_text, text)
|
||||
# If we are 'Verse Per Slide' then create a new slide.
|
||||
if self.parent.settings_tab.layout_style == 0:
|
||||
raw_slides.append(bible_text)
|
||||
bible_text = u''
|
||||
# If we are not 'Verse Per Slide' we have to make sure, that we
|
||||
# add more verses.
|
||||
else:
|
||||
if item.row() < len(items) - 1:
|
||||
bitem = items[item.row() + 1]
|
||||
reference = bitem.data(QtCore.Qt.UserRole)
|
||||
if isinstance(reference, QtCore.QVariant):
|
||||
reference = reference.toPyObject()
|
||||
bible_new = self._decodeQtObject(reference, 'bible')
|
||||
dual_bible_new = self._decodeQtObject(reference,
|
||||
'dual_bible')
|
||||
if dual_bible_new:
|
||||
raw_slides.append(bible_text)
|
||||
bible_text = u''
|
||||
elif bible != bible_new:
|
||||
raw_slides.append(bible_text)
|
||||
bible_text = u''
|
||||
else:
|
||||
if has_dual_bible:
|
||||
if dual_bible:
|
||||
footer = u'%s (%s %s %s)' % (book, dual_version,
|
||||
dual_copyright, dual_permission)
|
||||
if footer not in raw_footer:
|
||||
raw_footer.append(footer)
|
||||
# If there is an old bible_text we have to add it.
|
||||
if bible_text:
|
||||
raw_slides.append(bible_text)
|
||||
bible_text = u''
|
||||
# service item title
|
||||
bible_text = u'%s %s\n\n%s %s' % (verse_text, text,
|
||||
verse_text, dual_text)
|
||||
raw_slides.append(bible_text)
|
||||
bible_text = u''
|
||||
elif self.parent.settings_tab.layout_style == 0:
|
||||
bible_text = u'%s %s' % (verse_text, text)
|
||||
raw_slides.append(bible_text)
|
||||
bible_text = u''
|
||||
else:
|
||||
bible_text = u'%s %s %s\n' % (bible_text, verse_text, text)
|
||||
# If we are 'Verse Per Slide' then create a new slide.
|
||||
elif self.parent.settings_tab.layout_style == 0:
|
||||
bible_text = u'%s %s' % (verse_text, text)
|
||||
raw_slides.append(bible_text)
|
||||
bible_text = u''
|
||||
# If we are 'Verse Per Line' then force a new line.
|
||||
elif self.parent.settings_tab.layout_style == 1:
|
||||
bible_text = u'%s %s %s\n' % (bible_text, verse_text, text)
|
||||
# We have to be 'Continuous'.
|
||||
else:
|
||||
bible_text = u'%s %s %s\n' % (bible_text, verse_text, text)
|
||||
old_chapter = chapter
|
||||
# If there are no more items we check whether we have to add bible_text.
|
||||
if bible_text:
|
||||
raw_slides.append(bible_text)
|
||||
bible_text = u''
|
||||
# Service Item: Capabilities
|
||||
if self.parent.settings_tab.layout_style == 2 and not has_dual_bible:
|
||||
# split the line but do not replace line breaks in renderer
|
||||
service_item.add_capability(ItemCapabilities.NoLineBreaks)
|
||||
service_item.add_capability(ItemCapabilities.AllowsPreview)
|
||||
service_item.add_capability(ItemCapabilities.AllowsLoop)
|
||||
service_item.add_capability(ItemCapabilities.AllowsAdditions)
|
||||
# Service Item: Title
|
||||
if not service_item.title:
|
||||
if dual_bible:
|
||||
service_item.title = u'%s (%s, %s) %s' % (book, version,
|
||||
@ -573,7 +568,7 @@ class BibleMediaItem(MediaManagerItem):
|
||||
translate('BiblesPlugin.MediaItem', 'etc')) == -1:
|
||||
service_item.title = u'%s, %s' % (service_item.title,
|
||||
translate('BiblesPlugin.MediaItem', 'etc'))
|
||||
# item theme
|
||||
# Service Item: Theme
|
||||
if len(self.parent.settings_tab.bible_theme) == 0:
|
||||
service_item.theme = None
|
||||
else:
|
||||
@ -587,14 +582,20 @@ class BibleMediaItem(MediaManagerItem):
|
||||
service_item.raw_footer = raw_footer
|
||||
return True
|
||||
|
||||
def formatVerse(self, old_chapter, chapter, verse, opening, closing):
|
||||
verse_text = opening
|
||||
if old_chapter != chapter:
|
||||
verse_text += chapter + u':'
|
||||
elif not self.parent.settings_tab.show_new_chapters:
|
||||
verse_text += chapter + u':'
|
||||
verse_text += verse
|
||||
verse_text += closing
|
||||
def formatVerse(self, old_chapter, chapter, verse):
|
||||
if not self.parent.settings_tab.show_new_chapters or \
|
||||
old_chapter != chapter:
|
||||
verse_text = chapter + u':' + verse
|
||||
else:
|
||||
verse_text = verse
|
||||
if self.parent.settings_tab.display_style == 1:
|
||||
verse_text = u'{su}(' + verse_text + u'){/su}'
|
||||
elif self.parent.settings_tab.display_style == 2:
|
||||
verse_text = u'{su}{' + verse_text + u'}{/su}'
|
||||
elif self.parent.settings_tab.display_style == 3:
|
||||
verse_text = u'{su}[' + verse_text + u']{/su}'
|
||||
else:
|
||||
verse_text = u'{su}' + verse_text + u'{/su}'
|
||||
return verse_text
|
||||
|
||||
def reloadBibles(self):
|
||||
@ -639,14 +640,14 @@ class BibleMediaItem(MediaManagerItem):
|
||||
for i in range(int(range_from), int(range_to) + 1):
|
||||
combo.addItem(unicode(i))
|
||||
|
||||
def displayResults(self, bible, dual_bible=None):
|
||||
def displayResults(self, bible, dual_bible=u''):
|
||||
"""
|
||||
Displays the search results in the media manager. All data needed for
|
||||
further action is saved for/in each row.
|
||||
"""
|
||||
version = self.parent.manager.get_meta_data(bible, u'Version')
|
||||
copyright = self.parent.manager.get_meta_data(bible, u'Copyright')
|
||||
#permission = self.parent.manager.get_meta_data(bible, u'Permissions')
|
||||
permission = self.parent.manager.get_meta_data(bible, u'Permissions')
|
||||
if dual_bible:
|
||||
dual_version = self.parent.manager.get_meta_data(dual_bible,
|
||||
u'Version')
|
||||
@ -654,43 +655,41 @@ class BibleMediaItem(MediaManagerItem):
|
||||
u'Copyright')
|
||||
dual_permission = self.parent.manager.get_meta_data(dual_bible,
|
||||
u'Permissions')
|
||||
if dual_permission:
|
||||
dual_permission = dual_permission.value
|
||||
else:
|
||||
if not dual_permission:
|
||||
dual_permission = u''
|
||||
# We count the number of rows which are maybe already present.
|
||||
start_count = self.listView.count()
|
||||
for count, verse in enumerate(self.search_results):
|
||||
if dual_bible:
|
||||
vdict = {
|
||||
'book':QtCore.QVariant(verse.book.name),
|
||||
'chapter':QtCore.QVariant(verse.chapter),
|
||||
'verse':QtCore.QVariant(verse.verse),
|
||||
'bible':QtCore.QVariant(bible),
|
||||
'version':QtCore.QVariant(version.value),
|
||||
'copyright':QtCore.QVariant(copyright.value),
|
||||
#'permission':QtCore.QVariant(permission.value),
|
||||
'text':QtCore.QVariant(verse.text),
|
||||
'dual_bible':QtCore.QVariant(dual_bible),
|
||||
'dual_version':QtCore.QVariant(dual_version.value),
|
||||
'dual_copyright':QtCore.QVariant(dual_copyright.value),
|
||||
#'dual_permission':QtCore.QVariant(dual_permission),
|
||||
'dual_text':QtCore.QVariant(
|
||||
'book': QtCore.QVariant(verse.book.name),
|
||||
'chapter': QtCore.QVariant(verse.chapter),
|
||||
'verse': QtCore.QVariant(verse.verse),
|
||||
'bible': QtCore.QVariant(bible),
|
||||
'version': QtCore.QVariant(version.value),
|
||||
'copyright': QtCore.QVariant(copyright.value),
|
||||
'permission': QtCore.QVariant(permission.value),
|
||||
'text': QtCore.QVariant(verse.text),
|
||||
'dual_bible': QtCore.QVariant(dual_bible),
|
||||
'dual_version': QtCore.QVariant(dual_version.value),
|
||||
'dual_copyright': QtCore.QVariant(dual_copyright.value),
|
||||
'dual_permission': QtCore.QVariant(dual_permission.value),
|
||||
'dual_text': QtCore.QVariant(
|
||||
self.dual_search_results[count].text)
|
||||
}
|
||||
bible_text = u' %s %d:%d (%s, %s)' % (verse.book.name,
|
||||
verse.chapter, verse.verse, version.value, dual_version.value)
|
||||
else:
|
||||
vdict = {
|
||||
'book':QtCore.QVariant(verse.book.name),
|
||||
'chapter':QtCore.QVariant(verse.chapter),
|
||||
'verse':QtCore.QVariant(verse.verse),
|
||||
'bible':QtCore.QVariant(bible),
|
||||
'version':QtCore.QVariant(version.value),
|
||||
'copyright':QtCore.QVariant(copyright.value),
|
||||
#'permission':QtCore.QVariant(permission.value),
|
||||
'text':QtCore.QVariant(verse.text),
|
||||
'dual_bible':QtCore.QVariant(dual_bible)
|
||||
'book': QtCore.QVariant(verse.book.name),
|
||||
'chapter': QtCore.QVariant(verse.chapter),
|
||||
'verse': QtCore.QVariant(verse.verse),
|
||||
'bible': QtCore.QVariant(bible),
|
||||
'version': QtCore.QVariant(version.value),
|
||||
'copyright': QtCore.QVariant(copyright.value),
|
||||
'permission': QtCore.QVariant(permission.value),
|
||||
'text': QtCore.QVariant(verse.text),
|
||||
'dual_bible': QtCore.QVariant(dual_bible)
|
||||
}
|
||||
bible_text = u' %s %d:%d (%s)' % (verse.book.name,
|
||||
verse.chapter, verse.verse, version.value)
|
||||
|
@ -26,8 +26,7 @@
|
||||
|
||||
from PyQt4 import QtCore, QtGui
|
||||
|
||||
from openlp.core.lib import build_icon, translate
|
||||
from openlp.core.ui import SpellTextEdit
|
||||
from openlp.core.lib import build_icon, translate, SpellTextEdit
|
||||
|
||||
class Ui_CustomEditDialog(object):
|
||||
def setupUi(self, customEditDialog):
|
||||
|
@ -26,8 +26,7 @@
|
||||
|
||||
from PyQt4 import QtCore, QtGui
|
||||
|
||||
from openlp.core.lib import build_icon, translate
|
||||
from openlp.core.ui import SpellTextEdit
|
||||
from openlp.core.lib import build_icon, translate, SpellTextEdit
|
||||
from openlp.plugins.songs.lib import VerseType
|
||||
|
||||
class Ui_EditVerseDialog(object):
|
||||
|
@ -83,6 +83,12 @@ class ImportWizardForm(QtGui.QWizard, Ui_SongImportWizard):
|
||||
QtCore.QObject.connect(self.wordsOfWorshipRemoveButton,
|
||||
QtCore.SIGNAL(u'clicked()'),
|
||||
self.onWordsOfWorshipRemoveButtonClicked)
|
||||
QtCore.QObject.connect(self.ccliAddButton,
|
||||
QtCore.SIGNAL(u'clicked()'),
|
||||
self.onCCLIAddButtonClicked)
|
||||
QtCore.QObject.connect(self.ccliRemoveButton,
|
||||
QtCore.SIGNAL(u'clicked()'),
|
||||
self.onCCLIRemoveButtonClicked)
|
||||
QtCore.QObject.connect(self.songsOfFellowshipAddButton,
|
||||
QtCore.SIGNAL(u'clicked()'),
|
||||
self.onSongsOfFellowshipAddButtonClicked)
|
||||
@ -277,6 +283,16 @@ class ImportWizardForm(QtGui.QWizard, Ui_SongImportWizard):
|
||||
def onWordsOfWorshipRemoveButtonClicked(self):
|
||||
self.removeSelectedItems(self.wordsOfWorshipFileListWidget)
|
||||
|
||||
def onCCLIAddButtonClicked(self):
|
||||
self.getFiles(
|
||||
translate('SongsPlugin.ImportWizardForm',
|
||||
'Select CCLI Files'),
|
||||
self.ccliFileListWidget
|
||||
)
|
||||
|
||||
def onCCLIRemoveButtonClicked(self):
|
||||
self.removeSelectedItems(self.ccliFileListWidget)
|
||||
|
||||
def onSongsOfFellowshipAddButtonClicked(self):
|
||||
self.getFiles(
|
||||
translate('SongsPlugin.ImportWizardForm',
|
||||
|
310
openlp/plugins/songs/lib/cclifileimport.py
Executable file
310
openlp/plugins/songs/lib/cclifileimport.py
Executable file
@ -0,0 +1,310 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
|
||||
|
||||
###############################################################################
|
||||
# OpenLP - Open Source Lyrics Projection #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Copyright (c) 2008-2010 Raoul Snyman #
|
||||
# Portions copyright (c) 2008-2010 Tim Bentley, Jonathan Corwin, Michael #
|
||||
# Gorven, Scott Guerrieri, Meinert Jordan, Andreas Preikschat, Christian #
|
||||
# Richter, Philip Ridout, Maikel Stuivenberg, Martin Thompson, Jon Tibble, #
|
||||
# Carsten Tinggaard, Frode Woldsund, Derek Scotney #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# This program is free software; you can redistribute it and/or modify it #
|
||||
# under the terms of the GNU General Public License as published by the Free #
|
||||
# Software Foundation; version 2 of the License. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT #
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
|
||||
# more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License along #
|
||||
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||
###############################################################################
|
||||
|
||||
import logging
|
||||
import os
|
||||
import chardet
|
||||
import codecs
|
||||
|
||||
from songimport import SongImport
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
class CCLIFileImportError(Exception):
|
||||
pass
|
||||
|
||||
class CCLIFileImport(SongImport):
|
||||
"""
|
||||
The :class:`CCLIFileImport` class provides OpenLP with the
|
||||
ability to import CCLI SongSelect song files in both .txt and
|
||||
.usr formats. See http://www.ccli.com
|
||||
"""
|
||||
|
||||
def __init__(self, manager, **kwargs):
|
||||
"""
|
||||
Initialise the import.
|
||||
|
||||
``manager``
|
||||
The song manager for the running OpenLP installation.
|
||||
``filenames``
|
||||
The files to be imported.
|
||||
"""
|
||||
SongImport.__init__(self, manager)
|
||||
if u'filenames' in kwargs:
|
||||
self.filenames = kwargs[u'filenames']
|
||||
log.debug(self.filenames)
|
||||
else:
|
||||
raise KeyError(u'Keyword argument "filenames" not supplied.')
|
||||
|
||||
def do_import(self):
|
||||
"""
|
||||
Import either a .usr or a .txt SongSelect file
|
||||
"""
|
||||
log.debug(u'Starting CCLI File Import')
|
||||
song_total = len(self.filenames)
|
||||
self.import_wizard.importProgressBar.setMaximum(song_total)
|
||||
song_count = 1
|
||||
for filename in self.filenames:
|
||||
self.import_wizard.incrementProgressBar(
|
||||
u'Importing song %s of %s' % (song_count, song_total))
|
||||
filename = unicode(filename)
|
||||
log.debug(u'Importing CCLI File: %s', filename)
|
||||
lines = []
|
||||
if os.path.isfile(filename):
|
||||
detect_file = open(filename, u'r')
|
||||
details = chardet.detect(detect_file.read(2048))
|
||||
detect_file.close()
|
||||
infile = codecs.open(filename, u'r', details['encoding'])
|
||||
lines = infile.readlines()
|
||||
ext = os.path.splitext(filename)[1]
|
||||
if ext.lower() == ".usr":
|
||||
log.info(u'SongSelect .usr format file found %s: ' , filename)
|
||||
self.do_import_usr_file(lines)
|
||||
elif ext.lower() == ".txt":
|
||||
log.info(u'SongSelect .txt format file found %s: ', filename)
|
||||
self.do_import_txt_file(lines)
|
||||
else:
|
||||
log.info(u'Extension %s is not valid', filename)
|
||||
pass
|
||||
song_count += 1
|
||||
if self.stop_import_flag:
|
||||
return False
|
||||
return True
|
||||
|
||||
def do_import_usr_file(self, textList):
|
||||
"""
|
||||
The :method:`do_import_usr_file` method provides OpenLP
|
||||
with the ability to import CCLI SongSelect songs in
|
||||
*USR* file format
|
||||
|
||||
``textList``
|
||||
An array of strings containing the usr file content.
|
||||
|
||||
**SongSelect .usr file format**
|
||||
``[File]``
|
||||
USR file format first line
|
||||
``Type=``
|
||||
Indicates the file type
|
||||
e.g. *Type=SongSelect Import File*
|
||||
``Version=3.0``
|
||||
File format version
|
||||
``[S A2672885]``
|
||||
Contains the CCLI Song number e.g. *2672885*
|
||||
``Title=``
|
||||
Contains the song title (e.g. *Title=Above All*)
|
||||
``Author=``
|
||||
Contains a | delimited list of the song authors
|
||||
e.g. *Author=LeBlanc, Lenny | Baloche, Paul*
|
||||
``Copyright=``
|
||||
Contains a | delimited list of the song copyrights
|
||||
e.g. Copyright=1999 Integrity's Hosanna! Music |
|
||||
LenSongs Publishing (Verwaltet von Gerth Medien
|
||||
Musikverlag)
|
||||
``Admin=``
|
||||
Contains the song administrator
|
||||
e.g. *Admin=Gerth Medien Musikverlag*
|
||||
``Themes=``
|
||||
Contains a /t delimited list of the song themes
|
||||
e.g. *Themes=Cross/tKingship/tMajesty/tRedeemer*
|
||||
``Keys=``
|
||||
Contains the keys in which the music is played??
|
||||
e.g. *Keys=A*
|
||||
``Fields=``
|
||||
Contains a list of the songs fields in order /t delimited
|
||||
e.g. *Fields=Vers 1/tVers 2/tChorus 1/tAndere 1*
|
||||
``Words=``
|
||||
Contains the songs various lyrics in order as shown by the
|
||||
*Fields* description
|
||||
e.g. *Words=Above all powers....* [/n = CR, /n/t = CRLF]
|
||||
"""
|
||||
log.debug(u'USR file text: %s', textList)
|
||||
lyrics = []
|
||||
self.set_defaults()
|
||||
for line in textList:
|
||||
if line.startswith(u'Title='):
|
||||
song_name = line[6:].strip()
|
||||
elif line.startswith(u'Author='):
|
||||
song_author = line[7:].strip()
|
||||
elif line.startswith(u'Copyright='):
|
||||
song_copyright = line[10:].strip()
|
||||
elif line.startswith(u'[S A'):
|
||||
song_ccli = line[4:-3].strip()
|
||||
elif line.startswith(u'Fields='):
|
||||
#Fields contain single line indicating verse, chorus, etc,
|
||||
#/t delimited, same as with words field. store seperately
|
||||
#and process at end.
|
||||
song_fields = line[7:].strip()
|
||||
elif line.startswith(u'Words='):
|
||||
song_words = line[6:].strip()
|
||||
#Unhandled usr keywords:Type,Version,Admin,Themes,Keys
|
||||
#Process Fields and words sections
|
||||
field_list = song_fields.split(u'/t')
|
||||
words_list = song_words.split(u'/t')
|
||||
for counter in range(0, len(field_list)):
|
||||
if field_list[counter].startswith(u'Ver'):
|
||||
verse_type = u'V'
|
||||
elif field_list[counter].startswith(u'Ch'):
|
||||
verse_type = u'C'
|
||||
elif field_list[counter].startswith(u'Br'):
|
||||
verse_type = u'B'
|
||||
else: #Other
|
||||
verse_type = u'O'
|
||||
verse_text = unicode(words_list[counter])
|
||||
verse_text = verse_text.replace("/n", "\n")
|
||||
if len(verse_text) > 0:
|
||||
self.add_verse(verse_text, verse_type);
|
||||
#Handle multiple authors
|
||||
author_list = song_author.split(u'/')
|
||||
if len(author_list) < 2:
|
||||
author_list = song_author.split(u'|')
|
||||
for author in author_list:
|
||||
seperated = author.split(u',')
|
||||
self.add_author(seperated[1].strip() + " " + seperated[0].strip())
|
||||
self.title = song_name
|
||||
self.copyright = song_copyright
|
||||
self.ccli_number = song_ccli
|
||||
self.finish()
|
||||
|
||||
def do_import_txt_file(self, textList):
|
||||
"""
|
||||
The :method:`do_import_txt_file` method provides OpenLP
|
||||
with the ability to import CCLI SongSelect songs in
|
||||
*TXT* file format
|
||||
|
||||
``textList``
|
||||
An array of strings containing the txt file content.
|
||||
|
||||
**SongSelect .txt file format**
|
||||
|
||||
``Song Title``
|
||||
Contains the song title
|
||||
|
||||
<Empty line>
|
||||
|
||||
``Title of following verse/chorus and number``
|
||||
e.g. Verse 1, Chorus 1
|
||||
|
||||
``Verse/Chorus lyrics``
|
||||
|
||||
<Empty line>
|
||||
|
||||
<Empty line>
|
||||
|
||||
``Title of next verse/chorus (repeats)``
|
||||
|
||||
``Verse/Chorus lyrics``
|
||||
|
||||
<Empty line>
|
||||
|
||||
<Empty line>
|
||||
|
||||
``Song CCLI Number``
|
||||
e.g. CCLI Number (e.g.CCLI-Liednummer: 2672885)
|
||||
``Song Copyright``
|
||||
e.g. © 1999 Integrity's Hosanna! Music | LenSongs Publishing
|
||||
``Song Authors``
|
||||
e.g. Lenny LeBlanc | Paul Baloche
|
||||
``Licencing info``
|
||||
e.g. For use solely with the SongSelect Terms of Use.
|
||||
All rights Reserved. www.ccli.com
|
||||
``CCLI Licence number of user``
|
||||
e.g. CCL-Liedlizenznummer: 14 / CCLI License No. 14
|
||||
"""
|
||||
log.debug(u'TXT file text: %s', textList)
|
||||
self.set_defaults()
|
||||
line_number = 0
|
||||
verse_text = u''
|
||||
song_comments = u''
|
||||
song_copyright = u'';
|
||||
verse_start = False
|
||||
for line in textList:
|
||||
clean_line = line.strip()
|
||||
if not clean_line:
|
||||
if line_number==0:
|
||||
continue
|
||||
elif verse_start:
|
||||
if verse_text:
|
||||
self.add_verse(verse_text, verse_type)
|
||||
verse_text = ''
|
||||
verse_start = False
|
||||
else:
|
||||
#line_number=0, song title
|
||||
if line_number==0:
|
||||
song_name = clean_line
|
||||
line_number += 1
|
||||
#line_number=1, verses
|
||||
elif line_number==1:
|
||||
#line_number=1, ccli number, first line after verses
|
||||
if clean_line.startswith(u'CCLI'):
|
||||
line_number += 1
|
||||
ccli_parts = clean_line.split(' ')
|
||||
song_ccli = ccli_parts[len(ccli_parts)-1]
|
||||
elif not verse_start:
|
||||
# We have the verse descriptor
|
||||
verse_desc_parts = clean_line.split(' ')
|
||||
if len(verse_desc_parts) == 2:
|
||||
if verse_desc_parts[0].startswith(u'Ver'):
|
||||
verse_type = u'V'
|
||||
elif verse_desc_parts[0].startswith(u'Ch'):
|
||||
verse_type = u'C'
|
||||
elif verse_desc_parts[0].startswith(u'Br'):
|
||||
verse_type = u'B'
|
||||
else:
|
||||
verse_type = u'O'
|
||||
verse_number = verse_desc_parts[1]
|
||||
else:
|
||||
verse_type = u'O'
|
||||
verse_number = 1
|
||||
verse_start = True
|
||||
else:
|
||||
# We have verse content or the start of the
|
||||
# last part. Add l so as to keep the CRLF
|
||||
verse_text = verse_text + line
|
||||
else:
|
||||
#line_number=2, copyright
|
||||
if line_number==2:
|
||||
line_number += 1
|
||||
song_copyright = clean_line
|
||||
#n=3, authors
|
||||
elif line_number==3:
|
||||
line_number += 1
|
||||
song_author = clean_line
|
||||
#line_number=4, comments lines before last line
|
||||
elif (line_number==4) and (not clean_line.startswith(u'CCL')):
|
||||
song_comments = song_comments + clean_line
|
||||
# split on known separators
|
||||
author_list = song_author.split(u'/')
|
||||
if len(author_list) < 2:
|
||||
author_list = song_author.split(u'|')
|
||||
#Clean spaces before and after author names
|
||||
for author_name in author_list:
|
||||
self.add_author(author_name.strip())
|
||||
self.title = song_name
|
||||
self.copyright = song_copyright
|
||||
self.ccli_number = song_ccli
|
||||
self.comments = song_comments
|
||||
self.finish()
|
||||
|
@ -29,6 +29,7 @@ from olpimport import OpenLPSongImport
|
||||
try:
|
||||
from sofimport import SofImport
|
||||
from oooimport import OooImport
|
||||
from cclifileimport import CCLIFileImport
|
||||
from wowimport import WowImport
|
||||
except ImportError:
|
||||
pass
|
||||
@ -68,6 +69,8 @@ class SongFormat(object):
|
||||
return WowImport
|
||||
elif format == SongFormat.Generic:
|
||||
return OooImport
|
||||
elif format == SongFormat.CCLI:
|
||||
return CCLIFileImport
|
||||
# else:
|
||||
return None
|
||||
|
||||
|
@ -359,16 +359,13 @@ class SongMediaItem(MediaManagerItem):
|
||||
author_list = author_list + u', '
|
||||
author_list = author_list + unicode(author.display_name)
|
||||
author_audit.append(unicode(author.display_name))
|
||||
if song.ccli_number is None or len(song.ccli_number) == 0:
|
||||
ccli = QtCore.QSettings().value(u'general/ccli number',
|
||||
QtCore.QVariant(u'')).toString()
|
||||
else:
|
||||
ccli = unicode(song.ccli_number)
|
||||
raw_footer.append(song.title)
|
||||
raw_footer.append(author_list)
|
||||
raw_footer.append(song.copyright )
|
||||
raw_footer.append(unicode(
|
||||
translate('SongsPlugin.MediaItem', 'CCLI Licence: ') + ccli))
|
||||
translate('SongsPlugin.MediaItem', 'CCLI Licence: ') +
|
||||
QtCore.QSettings().value(u'general/ccli number',
|
||||
QtCore.QVariant(u'')).toString()))
|
||||
service_item.raw_footer = raw_footer
|
||||
service_item.audit = [
|
||||
song.title, author_audit, song.copyright, unicode(song.ccli_number)
|
||||
|
@ -28,6 +28,7 @@ import os
|
||||
|
||||
from PyQt4 import QtCore
|
||||
|
||||
from openlp.core.lib import Receiver
|
||||
from songimport import SongImport
|
||||
|
||||
if os.name == u'nt':
|
||||
@ -43,23 +44,32 @@ else:
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
class OooImport(object):
|
||||
class OooImport(SongImport):
|
||||
"""
|
||||
Import songs from Impress/Powerpoint docs using Impress
|
||||
"""
|
||||
def __init__(self, songmanager):
|
||||
def __init__(self, master_manager, **kwargs):
|
||||
"""
|
||||
Initialise the class. Requires a songmanager class which is passed
|
||||
to SongImport for writing song to disk
|
||||
"""
|
||||
SongImport.__init__(self, master_manager)
|
||||
self.song = None
|
||||
self.manager = songmanager
|
||||
self.master_manager = master_manager
|
||||
self.document = None
|
||||
self.process_started = False
|
||||
self.filenames = kwargs[u'filenames']
|
||||
QtCore.QObject.connect(Receiver.get_receiver(),
|
||||
QtCore.SIGNAL(u'song_stop_import'), self.stop_import)
|
||||
|
||||
def import_docs(self, filenames):
|
||||
def do_import(self):
|
||||
self.abort = False
|
||||
self.import_wizard.importProgressBar.setMaximum(0)
|
||||
self.start_ooo()
|
||||
for filename in filenames:
|
||||
for filename in self.filenames:
|
||||
if self.abort:
|
||||
self.import_wizard.incrementProgressBar(u'Import cancelled', 0)
|
||||
return
|
||||
filename = unicode(filename)
|
||||
if os.path.isfile(filename):
|
||||
self.open_ooo_file(filename)
|
||||
@ -72,6 +82,12 @@ class OooImport(object):
|
||||
self.process_doc()
|
||||
self.close_ooo_file()
|
||||
self.close_ooo()
|
||||
self.import_wizard.importProgressBar.setMaximum(1)
|
||||
self.import_wizard.incrementProgressBar(u'', 1)
|
||||
return True
|
||||
|
||||
def stop_import(self):
|
||||
self.abort = True
|
||||
|
||||
def start_ooo(self):
|
||||
"""
|
||||
@ -135,6 +151,9 @@ class OooImport(object):
|
||||
"com.sun.star.presentation.PresentationDocument") and not \
|
||||
self.document.supportsService("com.sun.star.text.TextDocument"):
|
||||
self.close_ooo_file()
|
||||
else:
|
||||
self.import_wizard.incrementProgressBar(
|
||||
u'Processing file ' + filepath, 0)
|
||||
except:
|
||||
pass
|
||||
return
|
||||
@ -161,6 +180,9 @@ class OooImport(object):
|
||||
slides = doc.getDrawPages()
|
||||
text = u''
|
||||
for slide_no in range(slides.getCount()):
|
||||
if self.abort:
|
||||
self.import_wizard.incrementProgressBar(u'Import cancelled', 0)
|
||||
return
|
||||
slide = slides.getByIndex(slide_no)
|
||||
slidetext = u''
|
||||
for idx in range(slide.getCount()):
|
||||
|
@ -68,19 +68,30 @@ class SofImport(OooImport):
|
||||
It attempts to detect italiced verses, and treats these as choruses in
|
||||
the verse ordering. Again not perfect, but a start.
|
||||
"""
|
||||
def __init__(self, songmanager):
|
||||
def __init__(self, master_manager, **kwargs):
|
||||
"""
|
||||
Initialise the class. Requires a songmanager class which is passed
|
||||
to SongImport for writing song to disk
|
||||
"""
|
||||
OooImport.__init__(self, songmanager)
|
||||
OooImport.__init__(self, master_manager, **kwargs)
|
||||
|
||||
def import_sof(self, filename):
|
||||
def do_import(self):
|
||||
self.abort = False
|
||||
self.start_ooo()
|
||||
self.open_ooo_file(filename)
|
||||
self.process_sof_file()
|
||||
self.close_ooo_file()
|
||||
for filename in self.filenames:
|
||||
if self.abort:
|
||||
self.import_wizard.incrementProgressBar(u'Import cancelled', 0)
|
||||
return
|
||||
filename = unicode(filename)
|
||||
if os.path.isfile(filename):
|
||||
self.open_ooo_file(filename)
|
||||
if self.document:
|
||||
self.process_sof_file()
|
||||
self.close_ooo_file()
|
||||
self.close_ooo()
|
||||
self.import_wizard.importProgressBar.setMaximum(1)
|
||||
self.import_wizard.incrementProgressBar(u'', 1)
|
||||
return True
|
||||
|
||||
def process_sof_file(self):
|
||||
"""
|
||||
@ -90,6 +101,9 @@ class SofImport(OooImport):
|
||||
self.new_song()
|
||||
paragraphs = self.document.getText().createEnumeration()
|
||||
while paragraphs.hasMoreElements():
|
||||
if self.abort:
|
||||
self.import_wizard.incrementProgressBar(u'Import cancelled', 0)
|
||||
return
|
||||
paragraph = paragraphs.nextElement()
|
||||
if paragraph.supportsService("com.sun.star.text.Paragraph"):
|
||||
self.process_paragraph(paragraph)
|
||||
@ -244,6 +258,7 @@ class SofImport(OooImport):
|
||||
if title.endswith(u','):
|
||||
title = title[:-1]
|
||||
self.song.title = title
|
||||
self.import_wizard.incrementProgressBar(u'Processing song ' + title, 0)
|
||||
|
||||
def add_author(self, text):
|
||||
"""
|
||||
|
@ -43,12 +43,6 @@ class SongImport(QtCore.QObject):
|
||||
whether the authors etc already exist and add them or refer to them
|
||||
as necessary
|
||||
"""
|
||||
|
||||
COPYRIGHT_STRING = unicode(translate(
|
||||
'SongsPlugin.SongImport', 'copyright'))
|
||||
COPYRIGHT_SYMBOL = unicode(translate(
|
||||
'SongsPlugin.SongImport', '\xa9'))
|
||||
|
||||
def __init__(self, manager):
|
||||
"""
|
||||
Initialise and create defaults for properties
|
||||
@ -58,6 +52,7 @@ class SongImport(QtCore.QObject):
|
||||
"""
|
||||
self.manager = manager
|
||||
self.stop_import_flag = False
|
||||
self.set_defaults()
|
||||
QtCore.QObject.connect(Receiver.get_receiver(),
|
||||
QtCore.SIGNAL(u'songs_stop_import'), self.stop_import)
|
||||
self.setDefaults()
|
||||
@ -82,6 +77,10 @@ class SongImport(QtCore.QObject):
|
||||
self.verses = []
|
||||
self.versecount = 0
|
||||
self.choruscount = 0
|
||||
self.copyright_string = unicode(translate(
|
||||
'SongsPlugin.SongImport', 'copyright'))
|
||||
self.copyright_symbol = unicode(translate(
|
||||
'SongsPlugin.SongImport', '\xa9'))
|
||||
|
||||
def stop_import(self):
|
||||
"""
|
||||
@ -135,13 +134,13 @@ class SongImport(QtCore.QObject):
|
||||
|
||||
def process_verse_text(self, text):
|
||||
lines = text.split(u'\n')
|
||||
if text.lower().find(COPYRIGHT_STRING) >= 0 \
|
||||
or text.lower().find(COPYRIGHT_SYMBOL) >= 0:
|
||||
if text.lower().find(self.copyright_string) >= 0 \
|
||||
or text.lower().find(self.copyright_symbol) >= 0:
|
||||
copyright_found = False
|
||||
for line in lines:
|
||||
if (copyright_found or
|
||||
line.lower().find(COPYRIGHT_STRING) >= 0 or
|
||||
line.lower().find(COPYRIGHT_SYMBOL) >= 0):
|
||||
line.lower().find(self.copyright_string) >= 0 or
|
||||
line.lower().find(self.copyright_symbol) >= 0):
|
||||
copyright_found = True
|
||||
self.add_copyright(line)
|
||||
else:
|
||||
@ -306,7 +305,7 @@ class SongImport(QtCore.QObject):
|
||||
topic = Topic.populate(name=topictext)
|
||||
song.topics.append(topic)
|
||||
self.manager.save_object(song)
|
||||
self.setDefaults()
|
||||
self.set_defaults()
|
||||
|
||||
def print_song(self):
|
||||
"""
|
||||
|
Loading…
Reference in New Issue
Block a user