This commit is contained in:
Jonathan Corwin 2011-04-29 09:22:18 +01:00
commit ff4a9802e8
61 changed files with 1256 additions and 949 deletions

View File

@ -1,3 +1,5 @@
.. _dualmonitors:
==================
Dual Monitor Setup
==================
@ -32,6 +34,14 @@ projector hooked up to your computer as the second monitor. With the option of
extending your desktop across the second monitor, or your operating system's
equivalent.
**Special Note For Projectors Using USB Connections**
Users have reported experiencing difficulties when using a projector with a USB
connection, as third party software is often required to properly configure
dual monitors. If possible, it is best to use a direct output (VGA, DVI, HDMI,
S-Video) from your machine's video card. If a USB connection is your only option
please consult the manufacturer's manual for instructions on a proper setup.
Microsoft Windows
-----------------

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

View File

@ -175,3 +175,23 @@ only download the section you search for. If you do not have an internet
connection where you intend to use OpenLP you will need another scripture
source. For more information about acquiring Bibles please see :ref:`bibleimporter`.
OpenLP is using a large amount of RAM when showing a presentation
-----------------------------------------------------------------
OpenLP uses a large amount of RAM when showing a presentation due to the way it
handles presentations. OpenLP itself is unable to show those presentations or
load the presentation files, so it interacts with the presentation through
either Microsoft PowerPoint or LibreOffice Impress. In order to show the slides
in the slide controller, OpenLP requests that the presentation application
export the slides to images, and then uses those images as slides. This results
in a large amount of RAM being used, especially in presentations with more than
about 20 slides.
OpenLP is not displaying correctly, or is not on the correct screen
-------------------------------------------------------------------
Please read the documentation on :ref:`dualmonitors`. It is very important to
have dual monitors setup properly for OpenLP to function as expected.

View File

@ -289,6 +289,5 @@ from htmlbuilder import build_html, build_lyrics_format_css, \
from toolbar import OpenLPToolbar
from dockwidget import OpenLPDockWidget
from renderer import Renderer
from rendermanager import RenderManager
from mediamanageritem import MediaManagerItem
from openlp.core.utils.actions import ActionList

View File

@ -161,7 +161,7 @@ class Plugin(QtCore.QObject):
self.log = logging.getLogger(self.name)
self.previewController = plugin_helpers[u'preview']
self.liveController = plugin_helpers[u'live']
self.renderManager = plugin_helpers[u'render']
self.renderer = plugin_helpers[u'renderer']
self.serviceManager = plugin_helpers[u'service']
self.settingsForm = plugin_helpers[u'settings form']
self.mediadock = plugin_helpers[u'toolbox']
@ -359,4 +359,4 @@ class Plugin(QtCore.QObject):
use of the singular name of the plugin object so must only be called
after this has been set.
"""
self.textStrings[name] = {u'title': title, u'tooltip': tooltip}
self.textStrings[name] = {u'title': title, u'tooltip': tooltip}

View File

@ -23,46 +23,260 @@
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
The :mod:`renderer` module enables OpenLP to take the input from plugins and
format it for the output display.
"""
import logging
from PyQt4 import QtWebKit
from PyQt4 import QtCore, QtWebKit
from openlp.core.lib import expand_tags, build_lyrics_format_css, \
build_lyrics_outline_css, Receiver
from openlp.core.lib import ServiceItem, ImageManager, expand_tags, \
build_lyrics_format_css, build_lyrics_outline_css, Receiver, \
ItemCapabilities
from openlp.core.lib.theme import ThemeLevel
from openlp.core.ui import MainDisplay
log = logging.getLogger(__name__)
VERSE = u'The Lord said to {r}Noah{/r}: \n' \
'There\'s gonna be a {su}floody{/su}, {sb}floody{/sb}\n' \
'The Lord said to {g}Noah{/g}:\n' \
'There\'s gonna be a {st}floody{/st}, {it}floody{/it}\n' \
'Get those children out of the muddy, muddy \n' \
'{r}C{/r}{b}h{/b}{bl}i{/bl}{y}l{/y}{g}d{/g}{pk}' \
'r{/pk}{o}e{/o}{pp}n{/pp} of the Lord\n'
FOOTER = [u'Arky Arky (Unknown)', u'Public Domain', u'CCLI 123456']
HTML_END = u'</div></body></html>'
class Renderer(object):
"""
Genarates a pixmap image of a array of text. The Text is formatted to
make sure it fits on the screen and if not extra frames are generated.
Class to pull all Renderer interactions into one place. The plugins will
call helper methods to do the rendering but this class will provide
display defense code.
``theme_manager``
The ThemeManager instance, used to get the current theme details.
``screens``
Contains information about the Screens.
``screen_number``
Defaults to *0*. The index of the output/display screen.
"""
log.info(u'Renderer Loaded')
def __init__(self):
def __init__(self, theme_manager, screens):
"""
Initialise the renderer.
Initialise the render manager.
"""
self._rect = None
self.theme_name = None
self._theme = None
log.debug(u'Initilisation started')
self.screens = screens
self.image_manager = ImageManager()
self.display = MainDisplay(self, screens, False)
self.display.imageManager = self.image_manager
self.theme_manager = theme_manager
self.service_theme = u''
self.theme_level = u''
self.override_background = None
self.theme_data = None
self.force_page = False
def set_theme(self, theme):
def update_display(self):
"""
Set the theme to be used.
Updates the render manager's information about the current screen.
"""
log.debug(u'Update Display')
self._calculate_default(self.screens.current[u'size'])
self.display = MainDisplay(self, self.screens, False)
self.display.imageManager = self.image_manager
self.display.setup()
self.bg_frame = None
self.theme_data = None
self.image_manager.update_display(self.width, self.height)
def set_global_theme(self, global_theme, theme_level=ThemeLevel.Global):
"""
Set the global-level theme and the theme level.
``global_theme``
The global-level theme to be set.
``theme_level``
Defaults to *``ThemeLevel.Global``*. The theme level, can be
``ThemeLevel.Global``, ``ThemeLevel.Service`` or
``ThemeLevel.Song``.
"""
self.global_theme = global_theme
self.theme_level = theme_level
self.global_theme_data = \
self.theme_manager.getThemeData(self.global_theme)
self.theme_data = None
def set_service_theme(self, service_theme):
"""
Set the service-level theme.
``service_theme``
The service-level theme to be set.
"""
self.service_theme = service_theme
self.theme_data = None
def set_override_theme(self, override_theme, override_levels=False):
"""
Set the appropriate theme depending on the theme level.
Called by the service item when building a display frame
``theme``
The theme to be used.
"""
log.debug(u'set theme')
self._theme = theme
self.theme_name = theme.theme_name
The name of the song-level theme. None means the service
item wants to use the given value.
def set_text_rectangle(self, rect_main, rect_footer):
``override_levels``
Used to force the theme data passed in to be used.
"""
log.debug(u'set override theme to %s', override_theme)
theme_level = self.theme_level
if override_levels:
theme_level = ThemeLevel.Song
if theme_level == ThemeLevel.Global:
theme = self.global_theme
elif theme_level == ThemeLevel.Service:
if self.service_theme == u'':
theme = self.global_theme
else:
theme = self.service_theme
else:
# Images have a theme of -1
if override_theme and override_theme != -1:
theme = override_theme
elif theme_level == ThemeLevel.Song or \
theme_level == ThemeLevel.Service:
if self.service_theme == u'':
theme = self.global_theme
else:
theme = self.service_theme
else:
theme = self.global_theme
log.debug(u'theme is now %s', theme)
# Force the theme to be the one passed in.
if override_levels:
self.theme_data = override_theme
else:
self.theme_data = self.theme_manager.getThemeData(theme)
self._calculate_default(self.screens.current[u'size'])
self._build_text_rectangle(self.theme_data)
self.image_manager.add_image(self.theme_data.theme_name,
self.theme_data.background_filename)
return self._rect, self._rect_footer
def generate_preview(self, theme_data, force_page=False):
"""
Generate a preview of a theme.
``theme_data``
The theme to generated a preview for.
``force_page``
Flag to tell message lines per page need to be generated.
"""
log.debug(u'generate preview')
# save value for use in format_slide
self.force_page = force_page
# set the default image size for previews
self._calculate_default(self.screens.preview[u'size'])
# build a service item to generate preview
serviceItem = ServiceItem()
serviceItem.theme = theme_data
if self.force_page:
# make big page for theme edit dialog to get line count
serviceItem.add_from_text(u'', VERSE + VERSE + VERSE, FOOTER)
else:
self.image_manager.del_image(theme_data.theme_name)
serviceItem.add_from_text(u'', VERSE, FOOTER)
serviceItem.renderer = self
serviceItem.raw_footer = FOOTER
serviceItem.render(True)
if not self.force_page:
self.display.buildHtml(serviceItem)
raw_html = serviceItem.get_rendered_frame(0)
preview = self.display.text(raw_html)
# Reset the real screen size for subsequent render requests
self._calculate_default(self.screens.current[u'size'])
return preview
def format_slide(self, text, line_break, item):
"""
Calculate how much text can fit on a slide.
``text``
The words to go on the slides.
``line_break``
Add line endings after each line of text used for bibles.
"""
log.debug(u'format slide')
# clean up line endings
lines = self._lines_split(text)
pages = self._paginate_slide(lines, line_break, self.force_page)
if len(pages) > 1:
# Songs and Custom
if item.is_capable(ItemCapabilities.AllowsVirtualSplit):
# Do not forget the line breaks !
slides = text.split(u'\n[---]\n')
pages = []
for slide in slides:
lines = self._lines(slide)
new_pages = self._paginate_slide(lines, line_break,
self.force_page)
pages.extend([page for page in new_pages])
# Bibles
elif item.is_capable(ItemCapabilities.AllowsWordSplit):
pages = self._paginate_slide_words(text, line_break)
return pages
def _calculate_default(self, screen):
"""
Calculate the default dimentions of the screen.
``screen``
The QSize of the screen.
"""
log.debug(u'calculate default %s', screen)
self.width = screen.width()
self.height = screen.height()
self.screen_ratio = float(self.height) / float(self.width)
log.debug(u'calculate default %d, %d, %f',
self.width, self.height, self.screen_ratio)
# 90% is start of footer
self.footer_start = int(self.height * 0.90)
def _build_text_rectangle(self, theme):
"""
Builds a text block using the settings in ``theme``
and the size of the display screen.height.
Note the system has a 10 pixel border round the screen
``theme``
The theme to build a text block for.
"""
log.debug(u'_build_text_rectangle')
main_rect = None
footer_rect = None
if not theme.font_main_override:
main_rect = QtCore.QRect(10, 0, self.width - 20, self.footer_start)
else:
main_rect = QtCore.QRect(theme.font_main_x, theme.font_main_y,
theme.font_main_width - 1, theme.font_main_height - 1)
if not theme.font_footer_override:
footer_rect = QtCore.QRect(10, self.footer_start, self.width - 20,
self.height - self.footer_start)
else:
footer_rect = QtCore.QRect(theme.font_footer_x,
theme.font_footer_y, theme.font_footer_width - 1,
theme.font_footer_height - 1)
self._set_text_rectangle(main_rect, footer_rect)
def _set_text_rectangle(self, rect_main, rect_footer):
"""
Sets the rectangle within which text should be rendered.
@ -77,9 +291,9 @@ class Renderer(object):
self._rect_footer = rect_footer
self.page_width = self._rect.width()
self.page_height = self._rect.height()
if self._theme.font_main_shadow:
self.page_width -= int(self._theme.font_main_shadow_size)
self.page_height -= int(self._theme.font_main_shadow_size)
if self.theme_data.font_main_shadow:
self.page_width -= int(self.theme_data.font_main_shadow_size)
self.page_height -= int(self.theme_data.font_main_shadow_size)
self.web = QtWebKit.QWebView()
self.web.setVisible(False)
self.web.resize(self.page_width, self.page_height)
@ -89,59 +303,163 @@ class Renderer(object):
u'*{margin: 0; padding: 0; border: 0;} '\
u'#main {position:absolute; top:0px; %s %s}</style><body>' \
u'<div id="main">' % \
(build_lyrics_format_css(self._theme, self.page_width,
self.page_height), build_lyrics_outline_css(self._theme))
(build_lyrics_format_css(self.theme_data, self.page_width,
self.page_height), build_lyrics_outline_css(self.theme_data))
def format_slide(self, words, line_break, force_page=False):
def _paginate_slide(self, lines, line_break, force_page=False):
"""
Figure out how much text can appear on a slide, using the current
theme settings.
``words``
The words to be fitted on the slide.
``lines``
The words to be fitted on the slide split into lines.
``line_break``
Add line endings after each line of text used for bibles.
Add line endings after each line of text (used for bibles).
``force_page``
Flag to tell message lines in page.
"""
log.debug(u'format_slide - Start')
log.debug(u'_paginate_slide - Start')
line_end = u''
if line_break:
line_end = u'<br>'
words = words.replace(u'\r\n', u'\n')
verses_text = words.split(u'\n')
text = []
for verse in verses_text:
lines = verse.split(u'\n')
for line in lines:
text.append(line)
formatted = []
html_text = u''
styled_text = u''
line_count = 0
for line in text:
for line in lines:
if line_count != -1:
line_count += 1
styled_line = expand_tags(line) + line_end
styled_text += styled_line
html = self.page_shell + styled_text + u'</div></body></html>'
html = self.page_shell + styled_text + HTML_END
self.web.setHtml(html)
# Text too long so go to next page
if self.web_frame.contentsSize().height() > self.page_height:
if force_page and line_count > 0:
Receiver.send_message(u'theme_line_count', line_count)
line_count = -1
if html_text.endswith(u'<br>'):
html_text = html_text[:len(html_text)-4]
html_text = html_text.rstrip(u'<br>')
formatted.append(html_text)
html_text = u''
styled_text = styled_line
html_text += line + line_end
if html_text.endswith(u'<br>'):
html_text = html_text[:len(html_text)-4]
html_text = html_text.rstrip(u'<br>')
formatted.append(html_text)
log.debug(u'format_slide - End')
log.debug(u'_paginate_slide - End')
return formatted
def _paginate_slide_words(self, text, line_break):
"""
Figure out how much text can appear on a slide, using the current
theme settings. This version is to handle text which needs to be split
into words to get it to fit.
``text``
The words to be fitted on the slide split into lines.
``line_break``
Add line endings after each line of text used for bibles.
"""
log.debug(u'_paginate_slide_words - Start')
line_end = u''
if line_break:
line_end = u'<br>'
formatted = []
previous_html = u''
previous_raw = u''
lines = self._lines(text)
for line in lines:
styled_line = expand_tags(line)
html = self.page_shell + previous_html + styled_line + HTML_END
self.web.setHtml(html)
# Text too long so go to next page
if self.web_frame.contentsSize().height() > self.page_height:
# Check if there was a verse before the current one and append
# it, when it fits on the page.
if previous_html:
html = self.page_shell + previous_html + HTML_END
self.web.setHtml(html)
if self.web_frame.contentsSize().height() <= \
self.page_height:
previous_raw = previous_raw.rstrip(u'<br>')
formatted.append(previous_raw)
previous_html = u''
previous_raw = u''
html = self.page_shell + styled_line + HTML_END
self.web.setHtml(html)
# Now check if the current verse will fit, if it does
# not we have to start to process the verse word by
# word.
if self.web_frame.contentsSize().height() <= \
self.page_height:
previous_html = styled_line + line_end
previous_raw = line + line_end
continue
words = self._words_split(line)
for word in words:
styled_word = expand_tags(word)
html = self.page_shell + previous_html + styled_word + \
HTML_END
self.web.setHtml(html)
# Text too long so go to next page
if self.web_frame.contentsSize().height() > \
self.page_height:
previous_raw = previous_raw.rstrip(u'<br>')
formatted.append(previous_raw)
previous_html = u''
previous_raw = u''
previous_html += styled_word
previous_raw += word
previous_html += line_end
previous_raw += line_end
else:
previous_html += styled_line + line_end
previous_raw += line + line_end
previous_raw = previous_raw.rstrip(u'<br>')
formatted.append(previous_raw)
log.debug(u'_paginate_slide_words - End')
return formatted
def _lines(self, text):
"""
Split the slide up by physical line
"""
# this parse we do not want to use this so remove it
verses_text = text.split(u'\n')
text = []
for verse in verses_text:
lines = verse.split(u'\n')
text.extend([line for line in lines])
return text
def _words_split(self, line):
"""
Split the slide up by word so can wrap better
"""
# this parse we are to be wordy
line = line.replace(u'\n', u' ')
verses_text = line.split(u' ')
text = []
for verse in verses_text:
lines = verse.split(u' ')
text.extend([line + u' ' for line in lines])
return text
def _lines_split(self, text):
"""
Split the slide up by physical line
"""
# this parse we do not want to use this so remove it
text = text.replace(u'\n[---]', u'')
lines = text.split(u'\n')
real_lines = []
for line in lines:
line = line.replace(u'[---]', u'')
sub_lines = line.split(u'\n')
real_lines.extend([sub_line for sub_line in sub_lines])
return real_lines

View File

@ -1,261 +0,0 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2011 Raoul Snyman #
# Portions copyright (c) 2008-2011 Tim Bentley, Jonathan Corwin, Michael #
# Gorven, Scott Guerrieri, Matthias Hub, Meinert Jordan, Armin Köhler, #
# Andreas Preikschat, Mattias Põldaru, Christian Richter, Philip Ridout, #
# Maikel Stuivenberg, Martin Thompson, Jon Tibble, 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 logging
from PyQt4 import QtCore
from openlp.core.lib import Renderer, ServiceItem, ImageManager
from openlp.core.lib.theme import ThemeLevel
from openlp.core.ui import MainDisplay
log = logging.getLogger(__name__)
VERSE = u'The Lord said to {r}Noah{/r}: \n' \
'There\'s gonna be a {su}floody{/su}, {sb}floody{/sb}\n' \
'The Lord said to {g}Noah{/g}:\n' \
'There\'s gonna be a {st}floody{/st}, {it}floody{/it}\n' \
'Get those children out of the muddy, muddy \n' \
'{r}C{/r}{b}h{/b}{bl}i{/bl}{y}l{/y}{g}d{/g}{pk}' \
'r{/pk}{o}e{/o}{pp}n{/pp} of the Lord\n'
FOOTER = [u'Arky Arky (Unknown)', u'Public Domain', u'CCLI 123456']
class RenderManager(object):
"""
Class to pull all Renderer interactions into one place. The plugins will
call helper methods to do the rendering but this class will provide
display defense code.
``theme_manager``
The ThemeManager instance, used to get the current theme details.
``screens``
Contains information about the Screens.
``screen_number``
Defaults to *0*. The index of the output/display screen.
"""
log.info(u'RenderManager Loaded')
def __init__(self, theme_manager, screens):
"""
Initialise the render manager.
"""
log.debug(u'Initilisation started')
self.screens = screens
self.image_manager = ImageManager()
self.display = MainDisplay(self, screens, False)
self.display.imageManager = self.image_manager
self.theme_manager = theme_manager
self.renderer = Renderer()
self.calculate_default(self.screens.current[u'size'])
self.theme = u''
self.service_theme = u''
self.theme_level = u''
self.override_background = None
self.theme_data = None
self.force_page = False
def update_display(self):
"""
Updates the render manager's information about the current screen.
"""
log.debug(u'Update Display')
self.calculate_default(self.screens.current[u'size'])
self.display = MainDisplay(self, self.screens, False)
self.display.imageManager = self.image_manager
self.display.setup()
self.renderer.bg_frame = None
self.theme_data = None
self.image_manager.update_display(self.width, self.height)
def set_global_theme(self, global_theme, theme_level=ThemeLevel.Global):
"""
Set the global-level theme and the theme level.
``global_theme``
The global-level theme to be set.
``theme_level``
Defaults to *``ThemeLevel.Global``*. The theme level, can be
``ThemeLevel.Global``, ``ThemeLevel.Service`` or
``ThemeLevel.Song``.
"""
self.global_theme = global_theme
self.theme_level = theme_level
self.global_theme_data = \
self.theme_manager.getThemeData(self.global_theme)
self.theme_data = None
def set_service_theme(self, service_theme):
"""
Set the service-level theme.
``service_theme``
The service-level theme to be set.
"""
self.service_theme = service_theme
self.theme_data = None
def set_override_theme(self, theme, overrideLevels=False):
"""
Set the appropriate theme depending on the theme level.
Called by the service item when building a display frame
``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
if overrideLevels:
theme_level = ThemeLevel.Song
if theme_level == ThemeLevel.Global:
self.theme = self.global_theme
elif theme_level == ThemeLevel.Service:
if self.service_theme == u'':
self.theme = self.global_theme
else:
self.theme = self.service_theme
else:
# Images have a theme of -1
if theme and theme != -1:
self.theme = theme
elif theme_level == ThemeLevel.Song or \
theme_level == ThemeLevel.Service:
if self.service_theme == u'':
self.theme = self.global_theme
else:
self.theme = self.service_theme
else:
self.theme = self.global_theme
if self.theme != self.renderer.theme_name or self.theme_data 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.theme_data = theme
else:
self.theme_data = self.theme_manager.getThemeData(self.theme)
self.calculate_default(self.screens.current[u'size'])
self.renderer.set_theme(self.theme_data)
self.build_text_rectangle(self.theme_data)
self.image_manager.add_image(self.theme_data.theme_name,
self.theme_data.background_filename)
return self.renderer._rect, self.renderer._rect_footer
def build_text_rectangle(self, theme):
"""
Builds a text block using the settings in ``theme``
and the size of the display screen.height.
``theme``
The theme to build a text block for.
"""
log.debug(u'build_text_rectangle')
main_rect = None
footer_rect = None
if not theme.font_main_override:
main_rect = QtCore.QRect(10, 0, self.width - 20, self.footer_start)
else:
main_rect = QtCore.QRect(theme.font_main_x, theme.font_main_y,
theme.font_main_width - 1, theme.font_main_height - 1)
if not theme.font_footer_override:
footer_rect = QtCore.QRect(10, self.footer_start, self.width - 20,
self.height - self.footer_start)
else:
footer_rect = QtCore.QRect(theme.font_footer_x,
theme.font_footer_y, theme.font_footer_width - 1,
theme.font_footer_height - 1)
self.renderer.set_text_rectangle(main_rect, footer_rect)
def generate_preview(self, theme_data, force_page=False):
"""
Generate a preview of a theme.
``theme_data``
The theme to generated a preview for.
``force_page``
Flag to tell message lines per page need to be generated.
"""
log.debug(u'generate preview')
# save value for use in format_slide
self.force_page = force_page
# set the default image size for previews
self.calculate_default(self.screens.preview[u'size'])
# build a service item to generate preview
serviceItem = ServiceItem()
serviceItem.theme = theme_data
if self.force_page:
# make big page for theme edit dialog to get line count
serviceItem.add_from_text(u'', VERSE + VERSE + VERSE, FOOTER)
else:
self.image_manager.del_image(theme_data.theme_name)
serviceItem.add_from_text(u'', VERSE, FOOTER)
serviceItem.render_manager = self
serviceItem.raw_footer = FOOTER
serviceItem.render(True)
if not self.force_page:
self.display.buildHtml(serviceItem)
raw_html = serviceItem.get_rendered_frame(0)
preview = self.display.text(raw_html)
# Reset the real screen size for subsequent render requests
self.calculate_default(self.screens.current[u'size'])
return preview
def format_slide(self, words, line_break):
"""
Calculate how much text can fit on a slide.
``words``
The words to go on the slides.
``line_break``
Add line endings after each line of text used for bibles.
"""
log.debug(u'format slide')
return self.renderer.format_slide(words, line_break, self.force_page)
def calculate_default(self, screen):
"""
Calculate the default dimentions of the screen.
``screen``
The QSize of the screen.
"""
log.debug(u'calculate default %s', screen)
self.width = screen.width()
self.height = screen.height()
self.screen_ratio = float(self.height) / float(self.width)
log.debug(u'calculate default %d, %d, %f',
self.width, self.height, self.screen_ratio)
# 90% is start of footer
self.footer_start = int(self.height * 0.90)

View File

@ -63,6 +63,8 @@ class ItemCapabilities(object):
ProvidesOwnDisplay = 10
AllowsDetailedTitleDisplay = 11
AllowsVariableStartTime = 12
AllowsVirtualSplit = 13
AllowsWordSplit = 14
class ServiceItem(object):
@ -81,7 +83,7 @@ class ServiceItem(object):
The plugin that this service item belongs to.
"""
if plugin:
self.render_manager = plugin.renderManager
self.renderer = plugin.renderer
self.name = plugin.name
self.title = u''
self.shortname = u''
@ -151,7 +153,7 @@ class ServiceItem(object):
self.icon = icon
self.iconic_representation = build_icon(icon)
def render(self, useOverride=False):
def render(self, use_override=False):
"""
The render method is what generates the frames for the screen and
obtains the display information from the renderemanager.
@ -161,24 +163,23 @@ class ServiceItem(object):
log.debug(u'Render called')
self._display_frames = []
self.bg_image_bytes = None
line_break = True
if self.is_capable(ItemCapabilities.NoLineBreaks):
line_break = False
line_break = not self.is_capable(ItemCapabilities.NoLineBreaks)
theme = self.theme if self.theme else None
self.main, self.footer = \
self.render_manager.set_override_theme(theme, useOverride)
self.themedata = self.render_manager.renderer._theme
self.renderer.set_override_theme(theme, use_override)
self.themedata = self.renderer.theme_data
if self.service_item_type == ServiceItemType.Text:
log.debug(u'Formatting slides')
for slide in self._raw_frames:
formatted = self.render_manager \
.format_slide(slide[u'raw_slide'], line_break)
formatted = self.renderer \
.format_slide(slide[u'raw_slide'], line_break, self)
for page in formatted:
self._display_frames.append(
{u'title': clean_tags(page),
self._display_frames.append({
u'title': clean_tags(page),
u'text': clean_tags(page.rstrip()),
u'html': expand_tags(page.rstrip()),
u'verseTag': slide[u'verseTag'] })
u'verseTag': slide[u'verseTag']
})
elif self.service_item_type == ServiceItemType.Image or \
self.service_item_type == ServiceItemType.Command:
pass
@ -205,7 +206,7 @@ class ServiceItem(object):
"""
self.service_item_type = ServiceItemType.Image
self._raw_frames.append({u'title': title, u'path': path})
self.render_manager.image_manager.add_image(title, path)
self.renderer.image_manager.add_image(title, path)
self._new_item()
def add_from_text(self, title, raw_slide, verse_tag=None):
@ -453,4 +454,5 @@ class ServiceItem(object):
elif not start and end:
return end
else:
return u'%s : %s' % (start, end)
return u'%s : %s' % (start, end)

View File

@ -637,4 +637,4 @@ class ThemeXML(object):
self.font_footer_shadow_size)
self.add_display(self.display_horizontal_align,
self.display_vertical_align,
self.display_slide_transition)
self.display_slide_transition)

View File

@ -41,11 +41,11 @@ class AdvancedTab(SettingsTab):
"""
Initialise the settings tab
"""
generalTranslated = translate('AdvancedTab', 'Advanced')
SettingsTab.__init__(self, parent ,u'Advanced', generalTranslated)
advancedTranslated = translate('OpenLP.AdvancedTab', 'Advanced')
self.default_image = u':/graphics/openlp-splash-screen.png'
self.default_color = u'#ffffff'
self.icon_path = u':/system/system_settings.png'
SettingsTab.__init__(self, parent, u'Advanced', advancedTranslated)
def setupUi(self):
"""
@ -82,14 +82,6 @@ class AdvancedTab(SettingsTab):
u'enableAutoCloseCheckBox')
self.uiLayout.addRow(self.enableAutoCloseCheckBox)
self.leftLayout.addWidget(self.uiGroupBox)
self.hideMouseGroupBox = QtGui.QGroupBox(self.leftColumn)
self.hideMouseGroupBox.setObjectName(u'hideMouseGroupBox')
self.hideMouseLayout = QtGui.QVBoxLayout(self.hideMouseGroupBox)
self.hideMouseLayout.setObjectName(u'hideMouseLayout')
self.hideMouseCheckBox = QtGui.QCheckBox(self.hideMouseGroupBox)
self.hideMouseCheckBox.setObjectName(u'hideMouseCheckBox')
self.hideMouseLayout.addWidget(self.hideMouseCheckBox)
self.leftLayout.addWidget(self.hideMouseGroupBox)
self.leftLayout.addStretch()
self.defaultImageGroupBox = QtGui.QGroupBox(self.rightColumn)
self.defaultImageGroupBox.setObjectName(u'defaultImageGroupBox')
@ -109,26 +101,42 @@ class AdvancedTab(SettingsTab):
self.defaultBrowseButton.setObjectName(u'defaultBrowseButton')
self.defaultBrowseButton.setIcon(
build_icon(u':/general/general_open.png'))
self.defaultRevertButton = QtGui.QToolButton(self.defaultImageGroupBox)
self.defaultRevertButton.setObjectName(u'defaultRevertButton')
self.defaultRevertButton.setIcon(
build_icon(u':/general/general_revert.png'))
self.defaultFileLayout = QtGui.QHBoxLayout()
self.defaultFileLayout.setObjectName(u'defaultFileLayout')
self.defaultFileLayout.addWidget(self.defaultFileEdit)
self.defaultFileLayout.addWidget(self.defaultBrowseButton)
self.defaultFileLayout.addWidget(self.defaultRevertButton)
self.defaultImageLayout.addRow(self.defaultFileLabel,
self.defaultFileLayout)
self.rightLayout.addWidget(self.defaultImageGroupBox)
self.hideMouseGroupBox = QtGui.QGroupBox(self.leftColumn)
self.hideMouseGroupBox.setObjectName(u'hideMouseGroupBox')
self.hideMouseLayout = QtGui.QVBoxLayout(self.hideMouseGroupBox)
self.hideMouseLayout.setObjectName(u'hideMouseLayout')
self.hideMouseCheckBox = QtGui.QCheckBox(self.hideMouseGroupBox)
self.hideMouseCheckBox.setObjectName(u'hideMouseCheckBox')
self.hideMouseLayout.addWidget(self.hideMouseCheckBox)
self.rightLayout.addWidget(self.hideMouseGroupBox)
self.rightLayout.addStretch()
QtCore.QObject.connect(self.defaultColorButton,
QtCore.SIGNAL(u'pressed()'), self.onDefaultColorButtonPressed)
QtCore.QObject.connect(self.defaultBrowseButton,
QtCore.SIGNAL(u'pressed()'), self.onDefaultBrowseButtonPressed)
QtCore.QObject.connect(self.defaultRevertButton,
QtCore.SIGNAL(u'pressed()'), self.onDefaultRevertButtonPressed)
def retranslateUi(self):
"""
Setup the interface translation strings.
"""
self.tabTitleVisible = UiStrings().Advanced
self.uiGroupBox.setTitle(translate('OpenLP.AdvancedTab', 'UI Settings'))
self.uiGroupBox.setTitle(
translate('OpenLP.AdvancedTab', 'UI Settings'))
self.recentLabel.setText(
translate('OpenLP.AdvancedTab',
'Number of recent files to display:'))
@ -150,8 +158,14 @@ class AdvancedTab(SettingsTab):
'Default Image'))
self.defaultColorLabel.setText(translate('OpenLP.AdvancedTab',
'Background color:'))
self.defaultColorButton.setToolTip(translate('OpenLP.AdvancedTab',
'Click to select a color.'))
self.defaultFileLabel.setText(translate('OpenLP.AdvancedTab',
'Image file:'))
self.defaultBrowseButton.setToolTip(translate('OpenLP.AdvancedTab',
'Browse for an image file to display.'))
self.defaultRevertButton.setToolTip(translate('OpenLP.AdvancedTab',
'Revert to the default OpenLP logo.'))
def load(self):
"""
@ -232,4 +246,8 @@ class AdvancedTab(SettingsTab):
file_filters)
if filename:
self.defaultFileEdit.setText(filename)
self.defaultFileEdit.setFocus()
self.defaultFileEdit.setFocus()
def onDefaultRevertButtonPressed(self):
self.defaultFileEdit.setText(u':/graphics/openlp-splash-screen.png')
self.defaultFileEdit.setFocus()

View File

@ -34,7 +34,7 @@ from PyQt4 import QtCore, QtGui, QtWebKit
from PyQt4.phonon import Phonon
from openlp.core.lib import Receiver, build_html, ServiceItem, image_to_byte, \
build_icon, translate
translate
from openlp.core.ui import HideMode
@ -69,8 +69,6 @@ class MainDisplay(DisplayWidget):
self.hideMode = None
self.videoHide = False
self.override = {}
mainIcon = build_icon(u':/icon/openlp-logo-16x16.png')
self.setWindowIcon(mainIcon)
self.retranslateUi()
self.setStyleSheet(u'border: 0px; margin: 0px; padding: 0px;')
self.setWindowFlags(QtCore.Qt.FramelessWindowHint | QtCore.Qt.Tool |
@ -144,7 +142,8 @@ class MainDisplay(DisplayWidget):
image_file = QtCore.QSettings().value(u'advanced/default image',
QtCore.QVariant(u':/graphics/openlp-splash-screen.png'))\
.toString()
background_color = QtGui.QColor(QtCore.QSettings().value(
background_color = QtGui.QColor()
background_color.setNamedColor(QtCore.QSettings().value(
u'advanced/default color',
QtCore.QVariant(u'#ffffff')).toString())
if not background_color.isValid():

View File

@ -30,7 +30,7 @@ from tempfile import gettempdir
from PyQt4 import QtCore, QtGui
from openlp.core.lib import RenderManager, build_icon, OpenLPDockWidget, \
from openlp.core.lib import Renderer, build_icon, OpenLPDockWidget, \
SettingsManager, PluginManager, Receiver, translate
from openlp.core.lib.ui import UiStrings, base_action, checkable_action, \
icon_action, shortcut_action
@ -71,7 +71,7 @@ class Ui_MainWindow(object):
mainWindow.setObjectName(u'MainWindow')
mainWindow.resize(self.settingsmanager.width,
self.settingsmanager.height)
mainWindow.setWindowIcon(build_icon(u':/icon/openlp-logo-16x16.png'))
mainWindow.setWindowIcon(build_icon(u':/icon/openlp-logo-64x64.png'))
mainWindow.setDockNestingEnabled(True)
# Set up the main container, which contains all the other form widgets.
self.MainContent = QtGui.QWidget(mainWindow)
@ -545,9 +545,9 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
QtCore.SIGNAL(u'openlp_information_message'),
self.onInformationMessage)
# warning cyclic dependency
# RenderManager needs to call ThemeManager and
# ThemeManager needs to call RenderManager
self.renderManager = RenderManager(
# renderer needs to call ThemeManager and
# ThemeManager needs to call Renderer
self.renderer = Renderer(
self.themeManagerContents, self.screens)
# Define the media Dock Manager
self.mediaDockManager = MediaDockManager(self.MediaToolBox)
@ -555,7 +555,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
# make the controllers available to the plugins
self.pluginHelpers[u'preview'] = self.previewController
self.pluginHelpers[u'live'] = self.liveController
self.pluginHelpers[u'render'] = self.renderManager
self.pluginHelpers[u'renderer'] = self.renderer
self.pluginHelpers[u'service'] = self.ServiceManagerContents
self.pluginHelpers[u'settings form'] = self.settingsForm
self.pluginHelpers[u'toolbox'] = self.mediaDockManager
@ -781,7 +781,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
their locations
"""
log.debug(u'screenChanged')
self.renderManager.update_display()
self.renderer.update_display()
self.setFocus()
self.activateWindow()

View File

@ -46,41 +46,58 @@ http://doc.trolltech.com/4.7/richtext-html-subset.html#css-properties
color:black;
}
.item {
color:black;
}
.itemTitle {
font-weight:600;
font-size:large;
color:black;
}
.itemText {
color:black;
}
.itemText {}
.itemFooter {
font-size:8px;
color:black;
}
.itemNotes {}
.itemNotesTitle {
font-weight:bold;
font-size:12px;
color:black;
}
.itemNotesText {
font-size:11px;
color:black;
}
.media {}
.mediaTitle {
font-weight:bold;
font-size:11px;
}
.mediaText {}
.imageList {}
.customNotes {
margin-top: 10px;
}
.customNotesTitle {
font-weight:bold;
font-size:11px;
color:black;
}
.customNotesText {
font-size:11px;
color:black;
}
.newPage {
page-break-before:always;
}
"""
@ -153,86 +170,90 @@ class PrintServiceForm(QtGui.QDialog, Ui_PrintServiceDialog):
"""
Creates the html text and updates the html of *self.document*.
"""
html_data = html.fromstring(
u'<title>%s</title>' % unicode(self.titleLineEdit.text()))
html_data = self._addElement(u'html')
self._addElement(u'head', parent=html_data)
self._addElement(u'title', unicode(self.titleLineEdit.text()),
html_data.head)
css_path = os.path.join(
AppLocation.get_data_path(), u'servicePrint.css')
if not os.path.isfile(css_path):
# Create default css file.
css_file = open(css_path, u'w')
css_file.write(DEFAULT_CSS)
css_file.close()
AppLocation.get_data_path(), u'service_print.css')
custom_css = get_text_file_string(css_path)
self._addChildToParent(
u'style', custom_css, html_data.head, u'type', u'text/css')
self._addChildToParent(u'body', parent=html_data)
self._addChildToParent(u'span', unicode(self.titleLineEdit.text()),
html_data.body, u'class', u'serviceTitle')
if not custom_css:
custom_css = DEFAULT_CSS
self._addElement(u'style', custom_css, html_data.head,
attribute=(u'type', u'text/css'))
self._addElement(u'body', parent=html_data)
self._addElement(u'h1', unicode(self.titleLineEdit.text()),
html_data.body, classId=u'serviceTitle')
for index, item in enumerate(self.serviceManager.serviceItems):
item = item[u'service_item']
div = self._addChildToParent(u'div', parent=html_data.body)
# Add the title of the service item.
item_title = self._addChildToParent(
u'h2', parent=div, attribute=u'class', value=u'itemTitle')
self._addChildToParent(
u'img', parent=item_title, attribute=u'src', value=item.icon)
self._fromstring(
u'<span> %s</span>' % item.get_display_title(), item_title)
if self.slideTextCheckBox.isChecked():
# Add the text of the service item.
if item.is_text():
verse_def = None
for slide in item.get_frames():
if not verse_def or verse_def != slide[u'verseTag']:
p = self._addChildToParent(u'p', parent=div,
attribute=u'class', value=u'itemText')
else:
self._addChildToParent(u'br', parent=p)
self._fromstring(u'<span>%s</span>' % slide[u'html'], p)
verse_def = slide[u'verseTag']
# Break the page before the div element.
if index != 0 and self.pageBreakAfterText.isChecked():
div.set(u'style', u'page-break-before:always')
# Add the image names of the service item.
elif item.is_image():
ol = self._addChildToParent(u'ol', parent=div)
for slide in range(len(item.get_frames())):
self._addChildToParent(u'li', item.get_frame_title(slide), ol)
# add footer
if item.foot_text:
self._fromstring(
item.foot_text, div, u'class', u'itemFooter')
# Add service items' notes.
if self.notesCheckBox.isChecked():
if item.notes:
p = self._addChildToParent(u'p', parent=div)
self._addChildToParent(u'span', unicode(
translate('OpenLP.ServiceManager', 'Notes:')), p,
u'class', u'itemNotesTitle')
self._fromstring(u'<span> %s</span>' % item.notes.replace(
u'\n', u'<br />'), p, u'class', u'itemNotesText')
# Add play length of media files.
if item.is_media() and self.metaDataCheckBox.isChecked():
tme = item.media_length
if item.end_time > 0:
tme = item.end_time - item.start_time
title = self._fromstring(u'<p><strong>%s</strong> </p>' %
translate('OpenLP.ServiceManager', 'Playing time:'), div)
self._fromstring(u'<span>%s</span>' %
unicode(datetime.timedelta(seconds=tme)), title)
self._addPreviewItem(html_data.body, item[u'service_item'], index)
# Add the custom service notes:
if self.footerTextEdit.toPlainText():
div = self._addChildToParent(u'div', parent=html_data.body)
self._addChildToParent(u'span', translate('OpenLP.ServiceManager',
u'Custom Service Notes:'), div, u'class', u'customNotesTitle')
self._addChildToParent(
u'span', u' %s' % self.footerTextEdit.toPlainText(), div,
u'class', u'customNotesText')
div = self._addElement(u'div', parent=html_data.body,
classId=u'customNotes')
self._addElement(u'span', translate('OpenLP.ServiceManager',
'Custom Service Notes: '), div, classId=u'customNotesTitle')
self._addElement(u'span', self.footerTextEdit.toPlainText(), div,
classId=u'customNotesText')
self.document.setHtml(html.tostring(html_data))
self.previewWidget.updatePreview()
def _addChildToParent(self, tag, text=None, parent=None, attribute=None,
value=None):
def _addPreviewItem(self, body, item, index):
div = self._addElement(u'div', classId=u'item', parent=body)
# Add the title of the service item.
item_title = self._addElement(u'h2', parent=div, classId=u'itemTitle')
self._addElement(u'img', parent=item_title,
attribute=(u'src', item.icon))
self._addElement(u'span', u'&nbsp;' + item.get_display_title(),
item_title)
if self.slideTextCheckBox.isChecked():
# Add the text of the service item.
if item.is_text():
verse_def = None
for slide in item.get_frames():
if not verse_def or verse_def != slide[u'verseTag']:
p = self._addElement(u'div', parent=div,
classId=u'itemText')
else:
self._addElement(u'br', parent=p)
self._addElement(u'p', slide[u'html'], p)
verse_def = slide[u'verseTag']
# Break the page before the div element.
if index != 0 and self.pageBreakAfterText.isChecked():
div.set(u'class', u'item newPage')
# Add the image names of the service item.
elif item.is_image():
ol = self._addElement(u'ol', parent=div, classId=u'imageList')
for slide in range(len(item.get_frames())):
self._addElement(u'li', item.get_frame_title(slide), ol)
# add footer
foot_text = item.foot_text
foot_text = foot_text.partition(u'<br>')[2]
if foot_text:
foot = self._addElement(u'div', foot_text, parent=div,
classId=u'itemFooter')
# Add service items' notes.
if self.notesCheckBox.isChecked():
if item.notes:
p = self._addElement(u'div', classId=u'itemNotes', parent=div)
self._addElement(u'span',
translate('OpenLP.ServiceManager', 'Notes: '), p,
classId=u'itemNotesTitle')
notes = self._addElement(u'span',
item.notes.replace(u'\n', u'<br />'), p,
classId=u'itemNotesText')
# Add play length of media files.
if item.is_media() and self.metaDataCheckBox.isChecked():
tme = item.media_length
if item.end_time > 0:
tme = item.end_time - item.start_time
title = self._addElement(u'div', classId=u'media', parent=div)
self._addElement(u'span', translate('OpenLP.ServiceManager',
'Playing time: '), title, classId=u'mediaTitle')
self._addElement(u'span', unicode(datetime.timedelta(seconds=tme)),
title, classId=u'mediaText')
def _addElement(self, tag, text=None, parent=None, classId=None,
attribute=None):
"""
Creates a html element. If ``text`` is given, the element's text will
set and if a ``parent`` is given, the element is appended.
@ -246,30 +267,22 @@ class PrintServiceForm(QtGui.QDialog, Ui_PrintServiceDialog):
``parent``
The parent element. Defaults to ``None``.
``attribute``
An optional attribute, for instance ``u'class``.
``classId``
Value for the class attribute
``value``
The value for the given ``attribute``. It does not have a meaning,
if the attribute is left to its default.
``attribute``
Tuple name/value pair to add as an optional attribute
"""
element = html.Element(tag)
if text is not None:
element.text = unicode(text)
element = html.fragment_fromstring(unicode(text), create_parent=tag)
else:
element = html.Element(tag)
if parent is not None:
parent.append(element)
if classId is not None:
element.set(u'class', classId)
if attribute is not None:
element.set(attribute, value if value is not None else u'')
return element
def _fromstring(self, string, parent, attribute=None, value=None):
"""
This is used to create a child html element from a string.
"""
element = html.fromstring(string)
if attribute is not None:
element.set(attribute, value if value is not None else u'')
parent.append(element)
element.set(attribute[0], attribute[1])
return element
def paintRequested(self, printer):
@ -380,4 +393,4 @@ class PrintServiceForm(QtGui.QDialog, Ui_PrintServiceDialog):
QtCore.QVariant(self.metaDataCheckBox.isChecked()))
settings.setValue(u'print notes',
QtCore.QVariant(self.notesCheckBox.isChecked()))
settings.endGroup()
settings.endGroup()

View File

@ -579,7 +579,7 @@ class ServiceManager(QtGui.QWidget):
for item in items:
serviceItem = ServiceItem()
serviceItem.from_service = True
serviceItem.render_manager = self.mainwindow.renderManager
serviceItem.renderer = self.mainwindow.renderer
serviceItem.set_from_service(item, self.servicePath)
self.validateItem(serviceItem)
self.addServiceItem(serviceItem)
@ -686,6 +686,7 @@ class ServiceManager(QtGui.QWidget):
self.serviceItems[item][u'service_item'].notes = \
self.serviceNoteForm.textEdit.toPlainText()
self.repaintServiceList(item, -1)
self.setModified()
def onStartTimeForm(self):
item = self.findServiceItem()[0]
@ -789,7 +790,7 @@ class ServiceManager(QtGui.QWidget):
# Top Item was selected so set the last one
if setLastItem:
lastItem.setSelected(True)
self.setModified(True)
self.setModified()
def onMoveSelectionDown(self):
"""
@ -811,7 +812,7 @@ class ServiceManager(QtGui.QWidget):
serviceIterator += 1
if setSelected:
firstItem.setSelected(True)
self.setModified(True)
self.setModified()
def onCollapseAll(self):
"""
@ -855,7 +856,7 @@ class ServiceManager(QtGui.QWidget):
self.serviceItems.remove(self.serviceItems[item])
self.serviceItems.insert(0, temp)
self.repaintServiceList(0, child)
self.setModified(True)
self.setModified()
def onServiceUp(self):
"""
@ -867,7 +868,7 @@ class ServiceManager(QtGui.QWidget):
self.serviceItems.remove(self.serviceItems[item])
self.serviceItems.insert(item - 1, temp)
self.repaintServiceList(item - 1, child)
self.setModified(True)
self.setModified()
def onServiceDown(self):
"""
@ -879,7 +880,7 @@ class ServiceManager(QtGui.QWidget):
self.serviceItems.remove(self.serviceItems[item])
self.serviceItems.insert(item + 1, temp)
self.repaintServiceList(item + 1, child)
self.setModified(True)
self.setModified()
def onServiceEnd(self):
"""
@ -891,7 +892,7 @@ class ServiceManager(QtGui.QWidget):
self.serviceItems.remove(self.serviceItems[item])
self.serviceItems.insert(len(self.serviceItems), temp)
self.repaintServiceList(len(self.serviceItems) - 1, child)
self.setModified(True)
self.setModified()
def onDeleteFromService(self):
"""
@ -901,7 +902,7 @@ class ServiceManager(QtGui.QWidget):
if item != -1:
self.serviceItems.remove(self.serviceItems[item])
self.repaintServiceList(item - 1, -1)
self.setModified(True)
self.setModified()
def repaintServiceList(self, serviceItem, serviceItemChild):
"""
@ -988,7 +989,7 @@ class ServiceManager(QtGui.QWidget):
"""
log.debug(u'onThemeComboBoxSelected')
self.service_theme = unicode(self.themeComboBox.currentText())
self.mainwindow.renderManager.set_service_theme(self.service_theme)
self.mainwindow.renderer.set_service_theme(self.service_theme)
QtCore.QSettings().setValue(
self.mainwindow.serviceSettingsSection + u'/service theme',
QtCore.QVariant(self.service_theme))
@ -1000,7 +1001,7 @@ class ServiceManager(QtGui.QWidget):
sure the theme combo box is in the correct state.
"""
log.debug(u'themeChange')
if self.mainwindow.renderManager.theme_level == ThemeLevel.Global:
if self.mainwindow.renderer.theme_level == ThemeLevel.Global:
self.toolbar.actions[u'ThemeLabel'].setVisible(False)
self.toolbar.actions[u'ThemeWidget'].setVisible(False)
else:
@ -1015,7 +1016,7 @@ class ServiceManager(QtGui.QWidget):
Receiver.send_message(u'cursor_busy')
log.debug(u'regenerateServiceItems')
# force reset of renderer as theme data has changed
self.mainwindow.renderManager.themedata = None
self.mainwindow.renderer.themedata = None
if self.serviceItems:
tempServiceItems = self.serviceItems
self.serviceManagerList.clear()
@ -1026,7 +1027,7 @@ class ServiceManager(QtGui.QWidget):
item[u'service_item'], False, expand=item[u'expanded'])
# Set to False as items may have changed rendering
# does not impact the saved song so True may also be valid
self.setModified(True)
self.setModified()
Receiver.send_message(u'cursor_normal')
def serviceItemUpdate(self, message):
@ -1037,7 +1038,7 @@ class ServiceManager(QtGui.QWidget):
for item in self.serviceItems:
if item[u'service_item']._uuid == uuid:
item[u'service_item'].edit_id = int(editId)
self.setModified(True)
self.setModified()
def replaceServiceItem(self, newItem):
"""
@ -1053,7 +1054,7 @@ class ServiceManager(QtGui.QWidget):
self.repaintServiceList(itemcount + 1, 0)
self.mainwindow.liveController.replaceServiceManagerItem(
newItem)
self.setModified(True)
self.setModified()
def addServiceItem(self, item, rebuild=False, expand=None, replace=False):
"""
@ -1098,7 +1099,7 @@ class ServiceManager(QtGui.QWidget):
if rebuild:
self.mainwindow.liveController.replaceServiceManagerItem(item)
self.dropPosition = 0
self.setModified(True)
self.setModified()
def makePreview(self):
"""
@ -1235,7 +1236,7 @@ class ServiceManager(QtGui.QWidget):
self.serviceItems.remove(serviceItem)
self.serviceItems.insert(endpos, serviceItem)
self.repaintServiceList(endpos, child)
self.setModified(True)
self.setModified()
else:
# we are not over anything so drop
replace = False
@ -1277,7 +1278,7 @@ class ServiceManager(QtGui.QWidget):
self.onThemeChangeAction, context=QtCore.Qt.WidgetShortcut)
self.themeMenu.addAction(action)
find_and_set_in_combo_box(self.themeComboBox, self.service_theme)
self.mainwindow.renderManager.set_service_theme(self.service_theme)
self.mainwindow.renderer.set_service_theme(self.service_theme)
self.regenerateServiceItems()
def onThemeChangeAction(self):

View File

@ -29,6 +29,7 @@ import re
from PyQt4 import QtCore, QtGui
from openlp.core.lib import Receiver
from openlp.core.utils import translate
from openlp.core.utils.actions import ActionList
from shortcutlistdialog import Ui_ShortcutListDialog
@ -95,43 +96,7 @@ class ShortcutListForm(QtGui.QDialog, Ui_ShortcutListDialog):
QtCore.Qt.ShiftModifier:
key_string = u'Shift+' + key_string
key_sequence = QtGui.QKeySequence(key_string)
# The action we are attempting to change.
changing_action = self._currentItemAction()
shortcut_valid = True
for category in self.action_list.categories:
for action in category.actions:
shortcuts = self._actionShortcuts(action)
if key_sequence not in shortcuts:
continue
if action is changing_action:
if self.primaryPushButton.isChecked() and \
shortcuts.index(key_sequence) == 0:
continue
if self.alternatePushButton.isChecked() and \
shortcuts.index(key_sequence) == 1:
continue
# Have the same parent, thus they cannot have the same shortcut.
if action.parent() is changing_action.parent():
shortcut_valid = False
# The new shortcut is already assigned, but if both shortcuts
# are only valid in a different widget the new shortcut is
# vaild, because they will not interfere.
if action.shortcutContext() in [QtCore.Qt.WindowShortcut,
QtCore.Qt.ApplicationShortcut]:
shortcut_valid = False
if changing_action.shortcutContext() in \
[QtCore.Qt.WindowShortcut, QtCore.Qt.ApplicationShortcut]:
shortcut_valid = False
if not shortcut_valid:
QtGui.QMessageBox.warning(self,
translate('OpenLP.ShortcutListDialog', 'Duplicate Shortcut'),
unicode(translate('OpenLP.ShortcutListDialog', 'The shortcut '
'"%s" is already assigned to another action, please '
'use a different shortcut.')) % key_sequence.toString(),
QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Ok),
QtGui.QMessageBox.Ok
)
else:
if self._validiate_shortcut(self._currentItemAction(), key_sequence):
if self.primaryPushButton.isChecked():
self._adjustButton(self.primaryPushButton,
False, text=key_sequence.toString())
@ -227,6 +192,12 @@ class ShortcutListForm(QtGui.QDialog, Ui_ShortcutListDialog):
new_shortcuts.append(
QtGui.QKeySequence(self.alternatePushButton.text()))
self.changedActions[action] = new_shortcuts
if not self.primaryPushButton.text():
# When we do not have a primary shortcut, the just entered alternate
# shortcut will automatically become the primary shortcut. That is
# why we have to adjust the primary button's text.
self.primaryPushButton.setText(self.alternatePushButton.text())
self.alternatePushButton.setText(u'')
self.refreshShortcutList()
def onItemDoubleClicked(self, item, column):
@ -374,6 +345,16 @@ class ShortcutListForm(QtGui.QDialog, Ui_ShortcutListDialog):
new_shortcuts = []
if len(action.defaultShortcuts) != 0:
new_shortcuts.append(action.defaultShortcuts[0])
# We have to check if the primary default shortcut is available. But
# we only have to check, if the action has a default primary
# shortcut (an "empty" shortcut is always valid and if the action
# does not have a default primary shortcut, then the alternative
# shortcut (not the default one) will become primary shortcut, thus
# the check will assume that an action were going to have the same
# shortcut twice.
if not self._validiate_shortcut(action, new_shortcuts[0]) and \
new_shortcuts[0] != shortcuts[0]:
return
if len(shortcuts) == 2:
new_shortcuts.append(shortcuts[1])
self.changedActions[action] = new_shortcuts
@ -394,10 +375,60 @@ class ShortcutListForm(QtGui.QDialog, Ui_ShortcutListDialog):
new_shortcuts.append(shortcuts[0])
if len(action.defaultShortcuts) == 2:
new_shortcuts.append(action.defaultShortcuts[1])
if len(new_shortcuts) == 2:
if not self._validiate_shortcut(action, new_shortcuts[1]):
return
self.changedActions[action] = new_shortcuts
self.refreshShortcutList()
self.onCurrentItemChanged(self.treeWidget.currentItem())
def _validiate_shortcut(self, changing_action, key_sequence):
"""
Checks if the given ``changing_action `` can use the given
``key_sequence``. Returns ``True`` if the ``key_sequence`` can be used
by the action, otherwise displays a dialog and returns ``False``.
``changing_action``
The action which wants to use the ``key_sequence``.
``key_sequence``
The key sequence which the action want so use.
"""
is_valid = True
for category in self.action_list.categories:
for action in category.actions:
shortcuts = self._actionShortcuts(action)
if key_sequence not in shortcuts:
continue
if action is changing_action:
if self.primaryPushButton.isChecked() and \
shortcuts.index(key_sequence) == 0:
continue
if self.alternatePushButton.isChecked() and \
shortcuts.index(key_sequence) == 1:
continue
# Have the same parent, thus they cannot have the same shortcut.
if action.parent() is changing_action.parent():
is_valid = False
# The new shortcut is already assigned, but if both shortcuts
# are only valid in a different widget the new shortcut is
# vaild, because they will not interfere.
if action.shortcutContext() in [QtCore.Qt.WindowShortcut,
QtCore.Qt.ApplicationShortcut]:
is_valid = False
if changing_action.shortcutContext() in \
[QtCore.Qt.WindowShortcut, QtCore.Qt.ApplicationShortcut]:
is_valid = False
if not is_valid:
Receiver.send_message(u'openlp_warning_message', {
u'title': translate('OpenLP.ShortcutListDialog',
'Duplicate Shortcut'),
u'message': unicode(translate('OpenLP.ShortcutListDialog',
'The shortcut "%s" is already assigned to another action, '
'please use a different shortcut.')) % key_sequence.toString()
})
return is_valid
def _actionShortcuts(self, action):
"""
This returns the shortcuts for the given ``action``, which also includes

View File

@ -180,16 +180,25 @@ class SlideController(QtGui.QWidget):
self.hideMenu.menu().addAction(self.themeScreen)
self.hideMenu.menu().addAction(self.desktopScreen)
self.toolbar.addToolbarSeparator(u'Loop Separator')
self.toolbar.addToolbarButton(
startLoop = self.toolbar.addToolbarButton(
# Does not need translating - control string.
u'Start Loop', u':/media/media_time.png',
translate('OpenLP.SlideController', 'Start continuous loop'),
self.onStartLoop)
self.toolbar.addToolbarButton(
action_list = ActionList.get_instance()
action_list.add_action(startLoop, UiStrings().LiveToolbar)
stopLoop = self.toolbar.addToolbarButton(
# Does not need translating - control string.
u'Stop Loop', u':/media/media_stop.png',
translate('OpenLP.SlideController', 'Stop continuous loop'),
self.onStopLoop)
action_list.add_action(stopLoop, UiStrings().LiveToolbar)
self.toogleLoop = shortcut_action(self, u'toogleLoop',
[QtGui.QKeySequence(u'L')], self.onToggleLoop,
category=UiStrings().LiveToolbar)
self.toogleLoop.setText(translate('OpenLP.SlideController',
'Start/Stop continuous loop'))
self.addAction(self.toogleLoop)
self.delaySpinBox = QtGui.QSpinBox()
self.delaySpinBox.setMinimum(1)
self.delaySpinBox.setMaximum(180)
@ -380,18 +389,21 @@ class SlideController(QtGui.QWidget):
action_list.add_action(self.previousItem)
action_list.add_action(self.nextItem)
self.previousService = shortcut_action(parent, u'previousService',
[QtCore.Qt.Key_Left], self.servicePrevious, UiStrings().LiveToolbar)
self.previousService.setShortcutContext(QtCore.Qt.WidgetWithChildrenShortcut)
[QtCore.Qt.Key_Left], self.servicePrevious,
category=UiStrings().LiveToolbar,
context=QtCore.Qt.WidgetWithChildrenShortcut)
self.previousService.setText(
translate('OpenLP.SlideController', 'Previous Service'))
self.nextService = shortcut_action(parent, 'nextService',
[QtCore.Qt.Key_Right], self.serviceNext, UiStrings().LiveToolbar)
self.nextService.setShortcutContext(QtCore.Qt.WidgetWithChildrenShortcut)
[QtCore.Qt.Key_Right], self.serviceNext,
category=UiStrings().LiveToolbar,
context=QtCore.Qt.WidgetWithChildrenShortcut)
self.nextService.setText(
translate('OpenLP.SlideController', 'Next Service'))
self.escapeItem = shortcut_action(parent, 'escapeItem',
[QtCore.Qt.Key_Escape], self.liveEscape, UiStrings().LiveToolbar)
self.escapeItem.setShortcutContext(QtCore.Qt.WidgetWithChildrenShortcut)
[QtCore.Qt.Key_Escape], self.liveEscape,
category=UiStrings().LiveToolbar,
context=QtCore.Qt.WidgetWithChildrenShortcut)
self.escapeItem.setText(
translate('OpenLP.SlideController', 'Escape Item'))
@ -412,7 +424,7 @@ class SlideController(QtGui.QWidget):
"""
# rebuild display as screen size changed
self.display = MainDisplay(self, self.screens, self.isLive)
self.display.imageManager = self.parent.renderManager.image_manager
self.display.imageManager = self.parent.renderer.image_manager
self.display.alertTab = self.alertTab
self.display.setup()
if self.isLive:
@ -491,6 +503,9 @@ class SlideController(QtGui.QWidget):
self.mediabar.setVisible(False)
self.toolbar.makeWidgetsInvisible([u'Song Menu'])
self.toolbar.makeWidgetsInvisible(self.loopList)
self.toogleLoop.setEnabled(False)
self.toolbar.actions[u'Start Loop'].setEnabled(False)
self.toolbar.actions[u'Stop Loop'].setEnabled(False)
self.toolbar.actions[u'Stop Loop'].setVisible(False)
if item.is_text():
if QtCore.QSettings().value(
@ -500,6 +515,9 @@ class SlideController(QtGui.QWidget):
if item.is_capable(ItemCapabilities.AllowsLoop) and \
len(item.get_frames()) > 1:
self.toolbar.makeWidgetsVisible(self.loopList)
self.toogleLoop.setEnabled(True)
self.toolbar.actions[u'Start Loop'].setEnabled(True)
self.toolbar.actions[u'Stop Loop'].setEnabled(True)
if item.is_media():
self.toolbar.setVisible(False)
self.mediabar.setVisible(True)
@ -614,19 +632,19 @@ class SlideController(QtGui.QWidget):
label.setScaledContents(True)
if self.serviceItem.is_command():
image = resize_image(frame[u'image'],
self.parent.renderManager.width,
self.parent.renderManager.height)
self.parent.renderer.width,
self.parent.renderer.height)
else:
# If current slide set background to image
if framenumber == slideno:
self.serviceItem.bg_image_bytes = \
self.parent.renderManager.image_manager. \
self.parent.renderer.image_manager. \
get_image_bytes(frame[u'title'])
image = self.parent.renderManager.image_manager. \
image = self.parent.renderer.image_manager. \
get_image(frame[u'title'])
label.setPixmap(QtGui.QPixmap.fromImage(image))
self.previewListWidget.setCellWidget(framenumber, 0, label)
slideHeight = width * self.parent.renderManager.screen_ratio
slideHeight = width * self.parent.renderer.screen_ratio
row += 1
text.append(unicode(row))
self.previewListWidget.setItem(framenumber, 0, item)
@ -992,6 +1010,15 @@ class SlideController(QtGui.QWidget):
self.previewListWidget.rowCount() - 1)
self.slideSelected()
def onToggleLoop(self, toggled):
"""
Toggles the loop state.
"""
if self.toolbar.actions[u'Start Loop'].isVisible():
self.onStartLoop()
else:
self.onStopLoop()
def onStartLoop(self):
"""
Start the timer loop running and store the timer id
@ -1150,4 +1177,4 @@ class SlideController(QtGui.QWidget):
elif self.desktopScreen.isChecked():
return HideMode.Screen
else:
return None
return None

View File

@ -56,6 +56,7 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard):
self.setupUi(self)
self.registerFields()
self.updateThemeAllowed = True
self.temp_background_filename = u''
QtCore.QObject.connect(self.backgroundComboBox,
QtCore.SIGNAL(u'currentIndexChanged(int)'),
self.onBackgroundComboBoxCurrentIndexChanged)
@ -279,6 +280,7 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard):
Run the wizard.
"""
log.debug(u'Editing theme %s' % self.theme.theme_name)
self.temp_background_filename = u''
self.updateThemeAllowed = False
self.setDefaults()
self.updateThemeAllowed = True
@ -432,6 +434,16 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard):
# do not allow updates when screen is building for the first time.
if self.updateThemeAllowed:
self.theme.background_type = BackgroundType.to_string(index)
if self.theme.background_type != \
BackgroundType.to_string(BackgroundType.Image) and \
self.temp_background_filename == u'':
self.temp_background_filename = self.theme.background_filename
self.theme.background_filename = u''
if self.theme.background_type == \
BackgroundType.to_string(BackgroundType.Image) and \
self.temp_background_filename != u'':
self.theme.background_filename = self.temp_background_filename
self.temp_background_filename = u''
self.setBackgroundPageValues()
def onGradientComboBoxCurrentIndexChanged(self, index):
@ -589,4 +601,4 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard):
QtGui.QColor(field), self)
if new_color.isValid():
field = new_color.name()
return field
return field

View File

@ -660,7 +660,7 @@ class ThemeManager(QtGui.QWidget):
def generateImage(self, themeData, forcePage=False):
"""
Call the RenderManager to build a Sample Image
Call the renderer to build a Sample Image
``themeData``
The theme to generated a preview for.
@ -669,7 +669,7 @@ class ThemeManager(QtGui.QWidget):
Flag to tell message lines per page need to be generated.
"""
log.debug(u'generateImage \n%s ', themeData)
return self.mainwindow.renderManager.generate_preview(
return self.mainwindow.renderer.generate_preview(
themeData, forcePage)
def getPreviewImage(self, theme):

View File

@ -149,7 +149,7 @@ class ThemesTab(SettingsTab):
settings.setValue(u'global theme',
QtCore.QVariant(self.global_theme))
settings.endGroup()
self.mainwindow.renderManager.set_global_theme(
self.mainwindow.renderer.set_global_theme(
self.global_theme, self.theme_level)
Receiver.send_message(u'theme_update_global', self.global_theme)
@ -167,7 +167,7 @@ class ThemesTab(SettingsTab):
def onDefaultComboBoxChanged(self, value):
self.global_theme = unicode(self.DefaultComboBox.currentText())
self.mainwindow.renderManager.set_global_theme(
self.mainwindow.renderer.set_global_theme(
self.global_theme, self.theme_level)
self.__previewGlobalTheme()
@ -188,7 +188,7 @@ class ThemesTab(SettingsTab):
for theme in theme_list:
self.DefaultComboBox.addItem(theme)
find_and_set_in_combo_box(self.DefaultComboBox, self.global_theme)
self.mainwindow.renderManager.set_global_theme(
self.mainwindow.renderer.set_global_theme(
self.global_theme, self.theme_level)
if self.global_theme is not u'':
self.__previewGlobalTheme()
@ -203,4 +203,4 @@ class ThemesTab(SettingsTab):
if not preview.isNull():
preview = preview.scaled(300, 255, QtCore.Qt.KeepAspectRatio,
QtCore.Qt.SmoothTransformation)
self.DefaultListView.setPixmap(preview)
self.DefaultListView.setPixmap(preview)

View File

@ -95,6 +95,10 @@ class OpenLPWizard(QtGui.QWizard):
self.customSignals()
QtCore.QObject.connect(self, QtCore.SIGNAL(u'currentIdChanged(int)'),
self.onCurrentIdChanged)
QtCore.QObject.connect(self.errorCopyToButton,
QtCore.SIGNAL(u'clicked()'), self.onErrorCopyToButtonClicked)
QtCore.QObject.connect(self.errorSaveToButton,
QtCore.SIGNAL(u'clicked()'), self.onErrorSaveToButtonClicked)
def setupUi(self, image):
"""
@ -129,10 +133,36 @@ class OpenLPWizard(QtGui.QWizard):
self.progressLayout.setObjectName(u'progressLayout')
self.progressLabel = QtGui.QLabel(self.progressPage)
self.progressLabel.setObjectName(u'progressLabel')
self.progressLabel.setWordWrap(True)
self.progressLayout.addWidget(self.progressLabel)
self.progressBar = QtGui.QProgressBar(self.progressPage)
self.progressBar.setObjectName(u'progressBar')
self.progressLayout.addWidget(self.progressBar)
# Add a QTextEdit and a copy to file and copy to clipboard button to be
# able to provide feedback to the user. Hidden by default.
self.errorReportTextEdit = QtGui.QTextEdit(self.progressPage)
self.errorReportTextEdit.setObjectName(u'progresserrorReportTextEdit')
self.errorReportTextEdit.setHidden(True)
self.errorReportTextEdit.setReadOnly(True)
self.progressLayout.addWidget(self.errorReportTextEdit)
self.errorButtonLayout = QtGui.QHBoxLayout()
self.errorButtonLayout.setObjectName(u'errorButtonLayout')
spacer = QtGui.QSpacerItem(40, 20,
QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
self.errorButtonLayout.addItem(spacer)
self.errorCopyToButton = QtGui.QPushButton(self.progressPage)
self.errorCopyToButton.setObjectName(u'errorCopyToButton')
self.errorCopyToButton.setHidden(True)
self.errorCopyToButton.setIcon(
build_icon(u':/system/system_edit_copy.png'))
self.errorButtonLayout.addWidget(self.errorCopyToButton)
self.errorSaveToButton = QtGui.QPushButton(self.progressPage)
self.errorSaveToButton.setObjectName(u'errorSaveToButton')
self.errorSaveToButton.setHidden(True)
self.errorSaveToButton.setIcon(
build_icon(u':/general/general_save.png'))
self.errorButtonLayout.addWidget(self.errorSaveToButton)
self.progressLayout.addLayout(self.errorButtonLayout)
self.addPage(self.progressPage)
def exec_(self):
@ -160,6 +190,18 @@ class OpenLPWizard(QtGui.QWizard):
self.performWizard()
self.postWizard()
def onErrorCopyToButtonClicked(self):
"""
Called when the ``onErrorCopyToButtonClicked`` has been clicked.
"""
pass
def onErrorSaveToButtonClicked(self):
"""
Called when the ``onErrorSaveToButtonClicked`` has been clicked.
"""
pass
def incrementProgressBar(self, status_text, increment=1):
"""
Update the wizard progress page.
@ -219,4 +261,5 @@ class OpenLPWizard(QtGui.QWizard):
if filename:
editbox.setText(filename)
SettingsManager.set_last_dir(self.plugin.settingsSection,
filename, 1)
filename, 1)

View File

@ -723,6 +723,7 @@ class BibleMediaItem(MediaManagerItem):
service_item.add_capability(ItemCapabilities.NoLineBreaks)
service_item.add_capability(ItemCapabilities.AllowsPreview)
service_item.add_capability(ItemCapabilities.AllowsLoop)
service_item.add_capability(ItemCapabilities.AllowsWordSplit)
# Service Item: Title
service_item.title = u', '.join(raw_title)
# Service Item: Theme
@ -849,4 +850,4 @@ class BibleMediaItem(MediaManagerItem):
self.settings.layout_style)
QtCore.QSettings().setValue(
self.settingsSection + u'/verse layout style',
QtCore.QVariant(self.settings.layout_style))
QtCore.QVariant(self.settings.layout_style))

View File

@ -37,6 +37,9 @@ from openlp.plugins.bibles.lib.db import BibleDB
log = logging.getLogger(__name__)
def replacement(match):
return match.group(2).upper()
class OSISBible(BibleDB):
"""
`OSIS <http://www.bibletechnologies.net/>`_ Bible format importer class.
@ -60,6 +63,7 @@ class OSISBible(BibleDB):
self.lg_regex = re.compile(r'<lg(.*?)>')
self.l_regex = re.compile(r'<l (.*?)>')
self.w_regex = re.compile(r'<w (.*?)>')
self.q_regex = re.compile(r'<q(.*?)>')
self.q1_regex = re.compile(r'<q(.*?)level="1"(.*?)>')
self.q2_regex = re.compile(r'<q(.*?)level="2"(.*?)>')
self.trans_regex = re.compile(r'<transChange(.*?)>(.*?)</transChange>')
@ -106,6 +110,7 @@ class OSISBible(BibleDB):
detect_file.close()
try:
osis = codecs.open(self.filename, u'r', details['encoding'])
repl = replacement
for file_record in osis:
if self.stop_import_flag:
break
@ -148,12 +153,13 @@ class OSISBible(BibleDB):
verse_text = self.rf_regex.sub(u'', verse_text)
verse_text = self.lb_regex.sub(u' ', verse_text)
verse_text = self.lg_regex.sub(u'', verse_text)
verse_text = self.l_regex.sub(u'', verse_text)
verse_text = self.l_regex.sub(u' ', verse_text)
verse_text = self.w_regex.sub(u'', verse_text)
verse_text = self.q1_regex.sub(u'"', verse_text)
verse_text = self.q2_regex.sub(u'\'', verse_text)
verse_text = self.q_regex.sub(u'', verse_text)
verse_text = self.divine_name_regex.sub(repl, verse_text)
verse_text = self.trans_regex.sub(u'', verse_text)
verse_text = self.divine_name_regex.sub(u'', verse_text)
verse_text = verse_text.replace(u'</lb>', u'')\
.replace(u'</l>', u'').replace(u'<lg>', u'')\
.replace(u'</lg>', u'').replace(u'</q>', u'')\

View File

@ -169,7 +169,7 @@ class EditCustomForm(QtGui.QDialog, Ui_CustomEditDialog):
item = self.slideListView.item(row)
slide_list += item.text()
if row != self.slideListView.count() - 1:
slide_list += u'\n[---]\n'
slide_list += u'\n[===]\n'
self.editSlideForm.setText(slide_list)
if self.editSlideForm.exec_():
self.updateSlideList(self.editSlideForm.getText(), True)

View File

@ -63,7 +63,7 @@ class EditCustomSlideForm(QtGui.QDialog, Ui_CustomSlideEditDialog):
"""
Returns a list with all slides.
"""
return self.slideTextEdit.toPlainText().split(u'\n[---]\n')
return self.slideTextEdit.toPlainText().split(u'\n[===]\n')
def onSplitButtonPressed(self):
"""
@ -71,5 +71,5 @@ class EditCustomSlideForm(QtGui.QDialog, Ui_CustomSlideEditDialog):
"""
if self.slideTextEdit.textCursor().columnNumber() != 0:
self.slideTextEdit.insertPlainText(u'\n')
self.slideTextEdit.insertPlainText(u'[---]\n')
self.slideTextEdit.insertPlainText(u'[===]\n')
self.slideTextEdit.setFocus()

View File

@ -140,6 +140,7 @@ class CustomMediaItem(MediaManagerItem):
service_item.add_capability(ItemCapabilities.AllowsEdit)
service_item.add_capability(ItemCapabilities.AllowsPreview)
service_item.add_capability(ItemCapabilities.AllowsLoop)
service_item.add_capability(ItemCapabilities.AllowsVirtualSplit)
customSlide = self.parent.manager.get_object(CustomSlide, item_id)
title = customSlide.title
credit = customSlide.credits

View File

@ -244,7 +244,7 @@ class ImpressDocument(PresentationDocument):
return False
self.presentation = self.document.getPresentation()
self.presentation.Display = \
self.controller.plugin.renderManager.screens.current_display + 1
self.controller.plugin.renderer.screens.current_display + 1
self.control = None
self.create_thumbnails()
return True
@ -463,4 +463,4 @@ class ImpressDocument(PresentationDocument):
shape = page.getByIndex(idx)
if shape.supportsService("com.sun.star.drawing.Text"):
text += shape.getString() + '\n'
return text
return text

View File

@ -251,14 +251,15 @@ class PowerpointDocument(PresentationDocument):
win32ui.GetForegroundWindow().GetDC().GetDeviceCaps(88)
except win32ui.error:
dpi = 96
rendermanager = self.controller.plugin.renderManager
rect = rendermanager.screens.current[u'size']
renderer = self.controller.plugin.renderer
rect = renderer.screens.current[u'size']
ppt_window = self.presentation.SlideShowSettings.Run()
ppt_window.Top = rect.y() * 72 / dpi
ppt_window.Height = rect.height() * 72 / dpi
ppt_window.Left = rect.x() * 72 / dpi
ppt_window.Width = rect.width() * 72 / dpi
def get_slide_number(self):
"""
Returns the current slide number.

View File

@ -121,8 +121,8 @@ class PptviewDocument(PresentationDocument):
The file name of the presentations to run.
"""
log.debug(u'LoadPresentation')
rendermanager = self.controller.plugin.renderManager
rect = rendermanager.screens.current[u'size']
renderer = self.controller.plugin.renderer
rect = renderer.screens.current[u'size']
rect = RECT(rect.x(), rect.y(), rect.right(), rect.bottom())
filepath = str(self.filepath.replace(u'/', u'\\'))
if not os.path.isdir(self.get_temp_folder()):
@ -244,4 +244,4 @@ class PptviewDocument(PresentationDocument):
"""
Triggers the previous slide on the running presentation
"""
self.controller.process.PrevStep(self.pptid)
self.controller.process.PrevStep(self.pptid)

View File

@ -89,7 +89,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
self.onVerseListViewPressed)
QtCore.QObject.connect(self.themeAddButton,
QtCore.SIGNAL(u'clicked()'),
self.parent.parent.renderManager.theme_manager.onAddTheme)
self.parent.parent.renderer.theme_manager.onAddTheme)
QtCore.QObject.connect(self.maintenanceButton,
QtCore.SIGNAL(u'clicked()'), self.onMaintenanceButtonClicked)
QtCore.QObject.connect(Receiver.get_receiver(),
@ -576,11 +576,13 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
if verse_index is not None:
order.append(VerseType.Tags[verse_index] + u'1')
else:
order.append(u'') # it matches no verses anyway
# it matches no verses anyway
order.append(u'')
else:
verse_index = VerseType.from_translated_tag(item[0])
if verse_index is None:
order.append(u'') # same as above
# it matches no verses anyway
order.append(u'')
else:
verse_tag = VerseType.Tags[verse_index]
verse_num = item[1:].lower()
@ -779,4 +781,4 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog):
self.song.verse_order)
except:
log.exception(u'Problem processing song Lyrics \n%s',
sxml.dump_xml())
sxml.dump_xml())

View File

@ -26,6 +26,7 @@
"""
The song import functions for OpenLP.
"""
import codecs
import logging
import os
@ -55,6 +56,7 @@ class SongImportForm(OpenLPWizard):
``plugin``
The songs plugin.
"""
self.clipboard = plugin.formparent.clipboard
OpenLPWizard.__init__(self, parent, plugin, u'songImportWizard',
u':/wizards/wizard_importsong.bmp')
@ -330,6 +332,10 @@ class SongImportForm(OpenLPWizard):
'Please wait while your songs are imported.'))
self.progressLabel.setText(WizardStrings.Ready)
self.progressBar.setFormat(WizardStrings.PercentSymbolFormat)
self.errorCopyToButton.setText(translate('SongsPlugin.ImportWizardForm',
'Copy'))
self.errorSaveToButton.setText(translate('SongsPlugin.ImportWizardForm',
'Save to File'))
# Align all QFormLayouts towards each other.
width = max(self.formatLabel.minimumSizeHint().width(),
self.openLP2FilenameLabel.minimumSizeHint().width())
@ -459,10 +465,7 @@ class SongImportForm(OpenLPWizard):
"""
Return a list of file from the listbox
"""
files = []
for row in range(0, listbox.count()):
files.append(unicode(listbox.item(row).text()))
return files
return [unicode(listbox.item(i).text()) for i in range(listbox.count())]
def removeSelectedItems(self, listbox):
"""
@ -659,6 +662,10 @@ class SongImportForm(OpenLPWizard):
self.songShowPlusFileListWidget.clear()
self.foilPresenterFileListWidget.clear()
#self.csvFilenameEdit.setText(u'')
self.errorReportTextEdit.clear()
self.errorReportTextEdit.setHidden(True)
self.errorCopyToButton.setHidden(True)
self.errorSaveToButton.setHidden(True)
def preWizard(self):
"""
@ -743,12 +750,30 @@ class SongImportForm(OpenLPWizard):
importer = self.plugin.importSongs(SongFormat.FoilPresenter,
filenames=self.getListOfFiles(self.foilPresenterFileListWidget)
)
if importer.do_import():
self.progressLabel.setText(WizardStrings.FinishedImport)
importer.do_import()
if importer.error_log:
self.progressLabel.setText(translate(
'SongsPlugin.SongImportForm', 'Your song import failed.'))
else:
self.progressLabel.setText(
translate('SongsPlugin.SongImportForm',
'Your song import failed.'))
self.progressLabel.setText(WizardStrings.FinishedImport)
def onErrorCopyToButtonClicked(self):
"""
Copy the error report to the clipboard.
"""
self.clipboard.setText(self.errorReportTextEdit.toPlainText())
def onErrorSaveToButtonClicked(self):
"""
Save the error report to a file.
"""
filename = QtGui.QFileDialog.getSaveFileName(self,
SettingsManager.get_last_dir(self.plugin.settingsSection, 1))
if not filename:
return
file = codecs.open(filename, u'w', u'utf-8')
file.write(self.errorReportTextEdit.toPlainText())
file.close()
def addFileSelectItem(self, prefix, obj_prefix=None, can_disable=False,
single_select=False):
@ -836,4 +861,4 @@ class SongImportForm(OpenLPWizard):
setattr(self, prefix + u'DisabledLayout', disabledLayout)
setattr(self, prefix + u'DisabledLabel', disabledLabel)
setattr(self, prefix + u'ImportWidget', importWidget)
return importWidget
return importWidget

View File

@ -386,7 +386,8 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog):
existing_author = self.manager.get_object_filtered(Author,
and_(Author.first_name == old_author.first_name,
Author.last_name == old_author.last_name,
Author.display_name == old_author.display_name))
Author.display_name == old_author.display_name,
Author.id != old_author.id))
# Find the songs, which have the old_author as author.
songs = self.manager.get_all_objects(Song,
Song.authors.contains(old_author))
@ -408,7 +409,7 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog):
"""
# Find the duplicate.
existing_topic = self.manager.get_object_filtered(Topic,
Topic.name == old_topic.name)
and_(Topic.name == old_topic.name, Topic.id != old_topic.id))
# Find the songs, which have the old_topic as topic.
songs = self.manager.get_all_objects(Song,
Song.topics.contains(old_topic))
@ -431,7 +432,8 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog):
# Find the duplicate.
existing_book = self.manager.get_object_filtered(Book,
and_(Book.name == old_book.name,
Book.publisher == old_book.publisher))
Book.publisher == old_book.publisher,
Book.id != old_book.id))
# Find the songs, which have the old_book as book.
songs = self.manager.get_all_objects(Song,
Song.song_book_id == old_book.id)
@ -503,4 +505,5 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog):
editButton.setEnabled(False)
else:
deleteButton.setEnabled(True)
editButton.setEnabled(True)
editButton.setEnabled(True)

View File

@ -257,7 +257,7 @@ def clean_song(manager, song):
``song``
The song object.
"""
song.title = song.title.strip() if song.title else u''
song.title = song.title.rstrip() if song.title else u''
if song.alternate_title is None:
song.alternate_title = u''
song.alternate_title = song.alternate_title.strip()
@ -278,24 +278,32 @@ def clean_song(manager, song):
# List for later comparison.
compare_order = []
for verse in verses:
type = VerseType.Tags[VerseType.from_loose_input(verse[0][u'type'])]
verse_type = VerseType.Tags[VerseType.from_loose_input(
verse[0][u'type'])]
sxml.add_verse_to_lyrics(
type,
verse_type,
verse[0][u'label'],
verse[1],
verse[0][u'lang'] if verse[0].has_key(u'lang') else None
)
compare_order.append((u'%s%s' % (type, verse[0][u'label'])).upper())
compare_order.append((u'%s%s' % (verse_type, verse[0][u'label'])
).upper())
if verse[0][u'label'] == u'1':
compare_order.append(verse_type.upper())
song.lyrics = unicode(sxml.extract_xml(), u'utf-8')
# Rebuild the verse order, to convert translated verse tags, which might
# have been added prior to 1.9.5.
order = song.verse_order.strip().split()
if song.verse_order:
order = song.verse_order.strip().split()
else:
order = []
new_order = []
for verse_def in order:
new_order.append((u'%s%s' % (
VerseType.Tags[VerseType.from_loose_input(verse_def[0])],
verse_def[1:])).upper()
)
verse_type = VerseType.Tags[VerseType.from_loose_input(verse_def[0])]
if len(verse_def) > 1:
new_order.append((u'%s%s' % (verse_type, verse_def[1:])).upper())
else:
new_order.append(verse_type.upper())
song.verse_order = u' '.join(new_order)
# Check if the verse order contains tags for verses which do not exist.
for order in new_order:

View File

@ -59,16 +59,10 @@ class CCLIFileImport(SongImport):
Import either a ``.usr`` or a ``.txt`` SongSelect file.
"""
log.debug(u'Starting CCLI File Import')
song_total = len(self.import_source)
self.import_wizard.progressBar.setMaximum(song_total)
song_count = 1
self.import_wizard.progressBar.setMaximum(len(self.import_source))
for filename in self.import_source:
self.import_wizard.incrementProgressBar(unicode(translate(
'SongsPlugin.CCLIFileImport', 'Importing song %d of %d')) %
(song_count, song_total))
filename = unicode(filename)
log.debug(u'Importing CCLI File: %s', filename)
self.set_defaults()
lines = []
if os.path.isfile(filename):
detect_file = open(filename, u'r')
@ -81,19 +75,23 @@ class CCLIFileImport(SongImport):
detect_file.close()
infile = codecs.open(filename, u'r', details['encoding'])
lines = infile.readlines()
infile.close()
ext = os.path.splitext(filename)[1]
if ext.lower() == u'.usr':
log.info(u'SongSelect .usr format file found: %s', filename)
self.do_import_usr_file(lines)
if not self.do_import_usr_file(lines):
self.log_error(filename)
elif ext.lower() == u'.txt':
log.info(u'SongSelect .txt format file found: %s', filename)
self.do_import_txt_file(lines)
if not self.do_import_txt_file(lines):
self.log_error(filename)
else:
self.log_error(filename,
translate('SongsPlugin.CCLIFileImport',
'The file does not have a valid extension.'))
log.info(u'Extension %s is not valid', filename)
song_count += 1
if self.stop_import_flag:
return False
return True
return
def do_import_usr_file(self, textList):
"""
@ -218,7 +216,7 @@ class CCLIFileImport(SongImport):
else:
self.add_author(author)
self.topics = [topic.strip() for topic in song_topics.split(u'/t')]
self.finish()
return self.finish()
def do_import_txt_file(self, textList):
"""
@ -334,6 +332,5 @@ class CCLIFileImport(SongImport):
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.finish()
[self.add_author(author_name.strip()) for author_name in author_list]
return self.finish()

View File

@ -26,11 +26,13 @@
import logging
import os
from lxml import etree, objectify
import re
from lxml import etree, objectify
from openlp.core.lib import translate
from openlp.core.ui.wizard import WizardStrings
from openlp.plugins.songs.lib import VerseType
from openlp.plugins.songs.lib.songimport import SongImport
log = logging.getLogger(__name__)
@ -56,26 +58,16 @@ class EasiSlidesImport(SongImport):
multiple opensong files. If `self.commit` is set False, the
import will not be committed to the database (useful for test scripts).
"""
self.import_wizard.progressBar.setMaximum(1)
log.info(u'Importing EasiSlides XML file %s', self.import_source)
parser = etree.XMLParser(remove_blank_text=True)
file = etree.parse(self.import_source, parser)
xml = unicode(etree.tostring(file))
song_xml = objectify.fromstring(xml)
self.import_wizard.incrementProgressBar(
WizardStrings.ImportingType % os.path.split(self.import_source)[-1])
self.import_wizard.progressBar.setMaximum(len(song_xml.Item))
for song in song_xml.Item:
self.import_wizard.incrementProgressBar(
unicode(translate('SongsPlugin.ImportWizardForm',
u'Importing %s, song %s...')) %
(os.path.split(self.import_source)[-1], song.Title1))
success = self._parse_song(song)
if not success or self.stop_import_flag:
return False
elif self.commit:
self.finish()
return True
if self.stop_import_flag:
return
self._parse_song(song)
def _parse_song(self, song):
self._success = True
@ -90,7 +82,11 @@ class EasiSlidesImport(SongImport):
self._add_copyright(song.LicenceAdmin2)
self._add_unicode_attribute(u'song_book_name', song.BookReference)
self._parse_and_add_lyrics(song)
return self._success
if self._success:
if not self.finish():
self.log_error(song.Title1 if song.Title1 else u'')
else:
self.set_defaults()
def _add_unicode_attribute(self, self_attribute, import_attribute,
mandatory=False):
@ -122,10 +118,8 @@ class EasiSlidesImport(SongImport):
def _add_authors(self, song):
try:
authors = unicode(song.Writer).split(u',')
for author in authors:
author = author.strip()
if len(author):
self.authors.append(author)
self.authors = \
[author.strip() for author in authors if author.strip()]
except UnicodeDecodeError:
log.exception(u'Unicode decode error while decoding Writer')
self._success = False
@ -188,12 +182,13 @@ class EasiSlidesImport(SongImport):
# if the regions are inside verses
regionsInVerses = (regions and regionlines[regionlines.keys()[0]] > 1)
MarkTypes = {
u'CHORUS': u'C',
u'VERSE': u'V',
u'INTRO': u'I',
u'ENDING': u'E',
u'BRIDGE': u'B',
u'PRECHORUS': u'P'}
u'CHORUS': VerseType.Tags[VerseType.Chorus],
u'VERSE': VerseType.Tags[VerseType.Verse],
u'INTRO': VerseType.Tags[VerseType.Intro],
u'ENDING': VerseType.Tags[VerseType.Ending],
u'BRIDGE': VerseType.Tags[VerseType.Bridge],
u'PRECHORUS': VerseType.Tags[VerseType.PreChorus]
}
verses = {}
# list as [region, versetype, versenum, instance]
our_verse_order = []

View File

@ -33,6 +33,7 @@ import struct
from openlp.core.lib import translate
from openlp.core.ui.wizard import WizardStrings
from openlp.plugins.songs.lib import VerseType
from openlp.plugins.songs.lib import retrieve_windows_encoding
from songimport import SongImport
@ -142,12 +143,12 @@ class EasyWorshipSongImport(SongImport):
# Open the DB and MB files if they exist
import_source_mb = self.import_source.replace('.DB', '.MB')
if not os.path.isfile(self.import_source):
return False
return
if not os.path.isfile(import_source_mb):
return False
return
db_size = os.path.getsize(self.import_source)
if db_size < 0x800:
return False
return
db_file = open(self.import_source, 'rb')
self.memo_file = open(import_source_mb, 'rb')
# Don't accept files that are clearly not paradox files
@ -156,7 +157,7 @@ class EasyWorshipSongImport(SongImport):
if header_size != 0x800 or block_size < 1 or block_size > 4:
db_file.close()
self.memo_file.close()
return False
return
# Take a stab at how text is encoded
self.encoding = u'cp1252'
db_file.seek(106)
@ -183,7 +184,7 @@ class EasyWorshipSongImport(SongImport):
self.encoding = u'cp874'
self.encoding = retrieve_windows_encoding(self.encoding)
if not self.encoding:
return False
return
# There does not appear to be a _reliable_ way of getting the number
# of songs/records, so let's use file blocks for measuring progress.
total_blocks = (db_size - header_size) / (block_size * 1024)
@ -203,8 +204,8 @@ class EasyWorshipSongImport(SongImport):
field_size))
self.set_record_struct(field_descs)
# Pick out the field description indexes we will need
success = True
try:
success = True
fi_title = self.find_field(u'Title')
fi_author = self.find_field(u'Author')
fi_copy = self.find_field(u'Copyright')
@ -223,31 +224,25 @@ class EasyWorshipSongImport(SongImport):
# Loop through each record within the current block
for i in range(rec_count):
if self.stop_import_flag:
success = False
break
raw_record = db_file.read(record_size)
self.fields = self.record_struct.unpack(raw_record)
self.set_defaults()
# Get title and update progress bar message
title = self.get_field(fi_title)
if title:
self.import_wizard.incrementProgressBar(
WizardStrings.ImportingType % title, 0)
self.title = title
# Get remaining fields
self.title = self.get_field(fi_title)
# Get remaining fields.
copy = self.get_field(fi_copy)
admin = self.get_field(fi_admin)
ccli = self.get_field(fi_ccli)
authors = self.get_field(fi_author)
words = self.get_field(fi_words)
# Set the SongImport object members
# Set the SongImport object members.
if copy:
self.copyright = copy
if admin:
if copy:
self.copyright += u', '
self.copyright += \
unicode(translate('SongsPlugin.ImportWizardForm',
unicode(translate('SongsPlugin.EasyWorshipSongImport',
'Administered by %s')) % admin
if ccli:
self.ccli_number = ccli
@ -264,19 +259,17 @@ class EasyWorshipSongImport(SongImport):
# Format the lyrics
words = strip_rtf(words, self.encoding)
for verse in words.split(u'\n\n'):
self.add_verse(verse.strip(), u'V')
self.add_verse(
verse.strip(), VerseType.Tags[VerseType.Verse])
if self.stop_import_flag:
success = False
break
self.finish()
if not self.stop_import_flag:
self.import_wizard.incrementProgressBar(u'')
if not self.finish():
self.log_error(self.import_source)
db_file.close()
self.memo_file.close()
return success
def find_field(self, field_name):
return [i for i, x in enumerate(self.field_descs) \
return [i for i, x in enumerate(self.field_descs)
if x.name == field_name][0]
def set_record_struct(self, field_descs):

View File

@ -97,6 +97,7 @@ from openlp.core.ui.wizard import WizardStrings
from openlp.plugins.songs.lib import clean_song, VerseType
from openlp.plugins.songs.lib.songimport import SongImport
from openlp.plugins.songs.lib.db import Author, Book, Song, Topic
from openlp.plugins.songs.lib.ui import SongStrings
from openlp.plugins.songs.lib.xml import SongXML
log = logging.getLogger(__name__)
@ -121,17 +122,16 @@ class FoilPresenterImport(SongImport):
parser = etree.XMLParser(remove_blank_text=True)
for file_path in self.import_source:
if self.stop_import_flag:
return False
return
self.import_wizard.incrementProgressBar(
WizardStrings.ImportingType % os.path.basename(file_path))
try:
parsed_file = etree.parse(file_path, parser)
xml = unicode(etree.tostring(parsed_file))
if self.FoilPresenter.xml_to_song(xml) is None:
log.debug(u'File could not be imported: %s' % file_path)
self.FoilPresenter.xml_to_song(xml)
except etree.XMLSyntaxError:
self.log_error(file_path, SongStrings.XMLSyntaxError)
log.exception(u'XML syntax error in file %s' % file_path)
return True
class FoilPresenter(object):
@ -211,7 +211,7 @@ class FoilPresenter(object):
"""
# No xml get out of here.
if not xml:
return None
return
if xml[:5] == u'<?xml':
xml = xml[38:]
song = Song()
@ -235,7 +235,6 @@ class FoilPresenter(object):
self._process_topics(foilpresenterfolie, song)
clean_song(self.manager, song)
self.manager.save_object(song)
return song.id
def _child(self, element):
"""
@ -305,9 +304,8 @@ class FoilPresenter(object):
for marker in markers:
copyright = re.compile(marker).sub(u'<marker>', copyright, re.U)
copyright = re.compile(u'(?<=<marker>) *:').sub(u'', copyright)
i = 0
x = 0
while i != 1:
while True:
if copyright.find(u'<marker>') != -1:
temp = copyright.partition(u'<marker>')
if temp[0].strip() and x > 0:
@ -316,9 +314,9 @@ class FoilPresenter(object):
x += 1
elif x > 0:
strings.append(copyright)
i = 1
break
else:
i = 1
break
author_temp = []
for author in strings:
temp = re.split(u',(?=\D{2})|(?<=\D),|\/(?=\D{3,})|(?<=\D);',
@ -349,8 +347,8 @@ class FoilPresenter(object):
if author is None:
# We need to create a new author, as the author does not exist.
author = Author.populate(display_name=display_name,
last_name = display_name.split(u' ')[-1],
first_name = u' '.join(display_name.split(u' ')[:-1]))
last_name=display_name.split(u' ')[-1],
first_name=u' '.join(display_name.split(u' ')[:-1]))
self.manager.save_object(author)
song.authors.append(author)
@ -414,8 +412,15 @@ class FoilPresenter(object):
temp_verse_order_backup = []
temp_sortnr_backup = 1
temp_sortnr_liste = []
versenumber = {u'V': 1, u'C': 1, u'B': 1, u'E': 1, u'O': 1, u'I': 1,
u'P': 1}
versenumber = {
VerseType.Tags[VerseType.Verse]: 1,
VerseType.Tags[VerseType.Chorus]: 1,
VerseType.Tags[VerseType.Bridge]: 1,
VerseType.Tags[VerseType.Ending]: 1,
VerseType.Tags[VerseType.Other]: 1,
VerseType.Tags[VerseType.Intro]: 1,
VerseType.Tags[VerseType.PreChorus]: 1
}
for strophe in foilpresenterfolie.strophen.strophe:
text = self._child(strophe.text_)
verse_name = self._child(strophe.key)
@ -434,25 +439,25 @@ class FoilPresenter(object):
temp_verse_name = re.compile(u'[0-9].*').sub(u'', verse_name)
temp_verse_name = temp_verse_name[:3].lower()
if temp_verse_name == u'ref':
verse_type = u'C'
verse_type = VerseType.Tags[VerseType.Chorus]
elif temp_verse_name == u'r':
verse_type = u'C'
verse_type = VerseType.Tags[VerseType.Chorus]
elif temp_verse_name == u'':
verse_type = u'V'
verse_type = VerseType.Tags[VerseType.Verse]
elif temp_verse_name == u'v':
verse_type = u'V'
verse_type = VerseType.Tags[VerseType.Verse]
elif temp_verse_name == u'bri':
verse_type = u'B'
verse_type = VerseType.Tags[VerseType.Bridge]
elif temp_verse_name == u'cod':
verse_type = u'E'
verse_type = VerseType.Tags[VerseType.Ending]
elif temp_verse_name == u'sch':
verse_type = u'E'
verse_type = VerseType.Tags[VerseType.Ending]
elif temp_verse_name == u'pre':
verse_type = u'P'
verse_type = VerseType.Tags[VerseType.PreChorus]
elif temp_verse_name == u'int':
verse_type = u'I'
verse_type = VerseType.Tags[VerseType.Intro]
else:
verse_type = u'O'
verse_type = VerseType.Tags[VerseType.Other]
verse_number = re.compile(u'[a-zA-Z.+-_ ]*').sub(u'', verse_name)
# Foilpresenter allows e. g. "C", but we need "C1".
if not verse_number:
@ -466,8 +471,8 @@ class FoilPresenter(object):
verse_number = unicode(int(verse_number) + 1)
verse_type_index = VerseType.from_tag(verse_type[0])
verse_type = VerseType.Names[verse_type_index]
temp_verse_order[verse_sortnr] = (u''.join((verse_type[0],
verse_number)))
temp_verse_order[verse_sortnr] = u''.join((verse_type[0],
verse_number))
temp_verse_order_backup.append(u''.join((verse_type[0],
verse_number)))
sxml.add_verse_to_lyrics(verse_type, verse_number, text)

View File

@ -347,6 +347,7 @@ class SongMediaItem(MediaManagerItem):
service_item.add_capability(ItemCapabilities.AllowsLoop)
service_item.add_capability(ItemCapabilities.OnLoadUpdate)
service_item.add_capability(ItemCapabilities.AddIfNewItem)
service_item.add_capability(ItemCapabilities.AllowsVirtualSplit)
song = self.parent.manager.get_object(Song, item_id)
service_item.theme = song.theme_name
service_item.edit_id = item_id

View File

@ -32,7 +32,7 @@ import logging
from chardet.universaldetector import UniversalDetector
import sqlite
from openlp.core.ui.wizard import WizardStrings
from openlp.core.lib import translate
from openlp.plugins.songs.lib import retrieve_windows_encoding
from songimport import SongImport
@ -61,10 +61,15 @@ class OpenLP1SongImport(SongImport):
"""
Run the import for an openlp.org 1.x song database.
"""
# Connect to the database
if not self.import_source.endswith(u'.olp'):
self.log_error(self.import_source,
translate('SongsPlugin.OpenLP1SongImport',
'Not a valid openlp.org 1.x song database.'))
return
encoding = self.get_encoding()
if not encoding:
return False
return
# Connect to the database
connection = sqlite.connect(self.import_source, mode=0444,
encoding=(encoding, 'replace'))
cursor = connection.cursor()
@ -72,12 +77,6 @@ class OpenLP1SongImport(SongImport):
cursor.execute(u'SELECT name FROM sqlite_master '
u'WHERE type = \'table\' AND name = \'tracks\'')
new_db = len(cursor.fetchall()) > 0
# Count the number of records we need to import, for the progress bar
cursor.execute(u'-- types int')
cursor.execute(u'SELECT COUNT(songid) FROM songs')
count = cursor.fetchone()[0]
success = True
self.import_wizard.progressBar.setMaximum(count)
# "cache" our list of authors
cursor.execute(u'-- types int, unicode')
cursor.execute(u'SELECT authorid, authorname FROM authors')
@ -92,37 +91,29 @@ class OpenLP1SongImport(SongImport):
cursor.execute(u'SELECT songid, songtitle, lyrics || \'\' AS lyrics, '
u'copyrightinfo FROM songs')
songs = cursor.fetchall()
self.import_wizard.progressBar.setMaximum(len(songs))
for song in songs:
self.set_defaults()
if self.stop_import_flag:
success = False
break
song_id = song[0]
title = song[1]
self.title = song[1]
lyrics = song[2].replace(u'\r\n', u'\n')
copyright = song[3]
self.import_wizard.incrementProgressBar(
WizardStrings.ImportingType % title)
self.title = title
self.add_copyright(song[3])
verses = lyrics.split(u'\n\n')
for verse in verses:
if verse.strip() != u'':
self.add_verse(verse.strip())
self.add_copyright(copyright)
[self.add_verse(verse.strip()) for verse in verses if verse.strip()]
cursor.execute(u'-- types int')
cursor.execute(u'SELECT authorid FROM songauthors '
u'WHERE songid = %s' % song_id)
author_ids = cursor.fetchall()
for author_id in author_ids:
if self.stop_import_flag:
success = False
break
for author in authors:
if author[0] == author_id[0]:
self.parse_author(author[1])
break
if self.stop_import_flag:
success = False
break
if new_db:
cursor.execute(u'-- types int')
@ -131,17 +122,15 @@ class OpenLP1SongImport(SongImport):
track_ids = cursor.fetchall()
for track_id in track_ids:
if self.stop_import_flag:
success = False
break
for track in tracks:
if track[0] == track_id[0]:
self.add_media_file(track[1])
break
if self.stop_import_flag:
success = False
break
self.finish()
return success
if not self.finish():
self.log_error(self.import_source)
def get_encoding(self):
"""

View File

@ -36,6 +36,7 @@ from sqlalchemy.orm.exc import UnmappedClassError
from openlp.core.lib import translate
from openlp.core.lib.db import BaseModel
from openlp.core.ui.wizard import WizardStrings
from openlp.plugins.songs.lib import clean_song
from openlp.plugins.songs.lib.db import Author, Book, Song, Topic #, MediaFile
from songimport import SongImport
@ -93,13 +94,18 @@ class OpenLPSongImport(SongImport):
The database providing the data to import.
"""
SongImport.__init__(self, manager, **kwargs)
self.import_source = u'sqlite:///%s' % self.import_source
self.source_session = None
def do_import(self):
"""
Run the import for an OpenLP version 2 song database.
"""
if not self.import_source.endswith(u'.sqlite'):
self.log_error(self.import_source,
translate('SongsPlugin.OpenLPSongImport',
'Not a valid OpenLP 2.0 song database.'))
return
self.import_source = u'sqlite:///%s' % self.import_source
engine = create_engine(self.import_source)
source_meta = MetaData()
source_meta.reflect(engine)
@ -124,10 +130,10 @@ class OpenLPSongImport(SongImport):
mapper(OldMediaFile, source_media_files_table)
song_props = {
'authors': relation(OldAuthor, backref='songs',
secondary=source_authors_songs_table),
secondary=source_authors_songs_table),
'book': relation(OldBook, backref='songs'),
'topics': relation(OldTopic, backref='songs',
secondary=source_songs_topics_table)
secondary=source_songs_topics_table)
}
if has_media_files:
song_props['media_files'] = relation(OldMediaFile, backref='songs',
@ -150,15 +156,9 @@ class OpenLPSongImport(SongImport):
mapper(OldTopic, source_topics_table)
source_songs = self.source_session.query(OldSong).all()
song_total = len(source_songs)
if self.import_wizard:
self.import_wizard.progressBar.setMaximum(song_total)
song_count = 1
self.import_wizard.progressBar.setMaximum(len(source_songs))
for song in source_songs:
if self.import_wizard:
self.import_wizard.incrementProgressBar(
unicode(translate('SongsPlugin.OpenLPSongImport',
'Importing song %d of %d.')) % (song_count, song_total))
new_song = Song()
new_song.title = song.title
if has_media_files and hasattr(song, 'alternate_title'):
@ -213,8 +213,9 @@ class OpenLPSongImport(SongImport):
# file_name=media_file.file_name))
clean_song(self.manager, new_song)
self.manager.save_object(new_song)
song_count += 1
if self.import_wizard:
self.import_wizard.incrementProgressBar(
WizardStrings.ImportingType % new_song.title)
if self.stop_import_flag:
return False
break
engine.dispose()
return True

View File

@ -56,13 +56,11 @@ class OooImport(SongImport):
self.process_started = False
def do_import(self):
self.stop_import_flag = False
self.import_wizard.progressBar.setMaximum(0)
self.start_ooo()
self.import_wizard.progressBar.setMaximum(len(self.import_source))
for filename in self.import_source:
if self.stop_import_flag:
self.import_wizard.incrementProgressBar(u'Import cancelled', 0)
return
break
filename = unicode(filename)
if os.path.isfile(filename):
self.open_ooo_file(filename)
@ -70,9 +68,6 @@ class OooImport(SongImport):
self.process_ooo_document()
self.close_ooo_file()
self.close_ooo()
self.import_wizard.progressBar.setMaximum(1)
self.import_wizard.incrementProgressBar(u'', 1)
return True
def process_ooo_document(self):
"""

View File

@ -35,6 +35,7 @@ from lxml import etree
from openlp.core.ui.wizard import WizardStrings
from openlp.plugins.songs.lib.songimport import SongImport
from openlp.plugins.songs.lib.ui import SongStrings
from openlp.plugins.songs.lib import OpenLyrics
log = logging.getLogger(__name__)
@ -59,7 +60,7 @@ class OpenLyricsImport(SongImport):
parser = etree.XMLParser(remove_blank_text=True)
for file_path in self.import_source:
if self.stop_import_flag:
return False
return
self.import_wizard.incrementProgressBar(
WizardStrings.ImportingType % os.path.basename(file_path))
try:
@ -67,8 +68,7 @@ class OpenLyricsImport(SongImport):
# special characters in the path (see lp:757673 and lp:744337).
parsed_file = etree.parse(open(file_path, u'r'), parser)
xml = unicode(etree.tostring(parsed_file))
if self.openLyrics.xml_to_song(xml) is None:
log.debug(u'File could not be imported: %s' % file_path)
self.openLyrics.xml_to_song(xml)
except etree.XMLSyntaxError:
log.exception(u'XML syntax error in file %s' % file_path)
return True
self.log_error(file_path, SongStrings.XMLSyntaxError)

View File

@ -26,13 +26,15 @@
import logging
import os
import re
from zipfile import ZipFile
from lxml import objectify
from lxml.etree import Error, LxmlError
import re
from openlp.core.ui.wizard import WizardStrings
from openlp.plugins.songs.lib import VerseType
from openlp.plugins.songs.lib.songimport import SongImport
from openlp.plugins.songs.lib.ui import SongStrings
log = logging.getLogger(__name__)
@ -105,77 +107,62 @@ class OpenSongImport(SongImport):
Initialise the class.
"""
SongImport.__init__(self, manager, **kwargs)
self.commit = True
def do_import(self):
"""
Import either each of the files in self.import_source - each element of
which can be either a single opensong file, or a zipfile containing
multiple opensong files. If `self.commit` is set False, the
import will not be committed to the database (useful for test scripts).
multiple opensong files.
"""
success = True
numfiles = 0
for filename in self.import_source:
ext = os.path.splitext(filename)[1]
if ext.lower() == u'.zip':
z = ZipFile(filename, u'r')
numfiles += len(z.infolist())
z.close()
else:
numfiles += 1
log.debug(u'Total number of files: %d', numfiles)
self.import_wizard.progressBar.setMaximum(numfiles)
for filename in self.import_source:
if self.stop_import_flag:
success = False
break
return
ext = os.path.splitext(filename)[1]
if ext.lower() == u'.zip':
log.debug(u'Zipfile found %s', filename)
z = ZipFile(filename, u'r')
for song in z.infolist():
if self.stop_import_flag:
success = False
break
z.close()
return
parts = os.path.split(song.filename)
if parts[-1] == u'':
#No final part => directory
# No final part => directory
continue
log.info(u'Zip importing %s', parts[-1])
self.import_wizard.incrementProgressBar(
WizardStrings.ImportingType % parts[-1])
songfile = z.open(song)
if self.do_import_file(songfile) and self.commit and \
not self.stop_import_flag:
self.finish()
else:
success = False
break
song_file = z.open(song)
self.do_import_file(song_file)
song_file.close()
z.close()
else:
# not a zipfile
log.info(u'Direct import %s', filename)
self.import_wizard.incrementProgressBar(
WizardStrings.ImportingType % os.path.split(filename)[-1])
song_file = open(filename)
if self.do_import_file(song_file) and self.commit and \
not self.stop_import_flag:
self.finish()
else:
success = False
break
return success
self.do_import_file(song_file)
song_file.close()
def do_import_file(self, file):
"""
Process the OpenSong file - pass in a file-like object,
not a filename
Process the OpenSong file - pass in a file-like object, not a file path.
"""
self.set_defaults()
try:
tree = objectify.parse(file)
except (Error, LxmlError):
self.log_error(file.name, SongStrings.XMLSyntaxError)
log.exception(u'Error parsing XML')
return False
return
root = tree.getroot()
fields = dir(root)
decode = {
@ -193,9 +180,6 @@ class OpenSongImport(SongImport):
setattr(self, fn_or_string, ustring)
else:
fn_or_string(ustring)
if not len(self.title):
# to prevent creation of empty songs from wrong files
return False
if u'theme' in fields and unicode(root.theme) not in self.topics:
self.topics.append(unicode(root.theme))
if u'alttheme' in fields and unicode(root.alttheme) not in self.topics:
@ -205,11 +189,14 @@ class OpenSongImport(SongImport):
# keep track of verses appearance order
our_verse_order = []
# default verse
verse_tag = u'v'
verse_tag = VerseType.Tags[VerseType.Verse]
verse_num = u'1'
# for the case where song has several sections with same marker
inst = 1
lyrics = unicode(root.lyrics)
if u'lyrics' in fields:
lyrics = unicode(root.lyrics)
else:
lyrics = u''
for this_line in lyrics.split(u'\n'):
# remove comments
semicolon = this_line.find(u';')
@ -230,7 +217,7 @@ class OpenSongImport(SongImport):
# have we got any digits?
# If so, verse number is everything from the digits
# to the end (even if there are some alpha chars on the end)
match = re.match(u'(.*)(\d+.*)', content)
match = re.match(u'(\D*)(\d+.*)', content)
if match is not None:
verse_tag = match.group(1)
verse_num = match.group(2)
@ -239,12 +226,13 @@ class OpenSongImport(SongImport):
# the verse tag
verse_tag = content
verse_num = u'1'
verse_index = VerseType.from_loose_input(verse_tag)
verse_tag = VerseType.Tags[verse_index]
inst = 1
if [verse_tag, verse_num, inst] in our_verse_order \
and verses.has_key(verse_tag) \
and verses[verse_tag].has_key(verse_num):
inst = len(verses[verse_tag][verse_num])+1
our_verse_order.append([verse_tag, verse_num, inst])
inst = len(verses[verse_tag][verse_num]) + 1
continue
# number at start of line.. it's verse number
if this_line[0].isdigit():
@ -257,6 +245,7 @@ class OpenSongImport(SongImport):
verses[verse_tag][verse_num] = {}
if not verses[verse_tag][verse_num].has_key(inst):
verses[verse_tag][verse_num][inst] = []
our_verse_order.append([verse_tag, verse_num, inst])
# Tidy text and remove the ____s from extended words
this_line = self.tidy_text(this_line)
this_line = this_line.replace(u'_', u'')
@ -268,28 +257,31 @@ class OpenSongImport(SongImport):
verse_def = u'%s%s' % (verse_tag, verse_num)
lines = u'\n'.join(verses[verse_tag][verse_num][inst])
self.add_verse(lines, verse_def)
if not self.verses:
self.add_verse('')
# figure out the presentation order, if present
if u'presentation' in fields and root.presentation != u'':
if u'presentation' in fields and root.presentation:
order = unicode(root.presentation)
# We make all the tags in the lyrics lower case, so match that here
# and then split into a list on the whitespace
order = order.lower().split()
for verse_def in order:
match = re.match(u'(.*)(\d+.*)', verse_def)
match = re.match(u'(\D*)(\d+.*)', verse_def)
if match is not None:
verse_tag = match.group(1)
verse_num = match.group(2)
if not len(verse_tag):
verse_tag = u'v'
verse_tag = VerseType.Tags[VerseType.Verse]
else:
# Assume it's no.1 if there are no digits
verse_tag = verse_def
verse_num = u'1'
verse_def = u'%s%s' % (verse_tag, verse_num)
if verses.has_key(verse_tag) \
and verses[verse_tag].has_key(verse_num):
if verses.has_key(verse_tag) and \
verses[verse_tag].has_key(verse_num):
self.verse_order_list.append(verse_def)
else:
log.info(u'Got order %s but not in verse tags, dropping'
u'this item from presentation order', verse_def)
return True
if not self.finish():
self.log_error(file.name)

View File

@ -88,7 +88,6 @@ class SofImport(OooImport):
paragraphs = self.document.getText().createEnumeration()
while paragraphs.hasMoreElements():
if self.stop_import_flag:
self.import_wizard.incrementProgressBar(u'Import cancelled', 0)
return
paragraph = paragraphs.nextElement()
if paragraph.supportsService("com.sun.star.text.Paragraph"):

View File

@ -33,9 +33,9 @@ import logging
import os
import re
from openlp.core.ui.wizard import WizardStrings
from openlp.plugins.songs.lib import VerseType
from openlp.plugins.songs.lib.songimport import SongImport
from openlp.plugins.songs.lib.ui import SongStrings
log = logging.getLogger(__name__)
@ -78,58 +78,55 @@ class SongBeamerImport(SongImport):
"""
Receive a single file or a list of files to import.
"""
if isinstance(self.import_source, list):
self.import_wizard.progressBar.setMaximum(
len(self.import_source))
for file in self.import_source:
# TODO: check that it is a valid SongBeamer file
self.set_defaults()
self.current_verse = u''
self.current_verse_type = VerseType.Tags[VerseType.Verse]
read_verses = False
file_name = os.path.split(file)[1]
self.import_wizard.incrementProgressBar(
WizardStrings.ImportingType % file_name, 0)
if os.path.isfile(file):
detect_file = open(file, u'r')
details = chardet.detect(detect_file.read(2048))
detect_file.close()
infile = codecs.open(file, u'r', details['encoding'])
songData = infile.readlines()
infile.close()
else:
return False
self.title = file_name.split('.sng')[0]
read_verses = False
for line in songData:
# Just make sure that the line is of the type 'Unicode'.
line = unicode(line).strip()
if line.startswith(u'#') and not read_verses:
self.parse_tags(line)
elif line.startswith(u'---'):
if self.current_verse:
self.replace_html_tags()
self.add_verse(self.current_verse,
self.current_verse_type)
self.current_verse = u''
self.current_verse_type = VerseType.Tags[VerseType.Verse]
read_verses = True
verse_start = True
elif read_verses:
if verse_start:
verse_start = False
if not self.check_verse_marks(line):
self.current_verse = line + u'\n'
else:
self.current_verse += line + u'\n'
if self.current_verse:
self.replace_html_tags()
self.add_verse(self.current_verse, self.current_verse_type)
if self.check_complete():
self.finish()
self.import_wizard.incrementProgressBar(
WizardStrings.ImportingType % file_name)
return True
self.import_wizard.progressBar.setMaximum(len(self.import_source))
if not isinstance(self.import_source, list):
return
for file in self.import_source:
# TODO: check that it is a valid SongBeamer file
if self.stop_import_flag:
return
self.set_defaults()
self.current_verse = u''
self.current_verse_type = VerseType.Tags[VerseType.Verse]
read_verses = False
file_name = os.path.split(file)[1]
if os.path.isfile(file):
detect_file = open(file, u'r')
details = chardet.detect(detect_file.read(2048))
detect_file.close()
infile = codecs.open(file, u'r', details['encoding'])
songData = infile.readlines()
infile.close()
else:
continue
self.title = file_name.split('.sng')[0]
read_verses = False
for line in songData:
# Just make sure that the line is of the type 'Unicode'.
line = unicode(line).strip()
if line.startswith(u'#') and not read_verses:
self.parse_tags(line)
elif line.startswith(u'---'):
if self.current_verse:
self.replace_html_tags()
self.add_verse(self.current_verse,
self.current_verse_type)
self.current_verse = u''
self.current_verse_type = VerseType.Tags[VerseType.Verse]
read_verses = True
verse_start = True
elif read_verses:
if verse_start:
verse_start = False
if not self.check_verse_marks(line):
self.current_verse = line + u'\n'
else:
self.current_verse += line + u'\n'
if self.current_verse:
self.replace_html_tags()
self.add_verse(self.current_verse, self.current_verse_type)
if not self.finish():
self.log_error(file)
def replace_html_tags(self):
"""
@ -189,7 +186,7 @@ class SongBeamerImport(SongImport):
elif tag_val[0] == u'#Bible':
pass
elif tag_val[0] == u'#Categories':
self.topics = line.split(',')
self.topics = tag_val[1].split(',')
elif tag_val[0] == u'#CCLI':
self.ccli_number = tag_val[1]
elif tag_val[0] == u'#Chords':
@ -236,11 +233,12 @@ class SongBeamerImport(SongImport):
pass
elif tag_val[0] == u'#Rights':
song_book_pub = tag_val[1]
elif tag_val[0] == u'#Songbook':
book_num = tag_val[1].split(' / ')
self.song_book_name = book_num[0]
if len(book_num) == book_num[1]:
self.song_number = u''
elif tag_val[0] == u'#Songbook' or tag_val[0] == u'#SongBook':
book_data = tag_val[1].split(u'/')
self.song_book_name = book_data[0].strip()
if len(book_data) == 2:
number = book_data[1].strip()
self.song_number = number if number.isdigit() else u''
elif tag_val[0] == u'#Speed':
pass
elif tag_val[0] == u'Tempo':
@ -287,5 +285,4 @@ class SongBeamerImport(SongImport):
if marks[1].isdigit():
self.current_verse_type += marks[1]
return True
else:
return False
return False

View File

@ -23,12 +23,14 @@
# 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 re
from PyQt4 import QtCore
from openlp.core.lib import Receiver, translate
from openlp.core.lib import Receiver, translate, check_directory_exists
from openlp.core.ui.wizard import WizardStrings
from openlp.core.utils import AppLocation
from openlp.plugins.songs.lib import clean_song, VerseType
from openlp.plugins.songs.lib.db import Song, Author, Topic, Book, MediaFile
from openlp.plugins.songs.lib.ui import SongStrings
@ -66,6 +68,7 @@ class SongImport(QtCore.QObject):
self.song = None
self.stop_import_flag = False
self.set_defaults()
self.error_log = []
QtCore.QObject.connect(Receiver.get_receiver(),
QtCore.SIGNAL(u'openlp_stop_wizard'), self.stop_import)
@ -94,6 +97,32 @@ class SongImport(QtCore.QObject):
self.copyright_string = unicode(translate(
'SongsPlugin.SongImport', 'copyright'))
def log_error(self, filepath, reason=SongStrings.SongIncomplete):
"""
This should be called, when a song could not be imported.
``filepath``
This should be the file path if ``self.import_source`` is a list
with different files. If it is not a list, but a single file (for
instance a database), then this should be the song's title.
``reason``
The reason, why the import failed. The string should be as
informative as possible.
"""
self.set_defaults()
if self.import_wizard is None:
return
if self.import_wizard.errorReportTextEdit.isHidden():
self.import_wizard.errorReportTextEdit.setText(
translate('SongsPlugin.SongImport',
'The following songs could not be imported:'))
self.import_wizard.errorReportTextEdit.setVisible(True)
self.import_wizard.errorCopyToButton.setVisible(True)
self.import_wizard.errorSaveToButton.setVisible(True)
self.import_wizard.errorReportTextEdit.append(
u'- %s (%s)' % (filepath, reason))
def stop_import(self):
"""
Sets the flag for importers to stop their import
@ -240,7 +269,7 @@ class SongImport(QtCore.QObject):
Author not checked here, if no author then "Author unknown" is
automatically added
"""
if self.title == u'' or len(self.verses) == 0:
if not self.title or not len(self.verses):
return False
else:
return True
@ -249,9 +278,15 @@ class SongImport(QtCore.QObject):
"""
All fields have been set to this song. Write the song to disk.
"""
if not self.check_complete():
self.set_defaults()
return False
log.info(u'committing song %s to database', self.title)
song = Song()
song.title = self.title
if self.import_wizard is not None:
self.import_wizard.incrementProgressBar(
WizardStrings.ImportingType % song.title)
song.alternate_title = self.alternate_title
# Values will be set when cleaning the song.
song.search_title = u''
@ -308,7 +343,7 @@ class SongImport(QtCore.QObject):
publisher=self.song_book_pub)
song.book = song_book
for topictext in self.topics:
if len(topictext) == 0:
if not topictext:
continue
topic = self.manager.get_object_filtered(Topic,
Topic.name == topictext)
@ -318,6 +353,7 @@ class SongImport(QtCore.QObject):
clean_song(self.manager, song)
self.manager.save_object(song)
self.set_defaults()
return True
def print_song(self):
"""

View File

@ -32,6 +32,7 @@ import logging
import struct
from openlp.core.ui.wizard import WizardStrings
from openlp.plugins.songs.lib import VerseType
from openlp.plugins.songs.lib.songimport import SongImport
TITLE = 1
@ -97,83 +98,81 @@ class SongShowPlusImport(SongImport):
Receive a single file or a list of files to import.
"""
if isinstance(self.import_source, list):
self.import_wizard.progressBar.setMaximum(len(self.import_source))
for file in self.import_source:
author = u''
self.sspVerseOrderList = []
otherCount = 0
otherList = {}
file_name = os.path.split(file)[1]
self.import_wizard.incrementProgressBar(
WizardStrings.ImportingType % file_name, 0)
songData = open(file, 'rb')
while (1):
blockKey, = struct.unpack("I", songData.read(4))
# The file ends with 4 NUL's
if blockKey == 0:
break
nextBlockStarts, = struct.unpack("I", songData.read(4))
if blockKey == VERSE or blockKey == CHORUS:
null, verseNo, = struct.unpack("BB", songData.read(2))
elif blockKey == CUSTOM_VERSE:
null, verseNameLength, = struct.unpack("BB",
songData.read(2))
verseName = songData.read(verseNameLength)
lengthDescriptorSize, = struct.unpack("B", songData.read(1))
# Detect if/how long the length descriptor is
if lengthDescriptorSize == 12:
lengthDescriptor, = struct.unpack("I", songData.read(4))
elif lengthDescriptorSize == 2:
lengthDescriptor = 1
elif lengthDescriptorSize == 9:
lengthDescriptor = 0
else:
lengthDescriptor, = struct.unpack("B", songData.read(1))
data = songData.read(lengthDescriptor)
if blockKey == TITLE:
self.title = unicode(data, u'cp1252')
elif blockKey == AUTHOR:
authors = data.split(" / ")
for author in authors:
if author.find(",") !=-1:
authorParts = author.split(", ")
author = authorParts[1] + " " + authorParts[0]
self.parse_author(unicode(author, u'cp1252'))
elif blockKey == COPYRIGHT:
self.add_copyright(unicode(data, u'cp1252'))
elif blockKey == CCLI_NO:
self.ccli_number = int(data)
elif blockKey == VERSE:
self.add_verse(unicode(data, u'cp1252'),
"V%s" % verseNo)
elif blockKey == CHORUS:
self.add_verse(unicode(data, u'cp1252'),
"C%s" % verseNo)
elif blockKey == TOPIC:
self.topics.append(unicode(data, u'cp1252'))
elif blockKey == COMMENTS:
self.comments = unicode(data, u'cp1252')
elif blockKey == VERSE_ORDER:
verseTag = self.toOpenLPVerseTag(data, True)
if verseTag:
self.sspVerseOrderList.append(unicode(verseTag,
u'cp1252'))
elif blockKey == SONG_BOOK:
self.song_book_name = unicode(data, u'cp1252')
elif blockKey == SONG_NUMBER:
self.song_number = ord(data)
elif blockKey == CUSTOM_VERSE:
verseTag = self.toOpenLPVerseTag(verseName)
self.add_verse(unicode(data, u'cp1252'), verseTag)
else:
log.debug("Unrecognised blockKey: %s, data: %s"
%(blockKey, data))
self.verse_order_list = self.sspVerseOrderList
songData.close()
self.finish()
self.import_wizard.incrementProgressBar(
WizardStrings.ImportingType % file_name)
return True
return
self.import_wizard.progressBar.setMaximum(len(self.import_source))
for file in self.import_source:
self.sspVerseOrderList = []
otherCount = 0
otherList = {}
file_name = os.path.split(file)[1]
self.import_wizard.incrementProgressBar(
WizardStrings.ImportingType % file_name, 0)
songData = open(file, 'rb')
while True:
blockKey, = struct.unpack("I", songData.read(4))
# The file ends with 4 NUL's
if blockKey == 0:
break
nextBlockStarts, = struct.unpack("I", songData.read(4))
if blockKey == VERSE or blockKey == CHORUS:
null, verseNo, = struct.unpack("BB", songData.read(2))
elif blockKey == CUSTOM_VERSE:
null, verseNameLength, = struct.unpack("BB",
songData.read(2))
verseName = songData.read(verseNameLength)
lengthDescriptorSize, = struct.unpack("B", songData.read(1))
# Detect if/how long the length descriptor is
if lengthDescriptorSize == 12:
lengthDescriptor, = struct.unpack("I", songData.read(4))
elif lengthDescriptorSize == 2:
lengthDescriptor = 1
elif lengthDescriptorSize == 9:
lengthDescriptor = 0
else:
lengthDescriptor, = struct.unpack("B", songData.read(1))
data = songData.read(lengthDescriptor)
if blockKey == TITLE:
self.title = unicode(data, u'cp1252')
elif blockKey == AUTHOR:
authors = data.split(" / ")
for author in authors:
if author.find(",") !=-1:
authorParts = author.split(", ")
author = authorParts[1] + " " + authorParts[0]
self.parse_author(unicode(author, u'cp1252'))
elif blockKey == COPYRIGHT:
self.add_copyright(unicode(data, u'cp1252'))
elif blockKey == CCLI_NO:
self.ccli_number = int(data)
elif blockKey == VERSE:
self.add_verse(unicode(data, u'cp1252'),
"V%s" % verseNo)
elif blockKey == CHORUS:
self.add_verse(unicode(data, u'cp1252'),
"C%s" % verseNo)
elif blockKey == TOPIC:
self.topics.append(unicode(data, u'cp1252'))
elif blockKey == COMMENTS:
self.comments = unicode(data, u'cp1252')
elif blockKey == VERSE_ORDER:
verseTag = self.toOpenLPVerseTag(data, True)
if verseTag:
self.sspVerseOrderList.append(unicode(verseTag,
u'cp1252'))
elif blockKey == SONG_BOOK:
self.song_book_name = unicode(data, u'cp1252')
elif blockKey == SONG_NUMBER:
self.song_number = ord(data)
elif blockKey == CUSTOM_VERSE:
verseTag = self.toOpenLPVerseTag(verseName)
self.add_verse(unicode(data, u'cp1252'), verseTag)
else:
log.debug("Unrecognised blockKey: %s, data: %s"
% (blockKey, data))
self.verse_order_list = self.sspVerseOrderList
songData.close()
if not self.finish():
self.log_error(file)
def toOpenLPVerseTag(self, verseName, ignoreUnique=False):
if verseName.find(" ") != -1:
@ -185,22 +184,19 @@ class SongShowPlusImport(SongImport):
verseNumber = "1"
verseType = verseType.lower()
if verseType == "verse":
verseTag = "V"
verseTag = VerseType.Tags[VerseType.Verse]
elif verseType == "chorus":
verseTag = "C"
verseTag = VerseType.Tags[VerseType.Chorus]
elif verseType == "bridge":
verseTag = "B"
verseTag = VerseType.Tags[VerseType.Bridge]
elif verseType == "pre-chorus":
verseTag = "P"
elif verseType == "bridge":
verseTag = "B"
verseTag = VerseType.Tags[VerseType.PreChorus]
else:
if not self.otherList.has_key(verseName):
if ignoreUnique:
return None
self.otherCount = self.otherCount + 1
self.otherList[verseName] = str(self.otherCount)
verseTag = "O"
verseTag = VerseType.Tags[VerseType.Other]
verseNumber = self.otherList[verseName]
verseTag = verseTag + verseNumber
return verseTag
return verseTag + verseNumber

View File

@ -40,6 +40,8 @@ class SongStrings(object):
CopyrightSymbol = translate('OpenLP.Ui', '\xa9', 'Copyright symbol.')
SongBook = translate('OpenLP.Ui', 'Song Book', 'Singular')
SongBooks = translate('OpenLP.Ui', 'Song Books', 'Plural')
SongIncomplete = translate('OpenLP.Ui','Title and/or verses not found')
SongMaintenance = translate('OpenLP.Ui', 'Song Maintenance')
Topic = translate('OpenLP.Ui', 'Topic', 'Singular')
Topics = translate('OpenLP.Ui', 'Topics', 'Plural')
XMLSyntaxError = translate('OpenLP.Ui', 'XML syntax error')

View File

@ -105,11 +105,7 @@ class WowImport(SongImport):
if isinstance(self.import_source, list):
self.import_wizard.progressBar.setMaximum(len(self.import_source))
for file in self.import_source:
author = u''
copyright = u''
file_name = os.path.split(file)[1]
self.import_wizard.incrementProgressBar(
WizardStrings.ImportingType % file_name, 0)
# Get the song title
self.title = file_name.rpartition(u'.')[0]
songData = open(file, 'rb')
@ -129,7 +125,7 @@ class WowImport(SongImport):
self.line_text = unicode(
songData.read(ord(songData.read(1))), u'cp1252')
songData.seek(1, os.SEEK_CUR)
if block_text != u'':
if block_text:
block_text += u'\n'
block_text += self.line_text
self.lines_to_read -= 1
@ -138,22 +134,19 @@ class WowImport(SongImport):
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) < no_of_blocks:
if block + 1 < no_of_blocks:
songData.seek(2, os.SEEK_CUR)
self.add_verse(block_text, block_type)
# Now to extract the author
author_length = ord(songData.read(1))
if author_length != 0:
author = unicode(songData.read(author_length), u'cp1252')
if author_length:
self.parse_author(
unicode(songData.read(author_length), u'cp1252'))
# Finally the copyright
copyright_length = ord(songData.read(1))
if copyright_length != 0:
copyright = unicode(
songData.read(copyright_length), u'cp1252')
self.parse_author(author)
self.add_copyright(copyright)
if copyright_length:
self.add_copyright(unicode(
songData.read(copyright_length), u'cp1252'))
songData.close()
self.finish()
self.import_wizard.incrementProgressBar(
WizardStrings.ImportingType % file_name)
return True
if not self.finish():
self.log_error(file)

View File

@ -32,7 +32,7 @@ The basic XML for storing the lyrics in the song database looks like this::
<song version="1.0">
<lyrics>
<verse type="c" label="1" lang="en">
<![CDATA[ ... ]]>
<![CDATA[Chorus virtual slide 1[---]Chorus virtual slide 2]]>
</verse>
</lyrics>
</song>
@ -129,18 +129,31 @@ class SongXML(object):
The returned list has the following format::
[[{'lang': 'en', 'type': 'v', 'label': '1'}, u"English verse"],
[[{'type': 'v', 'label': '1'},
u"virtual slide 1[---]virtual slide 2"],
[{'lang': 'en', 'type': 'c', 'label': '1'}, u"English chorus"]]
"""
self.song_xml = None
if xml[:5] == u'<?xml':
verse_list = []
if not xml.startswith(u'<?xml') and not xml.startswith(u'<song'):
# This is an old style song, without XML. Let's handle it correctly
# by iterating through the verses, and then recreating the internal
# xml object as well.
self.song_xml = objectify.fromstring(u'<song version="1.0" />')
self.lyrics = etree.SubElement(self.song_xml, u'lyrics')
verses = xml.split(u'\n\n')
for count, verse in enumerate(verses):
verse_list.append([{u'type': u'v', u'label': unicode(count)},
unicode(verse)])
self.add_verse_to_lyrics(u'v', unicode(count), verse)
return verse_list
elif xml.startswith(u'<?xml'):
xml = xml[38:]
try:
self.song_xml = objectify.fromstring(xml)
except etree.XMLSyntaxError:
log.exception(u'Invalid xml %s', xml)
xml_iter = self.song_xml.getiterator()
verse_list = []
for element in xml_iter:
if element.tag == u'verse':
if element.text is None:
@ -226,7 +239,6 @@ class OpenLyrics(object):
Convert the song to OpenLyrics Format.
"""
sxml = SongXML()
verse_list = sxml.get_verses(song.lyrics)
song_xml = objectify.fromstring(u'<song/>')
# Append the necessary meta data to the song.
song_xml.set(u'xmlns', u'http://openlyrics.info/namespace/2009/song')
@ -268,17 +280,26 @@ class OpenLyrics(object):
themes = etree.SubElement(properties, u'themes')
for topic in song.topics:
self._add_text_to_element(u'theme', themes, topic.name)
# Process the song's lyrics.
lyrics = etree.SubElement(song_xml, u'lyrics')
verse_list = sxml.get_verses(song.lyrics)
for verse in verse_list:
verse_tag = u'%s%s' % (
verse[0][u'type'][0].lower(), verse[0][u'label'])
element = \
self._add_text_to_element(u'verse', lyrics, None, verse_tag)
if verse[0].has_key(u'lang'):
element.set(u'lang', verse[0][u'lang'])
element = self._add_text_to_element(u'lines', element)
for line in unicode(verse[1]).split(u'\n'):
self._add_text_to_element(u'line', element, line)
verse_tag = verse[0][u'type'][0].lower()
verse_number = verse[0][u'label']
# Create a list with all "virtual" verses.
virtual_verses = verse[1].split(u'[---]')
for index, virtual_verse in enumerate(virtual_verses):
verse_def = verse_tag + verse_number
# We need "v1a" because we have more than one virtual verse.
if len(virtual_verses) > 1:
verse_def += list(u'abcdefghijklmnopqrstuvwxyz')[index]
element = \
self._add_text_to_element(u'verse', lyrics, None, verse_def)
if verse[0].has_key(u'lang'):
element.set(u'lang', verse[0][u'lang'])
element = self._add_text_to_element(u'lines', element)
for line in virtual_verse.strip(u'\n').split(u'\n'):
self._add_text_to_element(u'line', element, line)
return self._extract_xml(song_xml)
def xml_to_song(self, xml):
@ -446,6 +467,8 @@ class OpenLyrics(object):
The song object.
"""
sxml = SongXML()
verses = {}
verse_def_list = []
for verse in lyrics.verse:
text = u''
for lines in verse.lines:
@ -465,7 +488,15 @@ class OpenLyrics(object):
lang = None
if self._get(verse, u'lang'):
lang = self._get(verse, u'lang')
sxml.add_verse_to_lyrics(verse_tag, verse_number, text, lang)
if verses.has_key((verse_tag, verse_number, lang)):
verses[(verse_tag, verse_number, lang)] += u'\n[---]\n' + text
else:
verses[(verse_tag, verse_number, lang)] = text
verse_def_list.append((verse_tag, verse_number, lang))
# We have to use a list to keep the order, as dicts are not sorted.
for verse in verse_def_list:
sxml.add_verse_to_lyrics(
verse[0], verse[1], verses[verse], verse[2])
song.lyrics = unicode(sxml.extract_xml(), u'utf-8')
# Process verse order
if hasattr(properties, u'verseOrder'):

View File

@ -65,10 +65,12 @@ class SongsPlugin(Plugin):
def initialise(self):
log.info(u'Songs Initialising')
Plugin.initialise(self)
self.songImportItem.setVisible(True)
self.songExportItem.setVisible(True)
self.toolsReindexItem.setVisible(True)
action_list = ActionList.get_instance()
action_list.add_action(self.SongImportItem, UiStrings().Import)
action_list.add_action(self.SongExportItem, UiStrings().Export)
action_list.add_action(self.songImportItem, UiStrings().Import)
action_list.add_action(self.songExportItem, UiStrings().Export)
action_list.add_action(self.toolsReindexItem, UiStrings().Tools)
def addImportMenuItem(self, import_menu):
@ -81,13 +83,13 @@ class SongsPlugin(Plugin):
use it as their parent.
"""
# Main song import menu item - will eventually be the only one
self.SongImportItem = base_action(import_menu, u'SongImportItem')
self.SongImportItem.setText(translate('SongsPlugin', '&Song'))
self.SongImportItem.setToolTip(translate('SongsPlugin',
self.songImportItem = base_action(import_menu, u'songImportItem')
self.songImportItem.setText(translate('SongsPlugin', '&Song'))
self.songImportItem.setToolTip(translate('SongsPlugin',
'Import songs using the import wizard.'))
import_menu.addAction(self.SongImportItem)
import_menu.addAction(self.songImportItem)
# Signals and slots
QtCore.QObject.connect(self.SongImportItem,
QtCore.QObject.connect(self.songImportItem,
QtCore.SIGNAL(u'triggered()'), self.onSongImportItemClicked)
def addExportMenuItem(self, export_menu):
@ -100,13 +102,13 @@ class SongsPlugin(Plugin):
use it as their parent.
"""
# Main song import menu item - will eventually be the only one
self.SongExportItem = base_action(export_menu, u'SongExportItem')
self.SongExportItem.setText(translate('SongsPlugin', '&Song'))
self.SongExportItem.setToolTip(translate('SongsPlugin',
self.songExportItem = base_action(export_menu, u'songExportItem')
self.songExportItem.setText(translate('SongsPlugin', '&Song'))
self.songExportItem.setToolTip(translate('SongsPlugin',
'Exports songs using the export wizard.'))
export_menu.addAction(self.SongExportItem)
export_menu.addAction(self.songExportItem)
# Signals and slots
QtCore.QObject.connect(self.SongExportItem,
QtCore.QObject.connect(self.songExportItem,
QtCore.SIGNAL(u'triggered()'), self.onSongExportItemClicked)
def addToolsMenuItem(self, tools_menu):
@ -256,9 +258,12 @@ class SongsPlugin(Plugin):
"""
log.info(u'Songs Finalising')
self.manager.finalise()
self.songImportItem.setVisible(False)
self.songExportItem.setVisible(False)
self.toolsReindexItem.setVisible(False)
action_list = ActionList.get_instance()
action_list.remove_action(self.SongImportItem, UiStrings().Import)
action_list.remove_action(self.SongExportItem, UiStrings().Export)
action_list.remove_action(self.songImportItem, UiStrings().Import)
action_list.remove_action(self.songExportItem, UiStrings().Export)
action_list.remove_action(self.toolsReindexItem, UiStrings().Tools)
Plugin.finalise(self)
Plugin.finalise(self)

Binary file not shown.

After

Width:  |  Height:  |  Size: 737 B

View File

@ -52,6 +52,7 @@
<file>general_open.png</file>
<file>general_save.png</file>
<file>general_email.png</file>
<file>general_revert.png</file>
</qresource>
<qresource prefix="slides">
<file>slide_close.png</file>