diff --git a/documentation/manual/source/pics/configurethemes.png b/documentation/manual/source/pics/configurethemes.png new file mode 100644 index 000000000..1a9dd0d40 Binary files /dev/null and b/documentation/manual/source/pics/configurethemes.png differ diff --git a/openlp.pyw b/openlp.pyw index 425d3c874..0d4e8c200 100755 --- a/openlp.pyw +++ b/openlp.pyw @@ -46,7 +46,7 @@ from openlp.core.ui.firsttimeform import FirstTimeForm from openlp.core.ui.exceptionform import ExceptionForm from openlp.core.ui import SplashScreen, ScreenList from openlp.core.utils import AppLocation, LanguageManager, VersionThread, \ - get_application_version + get_application_version, DelayStartThread log = logging.getLogger() @@ -130,6 +130,7 @@ class OpenLP(QtGui.QApplication): u'general/update check', QtCore.QVariant(True)).toBool() if update_check: VersionThread(self.mainWindow).start() + DelayStartThread(self.mainWindow).start() return self.exec_() def isAlreadyRunning(self): @@ -250,4 +251,4 @@ if __name__ == u'__main__': """ Instantiate and run the application. """ - main() \ No newline at end of file + main() diff --git a/openlp/core/lib/__init__.py b/openlp/core/lib/__init__.py index 89eeb6ad4..687c49094 100644 --- a/openlp/core/lib/__init__.py +++ b/openlp/core/lib/__init__.py @@ -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 diff --git a/openlp/core/lib/mediamanageritem.py b/openlp/core/lib/mediamanageritem.py index e7d7bc4ec..8b63963a0 100644 --- a/openlp/core/lib/mediamanageritem.py +++ b/openlp/core/lib/mediamanageritem.py @@ -554,4 +554,4 @@ class MediaManagerItem(QtGui.QWidget): item_id = remoteItem else: item_id = (item.data(QtCore.Qt.UserRole)).toInt()[0] - return item_id \ No newline at end of file + return item_id diff --git a/openlp/core/lib/plugin.py b/openlp/core/lib/plugin.py index 08be86541..d0d83cd0c 100644 --- a/openlp/core/lib/plugin.py +++ b/openlp/core/lib/plugin.py @@ -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} \ No newline at end of file + self.textStrings[name] = {u'title': title, u'tooltip': tooltip} diff --git a/openlp/core/lib/renderer.py b/openlp/core/lib/renderer.py index 0eeae6abd..bba85d176 100644 --- a/openlp/core/lib/renderer.py +++ b/openlp/core/lib/renderer.py @@ -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'' + 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'[---]') + pages = [] + for slide in slides: + lines = slide.strip(u'\n').split(u'\n') + new_pages = self._paginate_slide(lines, line_break, + self.force_page) + pages.extend(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,146 @@ class Renderer(object): u'*{margin: 0; padding: 0; border: 0;} '\ u'#main {position:absolute; top:0px; %s %s}' \ u'
' % \ - (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'
' - 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'
' + 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'
'): - html_text = html_text[:len(html_text)-4] + while html_text.endswith(u'
'): + html_text = html_text[:-4] formatted.append(html_text) html_text = u'' styled_text = styled_line html_text += line + line_end - if html_text.endswith(u'
'): - html_text = html_text[:len(html_text)-4] + while html_text.endswith(u'
'): + html_text = html_text[:-4] 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'
' + formatted = [] + previous_html = u'' + previous_raw = u'' + lines = text.split(u'\n') + 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: + while previous_raw.endswith(u'
'): + previous_raw = previous_raw[:-4] + 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: + while previous_raw.endswith(u'
'): + previous_raw = previous_raw[:-4] + 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 + while previous_raw.endswith(u'
'): + previous_raw = previous_raw[:-4] + formatted.append(previous_raw) + log.debug(u'_paginate_slide_words - End') + return formatted + + 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' ') + words = line.split(u' ') + return [word + u' ' for word in words] + + 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') + return [line.replace(u'[---]', u'') for line in lines] diff --git a/openlp/core/lib/rendermanager.py b/openlp/core/lib/rendermanager.py deleted file mode 100644 index 65f2b355c..000000000 --- a/openlp/core/lib/rendermanager.py +++ /dev/null @@ -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) diff --git a/openlp/core/lib/serviceitem.py b/openlp/core/lib/serviceitem.py index 5de25f6aa..a2cb014a4 100644 --- a/openlp/core/lib/serviceitem.py +++ b/openlp/core/lib/serviceitem.py @@ -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) \ No newline at end of file + return u'%s : %s' % (start, end) + diff --git a/openlp/core/ui/maindisplay.py b/openlp/core/ui/maindisplay.py index 7843284b3..5fd066a12 100644 --- a/openlp/core/ui/maindisplay.py +++ b/openlp/core/ui/maindisplay.py @@ -70,6 +70,8 @@ class MainDisplay(DisplayWidget): self.videoHide = False self.override = {} self.retranslateUi() + self.mediaObject = None + self.firstTime = True self.setStyleSheet(u'border: 0px; margin: 0px; padding: 0px;') self.setWindowFlags(QtCore.Qt.FramelessWindowHint | QtCore.Qt.Tool | QtCore.Qt.WindowStaysOnTopHint) @@ -78,6 +80,9 @@ class MainDisplay(DisplayWidget): QtCore.SIGNAL(u'maindisplay_hide'), self.hideDisplay) QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'maindisplay_show'), self.showDisplay) + QtCore.QObject.connect(Receiver.get_receiver(), + QtCore.SIGNAL(u'openlp_phonon_creation'), + self.createMediaObject) def retranslateUi(self): """ @@ -102,19 +107,9 @@ class MainDisplay(DisplayWidget): self.videoWidget.setGeometry(QtCore.QRect(0, 0, self.screen[u'size'].width(), self.screen[u'size'].height())) log.debug(u'Setup Phonon for monitor %s' % self.screens.monitor_number) - self.mediaObject = Phonon.MediaObject(self) - self.audio = Phonon.AudioOutput(Phonon.VideoCategory, self.mediaObject) - Phonon.createPath(self.mediaObject, self.videoWidget) - Phonon.createPath(self.mediaObject, self.audio) - QtCore.QObject.connect(self.mediaObject, - QtCore.SIGNAL(u'stateChanged(Phonon::State, Phonon::State)'), - self.videoState) - QtCore.QObject.connect(self.mediaObject, - QtCore.SIGNAL(u'finished()'), - self.videoFinished) - QtCore.QObject.connect(self.mediaObject, - QtCore.SIGNAL(u'tick(qint64)'), - self.videoTick) + if self.isLive: + if not self.firstTime: + self.createMediaObject() log.debug(u'Setup webView for monitor %s' % self.screens.monitor_number) self.webView = QtWebKit.QWebView(self) self.webView.setGeometry(0, 0, @@ -175,6 +170,24 @@ class MainDisplay(DisplayWidget): log.debug( u'Finished setup for monitor %s' % self.screens.monitor_number) + def createMediaObject(self): + self.firstTime = False + log.debug(u'Creating Phonon objects - Start for %s', self.isLive) + self.mediaObject = Phonon.MediaObject(self) + self.audio = Phonon.AudioOutput(Phonon.VideoCategory, self.mediaObject) + Phonon.createPath(self.mediaObject, self.videoWidget) + Phonon.createPath(self.mediaObject, self.audio) + QtCore.QObject.connect(self.mediaObject, + QtCore.SIGNAL(u'stateChanged(Phonon::State, Phonon::State)'), + self.videoState) + QtCore.QObject.connect(self.mediaObject, + QtCore.SIGNAL(u'finished()'), + self.videoFinished) + QtCore.QObject.connect(self.mediaObject, + QtCore.SIGNAL(u'tick(qint64)'), + self.videoTick) + log.debug(u'Creating Phonon objects - Finished for %s', self.isLive) + def text(self, slide): """ Add the slide text from slideController @@ -348,6 +361,8 @@ class MainDisplay(DisplayWidget): """ Loads and starts a video to run with the option of sound """ + if not self.mediaObject: + self.createMediaObject() log.debug(u'video') self.webLoaded = True self.setGeometry(self.screen[u'size']) diff --git a/openlp/core/ui/mainwindow.py b/openlp/core/ui/mainwindow.py index cb01e37a1..aadc1c175 100644 --- a/openlp/core/ui/mainwindow.py +++ b/openlp/core/ui/mainwindow.py @@ -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 @@ -122,12 +122,17 @@ class Ui_MainWindow(object): self.HelpMenu = QtGui.QMenu(self.MenuBar) self.HelpMenu.setObjectName(u'HelpMenu') mainWindow.setMenuBar(self.MenuBar) - self.StatusBar = QtGui.QStatusBar(mainWindow) - self.StatusBar.setObjectName(u'StatusBar') - mainWindow.setStatusBar(self.StatusBar) - self.DefaultThemeLabel = QtGui.QLabel(self.StatusBar) - self.DefaultThemeLabel.setObjectName(u'DefaultThemeLabel') - self.StatusBar.addPermanentWidget(self.DefaultThemeLabel) + self.statusBar = QtGui.QStatusBar(mainWindow) + self.statusBar.setObjectName(u'statusBar') + mainWindow.setStatusBar(self.statusBar) + self.loadProgressBar = QtGui.QProgressBar(self.statusBar) + self.loadProgressBar.setObjectName(u'loadProgressBar') + self.statusBar.addPermanentWidget(self.loadProgressBar) + self.loadProgressBar.hide() + self.loadProgressBar.setValue(0) + self.defaultThemeLabel = QtGui.QLabel(self.statusBar) + self.defaultThemeLabel.setObjectName(u'defaultThemeLabel') + self.statusBar.addPermanentWidget(self.defaultThemeLabel) # Create the MediaManager self.mediaManagerDock = OpenLPDockWidget(mainWindow, u'mediaManagerDock', u':/system/system_mediamanager.png') @@ -545,9 +550,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 +560,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 +786,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() @@ -880,10 +885,10 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): self.setWindowTitle(title) def showStatusMessage(self, message): - self.StatusBar.showMessage(message) + self.statusBar.showMessage(message) def defaultThemeChanged(self, theme): - self.DefaultThemeLabel.setText( + self.defaultThemeLabel.setText( unicode(translate('OpenLP.MainWindow', 'Default Theme: %s')) % theme) @@ -979,7 +984,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): for fileId, filename in enumerate(recentFilesToDisplay): log.debug('Recent file name: %s', filename) action = base_action(self, u'') - action.setText(u'&%d %s' % + action.setText(u'&%d %s' % (fileId + 1, QtCore.QFileInfo(filename).fileName())) action.setData(QtCore.QVariant(filename)) self.connect(action, QtCore.SIGNAL(u'triggered()'), @@ -1008,3 +1013,34 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow): while self.recentFiles.count() > maxRecentFiles: # Don't care what API says takeLast works, removeLast doesn't! self.recentFiles.takeLast() + + def displayProgressBar(self, size): + """ + Make Progress bar visible and set size + """ + self.loadProgressBar.show() + self.loadProgressBar.setMaximum(size) + self.loadProgressBar.setValue(0) + Receiver.send_message(u'openlp_process_events') + + def incrementProgressBar(self): + """ + Increase the Progress Bar value by 1 + """ + self.loadProgressBar.setValue(self.loadProgressBar.value() + 1) + Receiver.send_message(u'openlp_process_events') + + def finishedProgressBar(self): + """ + Trigger it's removal after 2.5 second + """ + self.timer_id = self.startTimer(2500) + + def timerEvent(self, event): + """ + Remove the Progress bar from view. + """ + if event.timerId() == self.timer_id: + self.timer_id = 0 + self.loadProgressBar.hide() + Receiver.send_message(u'openlp_process_events') diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index a2260cce9..8b3e63e4f 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -460,7 +460,11 @@ class ServiceManager(QtGui.QWidget): service = [] write_list = [] total_size = 0 + Receiver.send_message(u'cursor_busy') + # Number of items + 1 to zip it + self.mainwindow.displayProgressBar(len(self.serviceItems) + 1) for item in self.serviceItems: + self.mainwindow.incrementProgressBar() service.append({u'serviceitem': item[u'service_item'].get_service_repr()}) if not item[u'service_item'].uses_file(): @@ -501,6 +505,7 @@ class ServiceManager(QtGui.QWidget): log.debug(u'ServiceManager.saveFile - allowZip64 is %s' % allow_zip_64) zip = None success = True + self.mainwindow.incrementProgressBar() try: zip = zipfile.ZipFile(path_file_name, 'w', zipfile.ZIP_STORED, allow_zip_64) @@ -516,6 +521,8 @@ class ServiceManager(QtGui.QWidget): finally: if zip: zip.close() + self.mainwindow.finishedProgressBar() + Receiver.send_message(u'cursor_normal') if success: self.mainwindow.addRecentFile(path_file_name) self.setModified(False) @@ -576,13 +583,15 @@ class ServiceManager(QtGui.QWidget): items = cPickle.load(fileTo) fileTo.close() self.newFile() + self.mainwindow.displayProgressBar(len(items)) for item in items: + self.mainwindow.incrementProgressBar() 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) + self.addServiceItem(serviceItem, repaint=False) if serviceItem.is_capable(ItemCapabilities.OnLoadUpdate): Receiver.send_message(u'%s_service_load' % serviceItem.name.lower(), serviceItem) @@ -592,7 +601,6 @@ class ServiceManager(QtGui.QWidget): self.setModified(False) QtCore.QSettings().setValue( 'service/last file', QtCore.QVariant(fileName)) - Receiver.send_message(u'cursor_normal') else: critical_error_message_box( message=translate('OpenLP.ServiceManager', @@ -623,6 +631,9 @@ class ServiceManager(QtGui.QWidget): fileTo.close() if zip: zip.close() + self.mainwindow.finishedProgressBar() + Receiver.send_message(u'cursor_normal') + self.repaintServiceList(-1, -1) def loadLastFile(self): """ @@ -989,7 +1000,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)) @@ -1001,7 +1012,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: @@ -1016,7 +1027,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() @@ -1056,7 +1067,8 @@ class ServiceManager(QtGui.QWidget): newItem) self.setModified() - def addServiceItem(self, item, rebuild=False, expand=None, replace=False): + def addServiceItem(self, item, rebuild=False, expand=None, replace=False, + repaint=True): """ Add a Service item to the list @@ -1089,7 +1101,8 @@ class ServiceManager(QtGui.QWidget): self.serviceItems.append({u'service_item': item, u'order': len(self.serviceItems) + 1, u'expanded': expand}) - self.repaintServiceList(len(self.serviceItems) - 1, -1) + if repaint: + self.repaintServiceList(len(self.serviceItems) - 1, -1) else: self.serviceItems.insert(self.dropPosition, {u'service_item': item, u'order': self.dropPosition, @@ -1278,7 +1291,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): diff --git a/openlp/core/ui/slidecontroller.py b/openlp/core/ui/slidecontroller.py index 5f2c63c23..fdbf184b5 100644 --- a/openlp/core/ui/slidecontroller.py +++ b/openlp/core/ui/slidecontroller.py @@ -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) @@ -268,15 +277,16 @@ class SlideController(QtGui.QWidget): self.slideLayout.setSpacing(0) self.slideLayout.setMargin(0) self.slideLayout.setObjectName(u'SlideLayout') - self.mediaObject = Phonon.MediaObject(self) - self.video = Phonon.VideoWidget() - self.video.setVisible(False) - self.audio = Phonon.AudioOutput(Phonon.VideoCategory, self.mediaObject) - Phonon.createPath(self.mediaObject, self.video) - Phonon.createPath(self.mediaObject, self.audio) if not self.isLive: + self.mediaObject = Phonon.MediaObject(self) + self.video = Phonon.VideoWidget() + self.video.setVisible(False) + self.audio = Phonon.AudioOutput(Phonon.VideoCategory, + self.mediaObject) + Phonon.createPath(self.mediaObject, self.video) + Phonon.createPath(self.mediaObject, self.audio) self.video.setGeometry(QtCore.QRect(0, 0, 300, 225)) - self.slideLayout.insertWidget(0, self.video) + self.slideLayout.insertWidget(0, self.video) # Actual preview screen self.slidePreview = QtGui.QLabel(self) sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, @@ -380,18 +390,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 +425,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 +504,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 +516,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 +633,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 +1011,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 +1178,4 @@ class SlideController(QtGui.QWidget): elif self.desktopScreen.isChecked(): return HideMode.Screen else: - return None \ No newline at end of file + return None diff --git a/openlp/core/ui/thememanager.py b/openlp/core/ui/thememanager.py index 335b9b174..741576938 100644 --- a/openlp/core/ui/thememanager.py +++ b/openlp/core/ui/thememanager.py @@ -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): diff --git a/openlp/core/ui/themestab.py b/openlp/core/ui/themestab.py index fccef1244..20f24d9fe 100644 --- a/openlp/core/ui/themestab.py +++ b/openlp/core/ui/themestab.py @@ -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) \ No newline at end of file + self.DefaultListView.setPixmap(preview) diff --git a/openlp/core/utils/__init__.py b/openlp/core/utils/__init__.py index 1bc7037ae..3051301d6 100644 --- a/openlp/core/utils/__init__.py +++ b/openlp/core/utils/__init__.py @@ -101,6 +101,20 @@ class VersionThread(QtCore.QThread): Receiver.send_message(u'openlp_version_check', u'%s' % version) +class DelayStartThread(QtCore.QThread): + """ + A special Qt thread class to build things after OpenLP has started + """ + def __init__(self, parent): + QtCore.QThread.__init__(self, parent) + + def run(self): + """ + Run the thread. + """ + Receiver.send_message(u'openlp_phonon_creation') + + class AppLocation(object): """ The :class:`AppLocation` class is a static class which retrieves a diff --git a/openlp/plugins/bibles/lib/mediaitem.py b/openlp/plugins/bibles/lib/mediaitem.py index e0e06b02f..c7e1f0bc2 100644 --- a/openlp/plugins/bibles/lib/mediaitem.py +++ b/openlp/plugins/bibles/lib/mediaitem.py @@ -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)) \ No newline at end of file + QtCore.QVariant(self.settings.layout_style)) diff --git a/openlp/plugins/bibles/lib/osis.py b/openlp/plugins/bibles/lib/osis.py index e2eb25b4f..a080524eb 100644 --- a/openlp/plugins/bibles/lib/osis.py +++ b/openlp/plugins/bibles/lib/osis.py @@ -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 `_ Bible format importer class. @@ -60,6 +63,7 @@ class OSISBible(BibleDB): self.lg_regex = re.compile(r'') self.l_regex = re.compile(r'') self.w_regex = re.compile(r'') + self.q_regex = re.compile(r'') self.q1_regex = re.compile(r'') self.q2_regex = re.compile(r'') self.trans_regex = re.compile(r'(.*?)') @@ -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'', u'')\ .replace(u'', u'').replace(u'', u'')\ .replace(u'', u'').replace(u'', u'')\ diff --git a/openlp/plugins/custom/forms/editcustomform.py b/openlp/plugins/custom/forms/editcustomform.py index 9312b5ddd..7b8a15c21 100644 --- a/openlp/plugins/custom/forms/editcustomform.py +++ b/openlp/plugins/custom/forms/editcustomform.py @@ -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) diff --git a/openlp/plugins/custom/forms/editcustomslideform.py b/openlp/plugins/custom/forms/editcustomslideform.py index 7d4e32968..bf775a1f2 100644 --- a/openlp/plugins/custom/forms/editcustomslideform.py +++ b/openlp/plugins/custom/forms/editcustomslideform.py @@ -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() diff --git a/openlp/plugins/custom/lib/mediaitem.py b/openlp/plugins/custom/lib/mediaitem.py index b508104c4..4bd3a8d4b 100644 --- a/openlp/plugins/custom/lib/mediaitem.py +++ b/openlp/plugins/custom/lib/mediaitem.py @@ -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 diff --git a/openlp/plugins/images/lib/mediaitem.py b/openlp/plugins/images/lib/mediaitem.py index 298e701e3..68da7fbe1 100644 --- a/openlp/plugins/images/lib/mediaitem.py +++ b/openlp/plugins/images/lib/mediaitem.py @@ -77,7 +77,7 @@ class ImageMediaItem(MediaManagerItem): u'thumbnails') check_directory_exists(self.servicePath) self.loadList(SettingsManager.load_list( - self.settingsSection, self.settingsSection)) + self.settingsSection, self.settingsSection), True) def addListViewToToolBar(self): MediaManagerItem.addListViewToToolBar(self) @@ -107,8 +107,13 @@ class ImageMediaItem(MediaManagerItem): SettingsManager.set_list(self.settingsSection, self.settingsSection, self.getFileList()) - def loadList(self, list): + def loadList(self, list, initialLoad=False): + Receiver.send_message(u'cursor_busy') + if not initialLoad: + self.parent.formparent.displayProgressBar(len(list)) for imageFile in list: + if not initialLoad: + self.parent.formparent.incrementProgressBar() filename = os.path.split(unicode(imageFile))[1] thumb = os.path.join(self.servicePath, filename) if os.path.exists(thumb): @@ -122,6 +127,9 @@ class ImageMediaItem(MediaManagerItem): item_name.setIcon(icon) item_name.setData(QtCore.Qt.UserRole, QtCore.QVariant(imageFile)) self.listView.addItem(item_name) + Receiver.send_message(u'cursor_normal') + if not initialLoad: + self.parent.formparent.finishedProgressBar() def generateSlideData(self, service_item, item=None, xmlVersion=False): items = self.listView.selectedIndexes() @@ -201,4 +209,4 @@ class ImageMediaItem(MediaManagerItem): critical_error_message_box(UiStrings().LiveBGError, unicode(translate('ImagePlugin.MediaItem', 'There was a problem replacing your background, ' - 'the image file "%s" no longer exists.')) % filename) \ No newline at end of file + 'the image file "%s" no longer exists.')) % filename) diff --git a/openlp/plugins/media/lib/mediaitem.py b/openlp/plugins/media/lib/mediaitem.py index 559af11e2..e494b9e5e 100644 --- a/openlp/plugins/media/lib/mediaitem.py +++ b/openlp/plugins/media/lib/mediaitem.py @@ -50,10 +50,13 @@ class MediaMediaItem(MediaManagerItem): u':/media/media_video.png').toImage() MediaManagerItem.__init__(self, parent, self, icon) self.singleServiceItem = False - self.mediaObject = Phonon.MediaObject(self) + self.mediaObject = None QtCore.QObject.connect(Receiver.get_receiver(), QtCore.SIGNAL(u'video_background_replaced'), self.videobackgroundReplaced) + QtCore.QObject.connect(Receiver.get_receiver(), + QtCore.SIGNAL(u'openlp_phonon_creation'), + self.createPhonon) def retranslateUi(self): self.onNewPrompt = translate('MediaPlugin.MediaItem', 'Select Media') @@ -209,4 +212,9 @@ class MediaMediaItem(MediaManagerItem): img = QtGui.QPixmap(u':/media/media_video.png').toImage() item_name.setIcon(build_icon(img)) item_name.setData(QtCore.Qt.UserRole, QtCore.QVariant(file)) - self.listView.addItem(item_name) \ No newline at end of file + self.listView.addItem(item_name) + + def createPhonon(self): + log.debug(u'CreatePhonon') + if not self.mediaObject: + self.mediaObject = Phonon.MediaObject(self) diff --git a/openlp/plugins/presentations/lib/impresscontroller.py b/openlp/plugins/presentations/lib/impresscontroller.py index 8166a2258..a67d8f818 100644 --- a/openlp/plugins/presentations/lib/impresscontroller.py +++ b/openlp/plugins/presentations/lib/impresscontroller.py @@ -219,7 +219,6 @@ class ImpressDocument(PresentationDocument): The file name of the presentatios to the run. """ log.debug(u'Load Presentation OpenOffice') - #print "s.dsk1 ", self.desktop if os.name == u'nt': desktop = self.controller.get_com_desktop() if desktop is None: @@ -234,7 +233,10 @@ class ImpressDocument(PresentationDocument): return False self.desktop = desktop properties = [] - properties.append(self.create_property(u'Minimized', True)) + if os.name != u'nt': + # Recent versions of Impress on Windows won't start the presentation + # if it starts as minimized. It seems OK on Linux though. + properties.append(self.create_property(u'Minimized', True)) properties = tuple(properties) try: self.document = desktop.loadComponentFromURL(url, u'_blank', @@ -242,9 +244,15 @@ class ImpressDocument(PresentationDocument): except: log.exception(u'Failed to load presentation %s' % url) return False + if os.name == u'nt': + # As we can't start minimized the Impress window gets in the way. + # Either window.setPosSize(0, 0, 200, 400, 12) or .setVisible(False) + window = self.document.getCurrentController().getFrame() \ + .getContainerWindow() + window.setVisible(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 @@ -387,14 +395,14 @@ class ImpressDocument(PresentationDocument): log.debug(u'start presentation OpenOffice') if self.control is None or not self.control.isRunning(): self.presentation.start() - # start() returns before the getCurrentComponent is ready. - # Try for 5 seconds + self.control = self.presentation.getController() + # start() returns before the Component is ready. + # Try for 15 seconds i = 1 - while self.desktop.getCurrentComponent() is None and i < 50: + while not self.control and i < 150: time.sleep(0.1) i = i + 1 - self.control = \ - self.desktop.getCurrentComponent().Presentation.getController() + self.control = self.presentation.getController() else: self.control.activate() self.goto_slide(1) diff --git a/openlp/plugins/presentations/lib/mediaitem.py b/openlp/plugins/presentations/lib/mediaitem.py index 74ff3fea8..22db8f39f 100644 --- a/openlp/plugins/presentations/lib/mediaitem.py +++ b/openlp/plugins/presentations/lib/mediaitem.py @@ -158,7 +158,12 @@ class PresentationMediaItem(MediaManagerItem): titles = [] for file in currlist: titles.append(os.path.split(file)[1]) + Receiver.send_message(u'cursor_busy') + if not initialLoad: + self.parent.formparent.displayProgressBar(len(list)) for file in list: + if not initialLoad: + self.parent.formparent.incrementProgressBar() if currlist.count(file) > 0: continue filename = os.path.split(unicode(file))[1] @@ -198,6 +203,9 @@ class PresentationMediaItem(MediaManagerItem): item_name.setData(QtCore.Qt.UserRole, QtCore.QVariant(file)) item_name.setIcon(icon) self.listView.addItem(item_name) + Receiver.send_message(u'cursor_normal') + if not initialLoad: + self.parent.formparent.finishedProgressBar() def onDeleteClick(self): """ @@ -285,7 +293,7 @@ class PresentationMediaItem(MediaManagerItem): "supports" the extension. If none found, then look for a controller which "also supports" it instead. """ - filetype = filename.split(u'.')[1] + filetype = os.path.splitext(filename)[1] if not filetype: return None for controller in self.controllers: @@ -296,4 +304,4 @@ class PresentationMediaItem(MediaManagerItem): if self.controllers[controller].enabled(): if filetype in self.controllers[controller].alsosupports: return controller - return None \ No newline at end of file + return None diff --git a/openlp/plugins/presentations/lib/powerpointcontroller.py b/openlp/plugins/presentations/lib/powerpointcontroller.py index a02bc841a..12e0d2746 100644 --- a/openlp/plugins/presentations/lib/powerpointcontroller.py +++ b/openlp/plugins/presentations/lib/powerpointcontroller.py @@ -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. diff --git a/openlp/plugins/presentations/lib/pptviewcontroller.py b/openlp/plugins/presentations/lib/pptviewcontroller.py index 94fd05f31..354c33361 100644 --- a/openlp/plugins/presentations/lib/pptviewcontroller.py +++ b/openlp/plugins/presentations/lib/pptviewcontroller.py @@ -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) \ No newline at end of file diff --git a/openlp/plugins/songs/forms/editsongform.py b/openlp/plugins/songs/forms/editsongform.py index 0b9c9316f..297318d53 100644 --- a/openlp/plugins/songs/forms/editsongform.py +++ b/openlp/plugins/songs/forms/editsongform.py @@ -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()) \ No newline at end of file + sxml.dump_xml()) diff --git a/openlp/plugins/songs/lib/__init__.py b/openlp/plugins/songs/lib/__init__.py index 249a76a38..d3ad959bd 100644 --- a/openlp/plugins/songs/lib/__init__.py +++ b/openlp/plugins/songs/lib/__init__.py @@ -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() @@ -293,7 +293,10 @@ def clean_song(manager, song): 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: verse_type = VerseType.Tags[VerseType.from_loose_input(verse_def[0])] diff --git a/openlp/plugins/songs/lib/mediaitem.py b/openlp/plugins/songs/lib/mediaitem.py index 8245adfbc..15fa774ad 100644 --- a/openlp/plugins/songs/lib/mediaitem.py +++ b/openlp/plugins/songs/lib/mediaitem.py @@ -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 @@ -472,4 +473,4 @@ class SongMediaItem(MediaManagerItem): Locale aware collation of song titles """ return locale.strcoll(unicode(song_1.title.lower()), - unicode(song_2.title.lower())) \ No newline at end of file + unicode(song_2.title.lower())) diff --git a/openlp/plugins/songs/lib/xml.py b/openlp/plugins/songs/lib/xml.py index 87e28591e..b11338d81 100644 --- a/openlp/plugins/songs/lib/xml.py +++ b/openlp/plugins/songs/lib/xml.py @@ -32,7 +32,7 @@ The basic XML for storing the lyrics in the song database looks like this:: - + @@ -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'') + 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'') # 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'):