', \
@@ -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
diff --git a/openlp/core/lib/htmlbuilder.py b/openlp/core/lib/htmlbuilder.py
index 00468608a..a39c3dac5 100644
--- a/openlp/core/lib/htmlbuilder.py
+++ b/openlp/core/lib/htmlbuilder.py
@@ -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"""
@@ -37,9 +42,9 @@ HTMLSRC = u"""
border: 0;
}
body {
- background-color: black;
+ %s;
}
-.dim {
+.size {
position: absolute;
left: 0px;
top: 0px;
@@ -51,6 +56,9 @@ body {
background-color: black;
display: none;
}
+#image {
+ z-index:1;
+}
#video {
z-index:2;
}
@@ -136,8 +144,12 @@ 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;
+ 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 +168,7 @@ body {
return 0;
}
if(position == ''){
- position = window.getComputedStyle(text, '').verticalAlign;
+ position = getComputedStyle(text, '').verticalAlign;
}
switch(position)
{
@@ -181,116 +193,67 @@ 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);
}
-
-
-
-
-
-
-
-
+
+
+%s
-
-
-
+
+
"""
-def build_html(item, screen, alert):
+def build_html(item, screen, alert, islive):
"""
Build the full web paged structure for display
@@ -300,92 +263,245 @@ def build_html(item, screen, alert):
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),
- u'true' if theme and theme.display_slideTransition \
+ 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;'
+ lyricsmain = u''
+ outline = u''
+ shadow = u''
if theme:
- lyricscommon = u'width: %spx; height: %spx; word-wrap: break-word; ' \
- u'font-family: %s; font-size: %spx; 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))
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_lyrics_outline_css(theme, is_shadow=False):
+ """
+ Build the css which controls the theme outline
+ Also used by renderer for splitting verses
-def build_footer(item):
+ `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''
+ if webkitvers < 533.3:
+ lyrics += u''
+ lyrics += u''
+ return lyrics
+
+def build_footer_css(item):
"""
Build the display of the item footer
@@ -398,7 +514,7 @@ def build_footer(item):
width: %spx;
height: %spx;
font-family: %s;
- font-size: %spx;
+ font-size: %spt;
color: %s;
text-align: %s;
"""
@@ -416,7 +532,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,10 +540,10 @@ 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: %spx;
+ font-size: %spt;
color: %s;
background-color: %s;
"""
diff --git a/openlp/core/lib/renderer.py b/openlp/core/lib/renderer.py
index 433018c23..330c434c5 100644
--- a/openlp/core/lib/renderer.py
+++ b/openlp/core/lib/renderer.py
@@ -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.
@@ -155,42 +143,35 @@ class Renderer(object):
for verse in verses_text:
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()
+ text.append(line)
+ 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'' \
+ u'' % \
+ (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'
'
+ 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
@@ -203,44 +184,47 @@ class Renderer(object):
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))
+ self.bg_frame = None
+# painter.fillRect(self.frame.rect(),
+# QtGui.QColor(self._theme.background_color))
elif self._theme.background_type == u'gradient':
+ self.bg_frame = None
# 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)
+# 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)
+# painter.end()
elif self._theme.background_type == u'image':
# image
+ 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()
+ painter.end()
diff --git a/openlp/core/lib/rendermanager.py b/openlp/core/lib/rendermanager.py
index 9caabdc69..6be26bd82 100644
--- a/openlp/core/lib/rendermanager.py
+++ b/openlp/core/lib/rendermanager.py
@@ -93,6 +93,7 @@ class RenderManager(object):
"""
self.global_theme = global_theme
self.theme_level = theme_level
+ self.themedata = None
def set_service_theme(self, service_theme):
"""
@@ -102,6 +103,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 +113,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 +143,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:
diff --git a/openlp/core/lib/serviceitem.py b/openlp/core/lib/serviceitem.py
index 134df0c42..0e8625ce7 100644
--- a/openlp/core/lib/serviceitem.py
+++ b/openlp/core/lib/serviceitem.py
@@ -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()),
diff --git a/openlp/core/lib/spelltextedit.py b/openlp/core/lib/spelltextedit.py
new file mode 100644
index 000000000..7d227079b
--- /dev/null
+++ b/openlp/core/lib/spelltextedit.py
@@ -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())))
diff --git a/openlp/core/ui/__init__.py b/openlp/core/ui/__init__.py
index dad79cb7b..33ba25046 100644
--- a/openlp/core/ui/__init__.py
+++ b/openlp/core/ui/__init__.py
@@ -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.
diff --git a/openlp/core/ui/maindisplay.py b/openlp/core/ui/maindisplay.py
index 98b7a84ec..2c9472414 100644
--- a/openlp/core/ui/maindisplay.py
+++ b/openlp/core/ui/maindisplay.py
@@ -115,8 +115,11 @@ 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()
@@ -135,7 +138,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(),
@@ -152,12 +155,12 @@ class MainDisplay(DisplayWidget):
splash_image)
serviceItem = ServiceItem()
serviceItem.bg_frame = initialFrame
- self.webView.setHtml(build_html(serviceItem, self.screen, \
- self.parent.alertTab))
+ self.webView.setHtml(build_html(serviceItem, self.screen,
+ self.parent.alertTab, self.isLive))
self.initialFrame = True
- self.show()
# To display or not to display?
if not self.screen[u'primary']:
+ self.show()
self.primary = False
else:
self.primary = True
@@ -297,13 +300,14 @@ class MainDisplay(DisplayWidget):
Generates a preview of the image displayed.
"""
log.debug(u'preview for %s', self.isLive)
- # Wait for the fade to finish before geting the preview.
- # Important otherwise preview will have incorrect text if at all !
- if self.serviceItem.themedata and \
- self.serviceItem.themedata.display_slideTransition:
- while self.frame.evaluateJavaScript(u'show_text_complete()') \
- .toString() == u'false':
- Receiver.send_message(u'openlp_process_events')
+ if self.isLive:
+ # Wait for the fade to finish before geting the preview.
+ # Important otherwise preview will have incorrect text if at all !
+ if self.serviceItem.themedata and \
+ self.serviceItem.themedata.display_slideTransition:
+ while self.frame.evaluateJavaScript(u'show_text_complete()') \
+ .toString() == u'false':
+ Receiver.send_message(u'openlp_process_events')
# Wait for the webview to update before geting the preview.
# Important otherwise first preview will miss the background !
while not self.loaded:
@@ -318,9 +322,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):
@@ -332,7 +333,8 @@ class MainDisplay(DisplayWidget):
self.loaded = False
self.initialFrame = False
self.serviceItem = serviceItem
- html = build_html(self.serviceItem, self.screen, self.parent.alertTab)
+ html = build_html(self.serviceItem, self.screen, self.parent.alertTab,\
+ self.isLive)
self.webView.setHtml(html)
if serviceItem.foot_text and serviceItem.foot_text:
self.footer(serviceItem.foot_text)
diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py
index f2961b717..c7d3c497f 100644
--- a/openlp/core/ui/servicemanager.py
+++ b/openlp/core/ui/servicemanager.py
@@ -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:
diff --git a/openlp/core/ui/thememanager.py b/openlp/core/ui/thememanager.py
index 5cd989a2a..11130293c 100644
--- a/openlp/core/ui/thememanager.py
+++ b/openlp/core/ui/thememanager.py
@@ -295,7 +295,7 @@ class ThemeManager(QtGui.QWidget):
path = unicode(path)
if path:
SettingsManager.set_last_dir(self.settingsSection, path, 1)
- themePath = os.path.join(path, theme + u'.thz')
+ themePath = os.path.join(path, theme + u'.otz')
zip = None
try:
zip = zipfile.ZipFile(themePath, u'w')
diff --git a/openlp/plugins/bibles/lib/csvbible.py b/openlp/plugins/bibles/lib/csvbible.py
index 7c0ba6b2b..cc981059c 100644
--- a/openlp/plugins/bibles/lib/csvbible.py
+++ b/openlp/plugins/bibles/lib/csvbible.py
@@ -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)
diff --git a/openlp/plugins/bibles/lib/mediaitem.py b/openlp/plugins/bibles/lib/mediaitem.py
index ab38b5f83..e7850c65c 100644
--- a/openlp/plugins/bibles/lib/mediaitem.py
+++ b/openlp/plugins/bibles/lib/mediaitem.py
@@ -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.
@@ -466,19 +465,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 +498,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 +506,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 +567,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 +581,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 +639,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 +654,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)
diff --git a/openlp/plugins/custom/forms/editcustomdialog.py b/openlp/plugins/custom/forms/editcustomdialog.py
index d8557b5e2..84a310cb9 100644
--- a/openlp/plugins/custom/forms/editcustomdialog.py
+++ b/openlp/plugins/custom/forms/editcustomdialog.py
@@ -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):
diff --git a/openlp/plugins/songs/forms/editversedialog.py b/openlp/plugins/songs/forms/editversedialog.py
index 64d56322f..43dc8b96d 100644
--- a/openlp/plugins/songs/forms/editversedialog.py
+++ b/openlp/plugins/songs/forms/editversedialog.py
@@ -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):
diff --git a/openlp/plugins/songs/forms/songimportform.py b/openlp/plugins/songs/forms/songimportform.py
index 68bba299d..f2a59ba81 100644
--- a/openlp/plugins/songs/forms/songimportform.py
+++ b/openlp/plugins/songs/forms/songimportform.py
@@ -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)
@@ -160,7 +166,7 @@ class ImportWizardForm(QtGui.QWizard, Ui_SongImportWizard):
self.openSongAddButton.setFocus()
return False
elif source_format == SongFormat.WordsOfWorship:
- if self.wordsOfWorshipListWidget.count() == 0:
+ if self.wordsOfWorshipFileListWidget.count() == 0:
QtGui.QMessageBox.critical(self,
translate('SongsPlugin.ImportWizardForm',
'No Words of Worship Files Selected'),
@@ -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',
@@ -315,6 +331,7 @@ class ImportWizardForm(QtGui.QWizard, Ui_SongImportWizard):
pass
def setDefaults(self):
+ self.restart()
self.formatComboBox.setCurrentIndex(0)
self.openLP2FilenameEdit.setText(u'')
self.openLP1FilenameEdit.setText(u'')
diff --git a/openlp/plugins/songs/lib/cclifileimport.py b/openlp/plugins/songs/lib/cclifileimport.py
new file mode 100755
index 000000000..08bccef79
--- /dev/null
+++ b/openlp/plugins/songs/lib/cclifileimport.py
@@ -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
+
+
+
+ ``Title of following verse/chorus and number``
+ e.g. Verse 1, Chorus 1
+
+ ``Verse/Chorus lyrics``
+
+
+
+
+
+ ``Title of next verse/chorus (repeats)``
+
+ ``Verse/Chorus lyrics``
+
+
+
+
+
+ ``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()
+
diff --git a/openlp/plugins/songs/lib/importer.py b/openlp/plugins/songs/lib/importer.py
index 7410a5184..5801ea44a 100644
--- a/openlp/plugins/songs/lib/importer.py
+++ b/openlp/plugins/songs/lib/importer.py
@@ -29,6 +29,8 @@ from olpimport import OpenLPSongImport
try:
from sofimport import SofImport
from oooimport import OooImport
+ from cclifileimport import CCLIFileImport
+ from wowimport import WowImport
except ImportError:
pass
@@ -63,8 +65,12 @@ class SongFormat(object):
return OpenSongImport
elif format == SongFormat.SongsOfFellowship:
return SofImport
+ elif format == SongFormat.WordsOfWorship:
+ return WowImport
elif format == SongFormat.Generic:
return OooImport
+ elif format == SongFormat.CCLI:
+ return CCLIFileImport
# else:
return None
diff --git a/openlp/plugins/songs/lib/songimport.py b/openlp/plugins/songs/lib/songimport.py
index 60cdfd969..bf5079c8c 100644
--- a/openlp/plugins/songs/lib/songimport.py
+++ b/openlp/plugins/songs/lib/songimport.py
@@ -76,7 +76,7 @@ class SongImport(QtCore.QObject):
'SongsPlugin.SongImport', 'copyright'))
self.copyright_symbol = unicode(translate(
'SongsPlugin.SongImport', '\xa9'))
-
+
def stop_import(self):
"""
Sets the flag for importers to stop their import
@@ -129,13 +129,13 @@ class SongImport(QtCore.QObject):
def process_verse_text(self, text):
lines = text.split(u'\n')
- if text.lower().find(self.copyright_string) >= 0 \
- or text.lower().find(self.copyright_symbol) >= 0:
+ if text.lower().find(COPYRIGHT_STRING) >= 0 \
+ or text.lower().find(COPYRIGHT_SYMBOL) >= 0:
copyright_found = False
for line in lines:
if (copyright_found or
- line.lower().find(self.copyright_string) >= 0 or
- line.lower().find(self.copyright_symbol) >= 0):
+ line.lower().find(COPYRIGHT_STRING) >= 0 or
+ line.lower().find(COPYRIGHT_SYMBOL) >= 0):
copyright_found = True
self.add_copyright(line)
else:
@@ -300,6 +300,7 @@ class SongImport(QtCore.QObject):
topic = Topic.populate(name=topictext)
song.topics.append(topic)
self.manager.save_object(song)
+ self.setDefaults()
def print_song(self):
"""
diff --git a/openlp/plugins/songs/lib/wowimport.py b/openlp/plugins/songs/lib/wowimport.py
new file mode 100644
index 000000000..dd09034d1
--- /dev/null
+++ b/openlp/plugins/songs/lib/wowimport.py
@@ -0,0 +1,173 @@
+# -*- 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 #
+###############################################################################
+"""
+The :mod:`wowimport` module provides the functionality for importing Words of
+Worship songs into the OpenLP database.
+"""
+import os
+import logging
+
+from openlp.plugins.songs.lib.songimport import SongImport
+
+BLOCK_TYPES = (u'V', u'C', u'B')
+
+log = logging.getLogger(__name__)
+
+class WowImport(SongImport):
+ """
+ The :class:`WowImport` class provides the ability to import song files from
+ Words of Worship.
+
+ Words Of Worship Song File Format
+ `````````````````````````````````
+
+ The Words Of Worship song file format is as follows:
+
+ * The song title is the file name minus the extension.
+ * The song has a header, a number of blocks, followed by footer containing
+ the author and the copyright.
+ * A block can be a verse, chorus or bridge.
+
+ File Header:
+ Bytes are counted from one, i.e. the first byte is byte 1. These bytes,
+ up to the 56 byte, can change but no real meaning has been found. The
+ 56th byte specifies how many blocks there are. The first block starts
+ with byte 83 after the "CSongDoc::CBlock" declaration.
+
+ Blocks:
+ Each block has a starting header, some lines of text, and an ending
+ footer. Each block starts with 4 bytes, the first byte specifies how
+ many lines are in that block, the next three bytes are null bytes.
+
+ Each block ends with 4 bytes, the first of which defines what type of
+ block it is, and the rest which are null bytes:
+
+ * ``NUL`` (\x00) - Verse
+ * ``SOH`` (\x01) - Chorus
+ * ``STX`` (\x02) - Bridge
+
+ Blocks are seperated by two bytes. The first byte is ``SOH`` (\x01),
+ and the second byte is ``€`` (\x80).
+
+ Lines:
+ Each line starts with a byte which specifies how long that line is,
+ the line text, and ends with a null byte.
+
+
+ Footer:
+ The footer follows on after the last block, the first byte specifies
+ the length of the author text, followed by the author text, if
+ this byte is null, then there is no author text. The byte after the
+ author text specifies the length of the copyright text, followed
+ by the copyright text.
+
+ The file is ended with four null bytes.
+
+ Valid extensions for a Words of Worship song file are:
+
+ * .wsg
+ * .wow-song
+ """
+
+ def __init__(self, master_manager, **kwargs):
+ """
+ Initialise the import.
+
+ ``master_manager``
+ The song manager for the running OpenLP installation.
+ """
+ SongImport.__init__(self, master_manager)
+ self.master_manager = master_manager
+ if kwargs.has_key(u'filename'):
+ self.import_source = kwargs[u'filename']
+ if kwargs.has_key(u'filenames'):
+ self.import_source = kwargs[u'filenames']
+ log.debug(self.import_source)
+
+ def do_import(self):
+ """
+ Recieve a single file, or a list of files to import.
+ """
+
+ if isinstance(self.import_source, list):
+ self.import_wizard.importProgressBar.setMaximum(
+ len(self.import_source))
+ for file in self.import_source:
+ # TODO: check that it is a valid words of worship file (could
+ # check header for WoW File Song Word)
+ self.author = u''
+ self.copyright= u''
+ # Get the song title
+ self.file_name = os.path.split(file)[1]
+ self.import_wizard.incrementProgressBar(
+ "Importing %s" % (self.file_name), 0)
+ self.title = self.file_name.rpartition(u'.')[0]
+ self.songData = open(file, 'rb')
+ # Seek to byte which stores number of blocks in the song
+ self.songData.seek(56)
+ self.no_of_blocks = ord(self.songData.read(1))
+ # Seek to the beging of the first block
+ self.songData.seek(82)
+ for block in range(self.no_of_blocks):
+ self.lines_to_read = ord(self.songData.read(1))
+ # Skip 3 nulls to the beginnig of the 1st line
+ self.songData.seek(3, os.SEEK_CUR)
+ self.block_text = u''
+ while self.lines_to_read:
+ self.length_of_line = ord(self.songData.read(1))
+ self.line_text = unicode(
+ self.songData.read(self.length_of_line), u'cp1252')
+ self.songData.seek(1, os.SEEK_CUR)
+ if self.block_text != u'':
+ self.block_text += u'\n'
+ self.block_text += self.line_text
+ self.lines_to_read -= 1
+ self.block_type = BLOCK_TYPES[ord(self.songData.read(1))]
+ # Skip 3 nulls at the end of the block
+ self.songData.seek(3, os.SEEK_CUR)
+ # Blocks are seperated by 2 bytes, skip them, but not if
+ # this is the last block!
+ if (block + 1) < self.no_of_blocks:
+ self.songData.seek(2, os.SEEK_CUR)
+ self.add_verse(self.block_text, self.block_type)
+ # Now to extact the author
+ self.author_length = ord(self.songData.read(1))
+ if self.author_length != 0:
+ self.author = unicode(
+ self.songData.read(self.author_length), u'cp1252')
+ # Finally the copyright
+ self.copyright_length = ord(self.songData.read(1))
+ if self.copyright_length != 0:
+ self.copyright = unicode(
+ self.songData.read(self.copyright_length), u'cp1252')
+ self.parse_author(self.author)
+ self.add_copyright(self.copyright)
+ self.songData.close()
+ self.finish()
+ self.import_wizard.incrementProgressBar(
+ "Importing %s" % (self.file_name))
+ return True
+