diff --git a/openlp/core/ui/advancedtab.py b/openlp/core/ui/advancedtab.py index ab8228658..67b5c1b04 100644 --- a/openlp/core/ui/advancedtab.py +++ b/openlp/core/ui/advancedtab.py @@ -29,12 +29,12 @@ The :mod:`advancedtab` provides an advanced settings facility. """ from datetime import datetime, timedelta - -from PyQt4 import QtCore, QtGui - import logging import os import sys + +from PyQt4 import QtCore, QtGui + from openlp.core.lib import SettingsTab, translate, build_icon, Receiver from openlp.core.lib.settings import Settings from openlp.core.lib.ui import UiStrings @@ -432,8 +432,7 @@ class AdvancedTab(SettingsTab): translate('OpenLP.AdvancedTab', 'WARNING: New data directory location contains ' 'OpenLP data files. These files WILL be replaced during a copy.')) - self.x11GroupBox.setTitle(translate('OpenLP.AdvancedTab', - 'X11')) + self.x11GroupBox.setTitle(translate('OpenLP.AdvancedTab', 'X11')) self.x11BypassCheckBox.setText(translate('OpenLP.AdvancedTab', 'Bypass X11 Window Manager')) # Slide Limits @@ -493,8 +492,14 @@ class AdvancedTab(SettingsTab): QtCore.QVariant(True)).toBool() self.serviceNameCheckBox.setChecked(default_service_enabled) self.serviceNameCheckBoxToggled(default_service_enabled) - self.x11BypassCheckBox.setChecked( - settings.value(u'x11 bypass wm', QtCore.QVariant(True)).toBool()) + # Fix for bug #1014422. + x11_bypass_default = True + if sys.platform.startswith(u'linux'): + # Default to False on Gnome. + x11_bypass_default = bool(not + os.environ.get(u'GNOME_DESKTOP_SESSION_ID')) + self.x11BypassCheckBox.setChecked(settings.value( + u'x11 bypass wm', QtCore.QVariant(x11_bypass_default)).toBool()) self.defaultColor = settings.value(u'default color', QtCore.QVariant(u'#ffffff')).toString() self.defaultFileEdit.setText(settings.value(u'default image', @@ -766,7 +771,7 @@ class AdvancedTab(SettingsTab): self.dataExists = False self.dataDirectoryCopyCheckBox.setChecked(True) self.newDataDirectoryHasFilesLabel.hide() - + def onDataDirectoryCancelButtonClicked(self): """ Cancel the data directory location change diff --git a/openlp/core/ui/maindisplay.py b/openlp/core/ui/maindisplay.py index 306bb7511..a1cc0215d 100644 --- a/openlp/core/ui/maindisplay.py +++ b/openlp/core/ui/maindisplay.py @@ -31,6 +31,7 @@ and play multimedia within OpenLP. """ import cgi import logging +import os import sys from PyQt4 import QtCore, QtGui, QtWebKit, QtOpenGL @@ -135,8 +136,14 @@ class MainDisplay(Display): self.setStyleSheet(u'border: 0px; margin: 0px; padding: 0px;') windowFlags = QtCore.Qt.FramelessWindowHint | QtCore.Qt.Tool | \ QtCore.Qt.WindowStaysOnTopHint + # Fix for bug #1014422. + x11_bypass_default = True + if sys.platform.startswith(u'linux'): + # Default to False on Gnome. + x11_bypass_default = bool(not + os.environ.get(u'GNOME_DESKTOP_SESSION_ID')) if Settings().value(u'advanced/x11 bypass wm', - QtCore.QVariant(True)).toBool(): + QtCore.QVariant(x11_bypass_default)).toBool(): windowFlags |= QtCore.Qt.X11BypassWindowManagerHint # TODO: The following combination of windowFlags works correctly # on Mac OS X. For next OpenLP version we should test it on other diff --git a/openlp/core/ui/media/vlc.py b/openlp/core/ui/media/vlc.py index db041f658..9948fcd81 100644 --- a/openlp/core/ui/media/vlc.py +++ b/openlp/core/ui/media/vlc.py @@ -48,7 +48,7 @@ import sys from inspect import getargspec __version__ = "N/A" -build_date = "Fri Sep 28 22:48:50 2012" +build_date = "Fri Oct 5 21:35:59 2012" if sys.version_info[0] > 2: str = str @@ -680,6 +680,19 @@ class LogCb(ctypes.c_void_p): \note Log message handlers must be thread-safe. """ pass +class VideoLockCb(ctypes.c_void_p): + """Callback prototype to allocate and lock a picture buffer. +Whenever a new video frame needs to be decoded, the lock callback is +invoked. Depending on the video chroma, one or three pixel planes of +adequate dimensions must be returned via the second parameter. Those +planes must be aligned on 32-bytes boundaries. +\param opaque private pointer as passed to L{libvlc_video_set_callbacks}() [IN] +\param planes start address of the pixel planes (LibVLC allocates the array + of void pointers, this callback must initialize the array) [OUT] +\return a private pointer for the display and unlock callbacks to identify + the picture buffers + """ + pass class VideoUnlockCb(ctypes.c_void_p): """Callback prototype to unlock a picture buffer. When the video frame decoding is complete, the unlock callback is invoked. @@ -687,7 +700,7 @@ This callback might not be needed at all. It is only an indication that the application can now read the pixel values if it needs to. \warning A picture buffer is unlocked after the picture is decoded, but before the picture is displayed. -\param opaque private pointer as passed to libvlc_video_set_callbacks() [IN] +\param opaque private pointer as passed to L{libvlc_video_set_callbacks}() [IN] \param picture private pointer returned from the @ref libvlc_video_lock_cb callback [IN] \param planes pixel planes as defined by the @ref libvlc_video_lock_cb @@ -698,7 +711,7 @@ class VideoDisplayCb(ctypes.c_void_p): """Callback prototype to display a picture. When the video frame needs to be shown, as determined by the media playback clock, the display callback is invoked. -\param opaque private pointer as passed to libvlc_video_set_callbacks() [IN] +\param opaque private pointer as passed to L{libvlc_video_set_callbacks}() [IN] \param picture private pointer returned from the @ref libvlc_video_lock_cb callback [IN] """ @@ -710,7 +723,7 @@ and the chain of video filters (if any). It can opt to change any parameter as it needs. In that case, LibVLC will attempt to convert the video format (rescaling and chroma conversion) but these operations can be CPU intensive. \param opaque pointer to the private pointer passed to - libvlc_video_set_callbacks() [IN/OUT] + L{libvlc_video_set_callbacks}() [IN/OUT] \param chroma pointer to the 4 bytes video format identifier [IN/OUT] \param width pointer to the pixel width [IN/OUT] \param height pointer to the pixel height [IN/OUT] @@ -730,7 +743,7 @@ in the video decoders, video filters and/or video converters. pass class VideoCleanupCb(ctypes.c_void_p): """Callback prototype to configure picture buffers format. -\param opaque private pointer as passed to libvlc_video_set_callbacks() +\param opaque private pointer as passed to L{libvlc_video_set_callbacks}() (and possibly modified by @ref libvlc_video_format_cb) [IN] """ pass @@ -806,6 +819,18 @@ class CallbackDecorators(object): \param args variable argument list for the format \note Log message handlers must be thread-safe. ''' + VideoLockCb = ctypes.CFUNCTYPE(ctypes.c_void_p, ctypes.c_void_p, ListPOINTER(ctypes.c_void_p)) + VideoLockCb.__doc__ = '''Callback prototype to allocate and lock a picture buffer. +Whenever a new video frame needs to be decoded, the lock callback is +invoked. Depending on the video chroma, one or three pixel planes of +adequate dimensions must be returned via the second parameter. Those +planes must be aligned on 32-bytes boundaries. +\param opaque private pointer as passed to L{libvlc_video_set_callbacks}() [IN] +\param planes start address of the pixel planes (LibVLC allocates the array + of void pointers, this callback must initialize the array) [OUT] +\return a private pointer for the display and unlock callbacks to identify + the picture buffers + ''' VideoUnlockCb = ctypes.CFUNCTYPE(ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ListPOINTER(ctypes.c_void_p)) VideoUnlockCb.__doc__ = '''Callback prototype to unlock a picture buffer. When the video frame decoding is complete, the unlock callback is invoked. @@ -813,7 +838,7 @@ This callback might not be needed at all. It is only an indication that the application can now read the pixel values if it needs to. \warning A picture buffer is unlocked after the picture is decoded, but before the picture is displayed. -\param opaque private pointer as passed to libvlc_video_set_callbacks() [IN] +\param opaque private pointer as passed to L{libvlc_video_set_callbacks}() [IN] \param picture private pointer returned from the @ref libvlc_video_lock_cb callback [IN] \param planes pixel planes as defined by the @ref libvlc_video_lock_cb @@ -823,7 +848,7 @@ but before the picture is displayed. VideoDisplayCb.__doc__ = '''Callback prototype to display a picture. When the video frame needs to be shown, as determined by the media playback clock, the display callback is invoked. -\param opaque private pointer as passed to libvlc_video_set_callbacks() [IN] +\param opaque private pointer as passed to L{libvlc_video_set_callbacks}() [IN] \param picture private pointer returned from the @ref libvlc_video_lock_cb callback [IN] ''' @@ -834,7 +859,7 @@ and the chain of video filters (if any). It can opt to change any parameter as it needs. In that case, LibVLC will attempt to convert the video format (rescaling and chroma conversion) but these operations can be CPU intensive. \param opaque pointer to the private pointer passed to - libvlc_video_set_callbacks() [IN/OUT] + L{libvlc_video_set_callbacks}() [IN/OUT] \param chroma pointer to the 4 bytes video format identifier [IN/OUT] \param width pointer to the pixel width [IN/OUT] \param height pointer to the pixel height [IN/OUT] @@ -853,7 +878,7 @@ in the video decoders, video filters and/or video converters. ''' VideoCleanupCb = ctypes.CFUNCTYPE(ctypes.c_void_p, ctypes.c_void_p) VideoCleanupCb.__doc__ = '''Callback prototype to configure picture buffers format. -\param opaque private pointer as passed to libvlc_video_set_callbacks() +\param opaque private pointer as passed to L{libvlc_video_set_callbacks}() (and possibly modified by @ref libvlc_video_format_cb) [IN] ''' AudioPlayCb = ctypes.CFUNCTYPE(ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_uint, ctypes.c_int64) @@ -2483,9 +2508,22 @@ class MediaPlayer(_Ctype): ''' return libvlc_media_player_stop(self) + def video_set_callbacks(self, lock, unlock, display, opaque): + '''Set callbacks and private data to render decoded video to a custom area + in memory. + Use L{video_set_format}() or L{video_set_format_callbacks}() + to configure the decoded format. + @param lock: callback to lock video memory (must not be NULL). + @param unlock: callback to unlock video memory (or NULL if not needed). + @param display: callback to display video (or NULL if not needed). + @param opaque: private pointer for the three callbacks (as first parameter). + @version: LibVLC 1.1.1 or later. + ''' + return libvlc_video_set_callbacks(self, lock, unlock, display, opaque) + def video_set_format(self, chroma, width, height, pitch): '''Set decoded video chroma and dimensions. - This only works in combination with libvlc_video_set_callbacks(), + This only works in combination with L{video_set_callbacks}(), and is mutually exclusive with L{video_set_format_callbacks}(). @param chroma: a four-characters string identifying the chroma (e.g. "RV32" or "YUYV"). @param width: pixel width. @@ -2498,7 +2536,7 @@ class MediaPlayer(_Ctype): def video_set_format_callbacks(self, setup, cleanup): '''Set decoded video chroma and dimensions. This only works in combination with - libvlc_video_set_callbacks(). + L{video_set_callbacks}(). @param setup: callback to select the video format (cannot be NULL). @param cleanup: callback to release any allocated resources (or NULL). @version: LibVLC 2.0.0 or later. @@ -4382,9 +4420,26 @@ def libvlc_media_player_stop(p_mi): None, MediaPlayer) return f(p_mi) +def libvlc_video_set_callbacks(mp, lock, unlock, display, opaque): + '''Set callbacks and private data to render decoded video to a custom area + in memory. + Use L{libvlc_video_set_format}() or L{libvlc_video_set_format_callbacks}() + to configure the decoded format. + @param mp: the media player. + @param lock: callback to lock video memory (must not be NULL). + @param unlock: callback to unlock video memory (or NULL if not needed). + @param display: callback to display video (or NULL if not needed). + @param opaque: private pointer for the three callbacks (as first parameter). + @version: LibVLC 1.1.1 or later. + ''' + f = _Cfunctions.get('libvlc_video_set_callbacks', None) or \ + _Cfunction('libvlc_video_set_callbacks', ((1,), (1,), (1,), (1,), (1,),), None, + None, MediaPlayer, VideoLockCb, VideoUnlockCb, VideoDisplayCb, ctypes.c_void_p) + return f(mp, lock, unlock, display, opaque) + def libvlc_video_set_format(mp, chroma, width, height, pitch): '''Set decoded video chroma and dimensions. - This only works in combination with libvlc_video_set_callbacks(), + This only works in combination with L{libvlc_video_set_callbacks}(), and is mutually exclusive with L{libvlc_video_set_format_callbacks}(). @param mp: the media player. @param chroma: a four-characters string identifying the chroma (e.g. "RV32" or "YUYV"). @@ -4401,7 +4456,7 @@ def libvlc_video_set_format(mp, chroma, width, height, pitch): def libvlc_video_set_format_callbacks(mp, setup, cleanup): '''Set decoded video chroma and dimensions. This only works in combination with - libvlc_video_set_callbacks(). + L{libvlc_video_set_callbacks}(). @param mp: the media player. @param setup: callback to select the video format (cannot be NULL). @param cleanup: callback to release any allocated resources (or NULL). @@ -5878,10 +5933,9 @@ def libvlc_vlm_get_event_manager(p_instance): return f(p_instance) -# 3 function(s) blacklisted: +# 2 function(s) blacklisted: # libvlc_printerr # libvlc_set_exit_handler -# libvlc_video_set_callbacks # 17 function(s) not wrapped as methods: # libvlc_audio_output_list_release diff --git a/openlp/plugins/alerts/alertsplugin.py b/openlp/plugins/alerts/alertsplugin.py index 2d7fd8296..1720fa382 100644 --- a/openlp/plugins/alerts/alertsplugin.py +++ b/openlp/plugins/alerts/alertsplugin.py @@ -105,6 +105,7 @@ CSS = """ font-size: %spt; color: %s; background-color: %s; + word-wrap: break-word; } """ diff --git a/openlp/plugins/alerts/forms/alertform.py b/openlp/plugins/alerts/forms/alertform.py index ab4b6583e..7832f0c7d 100644 --- a/openlp/plugins/alerts/forms/alertform.py +++ b/openlp/plugins/alerts/forms/alertform.py @@ -80,6 +80,10 @@ class AlertForm(QtGui.QDialog, Ui_AlertDialog): item_name = QtGui.QListWidgetItem(alert.text) item_name.setData(QtCore.Qt.UserRole, QtCore.QVariant(alert.id)) self.alertListWidget.addItem(item_name) + if alert.text == unicode(self.alertTextEdit.text()): + self.item_id = alert.id + self.alertListWidget.setCurrentRow( + self.alertListWidget.row(item_name)) def onDisplayClicked(self): self.triggerAlert(unicode(self.alertTextEdit.text())) @@ -112,7 +116,6 @@ class AlertForm(QtGui.QDialog, Ui_AlertDialog): alert = AlertItem() alert.text = unicode(self.alertTextEdit.text()) self.manager.save_object(alert) - self.alertTextEdit.setText(u'') self.loadList() def onSaveClick(self): @@ -125,6 +128,7 @@ class AlertForm(QtGui.QDialog, Ui_AlertDialog): self.manager.save_object(alert) self.item_id = None self.loadList() + self.saveButton.setEnabled(False) def onTextChanged(self): """ diff --git a/openlp/plugins/bibles/lib/__init__.py b/openlp/plugins/bibles/lib/__init__.py index 73e5ebe8b..97e4ce72e 100644 --- a/openlp/plugins/bibles/lib/__init__.py +++ b/openlp/plugins/bibles/lib/__init__.py @@ -355,37 +355,8 @@ def parse_reference(reference, bible, language_selection, book_ref_id=False): log.debug(u'Matched reference %s' % reference) book = match.group(u'book') if not book_ref_id: - book_names = BibleStrings().BookNames - # escape reserved characters - book_escaped = book - for character in u'\\.^$*+?{}[]()': - book_escaped = book_escaped.replace( - character, u'\\' + character) - regex_book = re.compile(u'\s*%s\s*' % u'\s*'.join( - book_escaped.split()), re.UNICODE | re.IGNORECASE) - if language_selection == LanguageSelection.Bible: - db_book = bible.get_book(book) - if db_book: - book_ref_id = db_book.book_reference_id - elif language_selection == LanguageSelection.Application: - books = filter(lambda key: - regex_book.match(unicode(book_names[key])), book_names.keys()) - books = filter(None, map(BiblesResourcesDB.get_book, books)) - for value in books: - if bible.get_book_by_book_ref_id(value[u'id']): - book_ref_id = value[u'id'] - break - elif language_selection == LanguageSelection.English: - books = BiblesResourcesDB.get_books_like(book) - if books: - book_list = filter( - lambda value: regex_book.match(value[u'name']), books) - if not book_list: - book_list = books - for value in book_list: - if bible.get_book_by_book_ref_id(value[u'id']): - book_ref_id = value[u'id'] - break + book_ref_id = bible.get_book_ref_id_by_localised_name( + book, language_selection) elif not bible.get_book_by_book_ref_id(book_ref_id): book_ref_id = False ranges = match.group(u'ranges') diff --git a/openlp/plugins/bibles/lib/db.py b/openlp/plugins/bibles/lib/db.py index acf7cf461..d4fd930ed 100644 --- a/openlp/plugins/bibles/lib/db.py +++ b/openlp/plugins/bibles/lib/db.py @@ -29,6 +29,7 @@ import chardet import logging import os +import re import sqlite3 from PyQt4 import QtCore @@ -44,6 +45,8 @@ import upgrade log = logging.getLogger(__name__) +RESERVED_CHARACTERS = u'\\.^$*+?{}[]()' + class BibleMeta(BaseModel): """ Bible Meta Data @@ -352,6 +355,53 @@ class BibleDB(QtCore.QObject, Manager): book, book_id, language_id) return book_id + def get_book_ref_id_by_localised_name(self, book, + language_selection): + """ + Return the id of a named book. + + ``book`` + The name of the book, according to the selected language. + + ``language_selection`` + The language selection the user has chosen in the settings + section of the Bible. + """ + log.debug(u'get_book_ref_id_by_localised_name("%s", "%s")', + book, language_selection) + from openlp.plugins.bibles.lib import LanguageSelection, \ + BibleStrings + book_names = BibleStrings().BookNames + # escape reserved characters + book_escaped = book + for character in RESERVED_CHARACTERS: + book_escaped = book_escaped.replace( + character, u'\\' + character) + regex_book = re.compile(u'\s*%s\s*' % u'\s*'.join( + book_escaped.split()), re.UNICODE | re.IGNORECASE) + if language_selection == LanguageSelection.Bible: + db_book = self.get_book(book) + if db_book: + return db_book.book_reference_id + elif language_selection == LanguageSelection.Application: + books = filter(lambda key: + regex_book.match(unicode(book_names[key])), book_names.keys()) + books = filter(None, map(BiblesResourcesDB.get_book, books)) + for value in books: + if self.get_book_by_book_ref_id(value[u'id']): + return value[u'id'] + elif language_selection == LanguageSelection.English: + books = BiblesResourcesDB.get_books_like(book) + if books: + book_list = filter( + lambda value: regex_book.match(value[u'name']), books) + if not book_list: + book_list = books + for value in book_list: + if self.get_book_by_book_ref_id(value[u'id']): + return value[u'id'] + return False + def get_verses(self, reference_list, show_error=True): """ This is probably the most used function. It retrieves the list of diff --git a/openlp/plugins/bibles/lib/manager.py b/openlp/plugins/bibles/lib/manager.py index a56aac80b..467f7c90a 100644 --- a/openlp/plugins/bibles/lib/manager.py +++ b/openlp/plugins/bibles/lib/manager.py @@ -277,8 +277,9 @@ class BibleManager(object): """ log.debug(u'BibleManager.get_verse_count("%s", "%s", %s)', bible, book, chapter) - db_book = self.db_cache[bible].get_book(book) - book_ref_id = db_book.book_reference_id + language_selection = self.get_language_selection(bible) + book_ref_id = self.db_cache[bible].get_book_ref_id_by_localised_name( + book, language_selection) return self.db_cache[bible].get_verse_count(book_ref_id, chapter) def get_verse_count_by_book_ref_id(self, bible, book_ref_id, chapter): diff --git a/openlp/plugins/remotes/html/index.html b/openlp/plugins/remotes/html/index.html index dad2f8351..148d444bf 100644 --- a/openlp/plugins/remotes/html/index.html +++ b/openlp/plugins/remotes/html/index.html @@ -66,10 +66,10 @@ ${refresh}
@@ -97,10 +97,10 @@ ${refresh}
@@ -127,10 +127,10 @@

${alerts}

@@ -148,10 +148,10 @@

${search}

diff --git a/openlp/plugins/remotes/html/openlp.js b/openlp/plugins/remotes/html/openlp.js index 26c13d2f1..87c82c5b4 100644 --- a/openlp/plugins/remotes/html/openlp.js +++ b/openlp/plugins/remotes/html/openlp.js @@ -55,7 +55,9 @@ window.OpenLP = { ); }, loadService: function (event) { - event.preventDefault(); + if (event) { + event.preventDefault(); + } $.getJSON( "/api/service/list", function (data, status) { @@ -150,6 +152,10 @@ window.OpenLP = { OpenLP.currentSlide = data.results.slide; OpenLP.currentItem = data.results.item; if ($("#service-manager").is(":visible")) { + if (OpenLP.currentService != data.results.service) { + OpenLP.currentService = data.results.service; + OpenLP.loadService(); + } $("#service-manager div[data-role=content] ul[data-role=listview] li").attr("data-theme", "c").removeClass("ui-btn-up-e").addClass("ui-btn-up-c"); $("#service-manager div[data-role=content] ul[data-role=listview] li a").each(function () { var item = $(this); @@ -307,7 +313,7 @@ window.OpenLP = { } ); }, - escapeString: function (string) { + escapeString: function (string) { return string.replace(/\\/g, "\\\\").replace(/"/g, "\\\"") } } diff --git a/openlp/plugins/remotes/html/stage.js b/openlp/plugins/remotes/html/stage.js index 38bd0fe01..6c5f35e1e 100644 --- a/openlp/plugins/remotes/html/stage.js +++ b/openlp/plugins/remotes/html/stage.js @@ -139,8 +139,10 @@ window.OpenLP = { "/api/poll", function (data, status) { OpenLP.updateClock(data); - if (OpenLP.currentItem != data.results.item) { + if (OpenLP.currentItem != data.results.item || + OpenLP.currentService != data.results.service) { OpenLP.currentItem = data.results.item; + OpenLP.currentService = data.results.service; OpenLP.loadSlides(); } else if (OpenLP.currentSlide != data.results.slide) { diff --git a/openlp/plugins/remotes/lib/httpserver.py b/openlp/plugins/remotes/lib/httpserver.py index cd2802542..8a66b919a 100644 --- a/openlp/plugins/remotes/lib/httpserver.py +++ b/openlp/plugins/remotes/lib/httpserver.py @@ -308,7 +308,9 @@ class HttpConnection(object): 'add_and_go_to_service': translate('RemotePlugin.Mobile', 'Add & Go to Service'), 'no_results': translate('RemotePlugin.Mobile', 'No Results'), - 'options': translate('RemotePlugin.Mobile', 'Options') + 'options': translate('RemotePlugin.Mobile', 'Options'), + 'service': translate('RemotePlugin.Mobile', 'Service'), + 'slides': translate('RemotePlugin.Mobile', 'Slides') } def ready_read(self): diff --git a/openlp/plugins/songs/lib/sundayplusimport.py b/openlp/plugins/songs/lib/sundayplusimport.py index fcf324d41..11b54bdc8 100644 --- a/openlp/plugins/songs/lib/sundayplusimport.py +++ b/openlp/plugins/songs/lib/sundayplusimport.py @@ -154,18 +154,20 @@ class SundayPlusImport(SongImport): # If any line inside any verse contains CCLI or # only Public Domain, we treat this as special data: # we remove that line and add data to specific field. + processed_lines = [] for i in xrange(len(lines)): - lines[i] = lines[i].strip() - line = lines[i] - if line[:4].lower() == u'ccli': + line = lines[i].strip() + if line[:3].lower() == u'ccl': m = re.search(r'[0-9]+', line) if m: self.ccliNumber = int(m.group(0)) - lines.pop(i) + continue elif line.lower() == u'public domain': self.copyright = u'Public Domain' - lines.pop(i) - self.addVerse('\n'.join(lines).strip(), verse_type) + continue + processed_lines.append(line) + self.addVerse('\n'.join(processed_lines).strip(), + verse_type) if end == -1: break i = end + 1 diff --git a/openlp/plugins/songs/lib/xml.py b/openlp/plugins/songs/lib/xml.py index 72f0ccd7a..55e9a0706 100644 --- a/openlp/plugins/songs/lib/xml.py +++ b/openlp/plugins/songs/lib/xml.py @@ -260,7 +260,7 @@ class OpenLyrics(object): IMPLEMENTED_VERSION = u'0.8' START_TAGS_REGEX = re.compile(r'\{(\w+)\}') END_TAGS_REGEX = re.compile(r'\{\/(\w+)\}') - VERSE_NUMBER_REGEX = re.compile(u'[a-zA-Z]*') + VERSE_TAG_SPLITTER = re.compile(u'([a-zA-Z]+)([0-9]*)([a-zA-Z]?)') def __init__(self, manager): self.manager = manager @@ -325,10 +325,22 @@ class OpenLyrics(object): # Process the song's lyrics. lyrics = etree.SubElement(song_xml, u'lyrics') verse_list = sxml.get_verses(song.lyrics) + # Add a suffix letter to each verse + verse_tags = [] for verse in verse_list: verse_tag = verse[0][u'type'][0].lower() verse_number = verse[0][u'label'] verse_def = verse_tag + verse_number + verse_tags.append(verse_def) + # Create the letter from the number of duplicates + verse[0][u'suffix'] = chr(96 + verse_tags.count(verse_def)) + # If the verse tag is a duplicate use the suffix letter + for verse in verse_list: + verse_tag = verse[0][u'type'][0].lower() + verse_number = verse[0][u'label'] + verse_def = verse_tag + verse_number + if verse_tags.count(verse_def) > 1: + verse_def += verse[0][u'suffix'] verse_element = \ self._add_text_to_element(u'verse', lyrics, None, verse_def) if u'lang' in verse[0]: @@ -742,11 +754,10 @@ class OpenLyrics(object): if lines.get(u'break') is not None: text += u'\n[---]' verse_def = verse.get(u'name', u' ').lower() - if verse_def[0] in VerseType.Tags: - verse_tag = verse_def[0] - else: + verse_tag, verse_number, verse_part = \ + OpenLyrics.VERSE_TAG_SPLITTER.search(verse_def).groups() + if verse_tag not in VerseType.Tags: verse_tag = VerseType.Tags[VerseType.Other] - verse_number = OpenLyrics.VERSE_NUMBER_REGEX.sub(u'', verse_def) # OpenLyrics allows e. g. "c", but we need "c1". However, this does # not correct the verse order. if not verse_number: @@ -757,13 +768,13 @@ class OpenLyrics(object): if song_xml.get(u'modifiedIn') in (u'1.9.6', u'OpenLP 1.9.6') and \ song_xml.get(u'version') == u'0.7' and \ (verse_tag, verse_number, lang) in verses: - verses[(verse_tag, verse_number, lang)] += u'\n[---]\n' + text + verses[(verse_tag, verse_number, lang, None)] += u'\n[---]\n' + text # Merge v1a, v1b, .... to v1. - elif (verse_tag, verse_number, lang) in verses: + elif (verse_tag, verse_number, lang, verse_part) in verses: verses[(verse_tag, verse_number, lang)] += u'\n' + text else: - verses[(verse_tag, verse_number, lang)] = text - verse_def_list.append((verse_tag, verse_number, lang)) + verses[(verse_tag, verse_number, lang, verse_part)] = text + verse_def_list.append((verse_tag, verse_number, lang, verse_part)) # 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(