diff --git a/openlp/core/common/applocation.py b/openlp/core/common/applocation.py index 1fce25000..2bc4027b6 100644 --- a/openlp/core/common/applocation.py +++ b/openlp/core/common/applocation.py @@ -110,7 +110,7 @@ class AppLocation(object): :param extension: Defaults to *None*. The extension to search for. For example:: - u'.png' + '.png' """ path = AppLocation.get_data_path() if section: diff --git a/openlp/core/common/settings.py b/openlp/core/common/settings.py index 590452f73..3b7b31ca1 100644 --- a/openlp/core/common/settings.py +++ b/openlp/core/common/settings.py @@ -68,8 +68,7 @@ class Settings(QtCore.QSettings): ``__obsolete_settings__`` Each entry is structured in the following way:: - (u'general/enable slide loop', u'advanced/slide limits', - [(SlideLimits.Wrap, True), (SlideLimits.End, False)]) + ('general/enable slide loop', 'advanced/slide limits', [(SlideLimits.Wrap, True), (SlideLimits.End, False)]) The first entry is the *old key*; it will be removed. diff --git a/openlp/core/lib/__init__.py b/openlp/core/lib/__init__.py index 9561baff4..a7cba33a0 100644 --- a/openlp/core/lib/__init__.py +++ b/openlp/core/lib/__init__.py @@ -296,8 +296,7 @@ def create_separated_list(string_list): :param string_list: List of unicode strings """ - if LooseVersion(Qt.PYQT_VERSION_STR) >= LooseVersion('4.9') and \ - LooseVersion(Qt.qVersion()) >= LooseVersion('4.8'): + if LooseVersion(Qt.PYQT_VERSION_STR) >= LooseVersion('4.9') and LooseVersion(Qt.qVersion()) >= LooseVersion('4.8'): return QtCore.QLocale().createSeparatedList(string_list) if not string_list: return '' diff --git a/openlp/core/lib/plugin.py b/openlp/core/lib/plugin.py index 1f459524c..8cd13088d 100644 --- a/openlp/core/lib/plugin.py +++ b/openlp/core/lib/plugin.py @@ -129,7 +129,7 @@ class Plugin(QtCore.QObject, RegistryProperties): class MyPlugin(Plugin): def __init__(self): - super(MyPlugin, self).__init__('MyPlugin', version=u'0.1') + super(MyPlugin, self).__init__('MyPlugin', version='0.1') :param name: Defaults to *None*. The name of the plugin. :param default_settings: A dict containing the plugin's settings. The value to each key is the default value diff --git a/openlp/core/lib/renderer.py b/openlp/core/lib/renderer.py index 233af3784..71a1f6058 100644 --- a/openlp/core/lib/renderer.py +++ b/openlp/core/lib/renderer.py @@ -248,6 +248,9 @@ class Renderer(OpenLPMixin, RegistryMixin, RegistryProperties): elif item.is_capable(ItemCapabilities.CanSoftBreak): pages = [] if '[---]' in text: + # Remove two or more option slide breaks next to each other (causing infinite loop). + while '\n[---]\n[---]\n' in text: + text = text.replace('\n[---]\n[---]\n', '\n[---]\n') while True: slides = text.split('\n[---]\n', 2) # If there are (at least) two occurrences of [---] we use the first two slides (and neglect the last @@ -392,7 +395,7 @@ class Renderer(OpenLPMixin, RegistryMixin, RegistryProperties): off when displayed. :param lines: The text to be fitted on the slide split into lines. - :param line_end: The text added after each line. Either ``u' '`` or ``u'
``. + :param line_end: The text added after each line. Either ``' '`` or ``'
``. """ formatted = [] previous_html = '' @@ -416,7 +419,7 @@ class Renderer(OpenLPMixin, RegistryMixin, RegistryProperties): processed word by word. This is sometimes need for **bible** verses. :param lines: The text to be fitted on the slide split into lines. - :param line_end: The text added after each line. Either ``u' '`` or ``u'
``. This is needed for **bibles**. + :param line_end: The text added after each line. Either ``' '`` or ``'
``. This is needed for **bibles**. """ formatted = [] previous_html = '' @@ -453,7 +456,7 @@ class Renderer(OpenLPMixin, RegistryMixin, RegistryProperties): """ Tests the given text for not closed formatting tags and returns a tuple consisting of three unicode strings:: - (u'{st}{r}Text text text{/r}{/st}', u'{st}{r}', u'') + ('{st}{r}Text text text{/r}{/st}', '{st}{r}', '') The first unicode string is the text, with correct closing tags. The second unicode string are OpenLP's opening formatting tags and the third unicode string the html opening formatting tags. @@ -500,8 +503,8 @@ class Renderer(OpenLPMixin, RegistryMixin, RegistryProperties): The text contains html. :param raw_list: The elements which do not fit on a slide and needs to be processed using the binary chop. The elements can contain formatting tags. - :param separator: The separator for the elements. For lines this is ``u'
'`` and for words this is ``u' '``. - :param line_end: The text added after each "element line". Either ``u' '`` or ``u'
``. This is needed for + :param separator: The separator for the elements. For lines this is ``'
'`` and for words this is ``' '``. + :param line_end: The text added after each "element line". Either ``' '`` or ``'
``. This is needed for bibles. """ smallest_index = 0 diff --git a/openlp/core/lib/screen.py b/openlp/core/lib/screen.py index 17ead5346..d05200f2f 100644 --- a/openlp/core/lib/screen.py +++ b/openlp/core/lib/screen.py @@ -63,8 +63,7 @@ class ScreenList(object): """ Initialise the screen list. - ``desktop`` - A ``QDesktopWidget`` object. + :param desktop: A QDesktopWidget object. """ screen_list = cls() screen_list.desktop = desktop @@ -136,7 +135,7 @@ class ScreenList(object): Returns a list with the screens. This should only be used to display available screens to the user:: - [u'Screen 1 (primary)', u'Screen 2'] + ['Screen 1 (primary)', 'Screen 2'] """ screen_list = [] for screen in self.screen_list: @@ -153,9 +152,9 @@ class ScreenList(object): :param screen: A dict with the screen properties:: { - u'primary': True, - u'number': 0, - u'size': PyQt4.QtCore.QRect(0, 0, 1024, 768) + 'primary': True, + 'number': 0, + 'size': PyQt4.QtCore.QRect(0, 0, 1024, 768) } """ log.info('Screen %d found with resolution %s' % (screen['number'], screen['size'])) diff --git a/openlp/core/ui/aboutform.py b/openlp/core/ui/aboutform.py index e971bbec4..3825312bd 100644 --- a/openlp/core/ui/aboutform.py +++ b/openlp/core/ui/aboutform.py @@ -30,7 +30,7 @@ The About dialog. """ -from PyQt4 import QtCore, QtGui +from PyQt4 import QtGui from .aboutdialog import Ui_AboutDialog from openlp.core.lib import translate diff --git a/openlp/core/ui/exceptiondialog.py b/openlp/core/ui/exceptiondialog.py index 212fee4cd..b8b3941cd 100644 --- a/openlp/core/ui/exceptiondialog.py +++ b/openlp/core/ui/exceptiondialog.py @@ -30,7 +30,7 @@ The GUI widgets of the exception dialog. """ -from PyQt4 import QtCore, QtGui +from PyQt4 import QtGui from openlp.core.lib import translate from openlp.core.lib.ui import create_button, create_button_box diff --git a/openlp/core/ui/firsttimeform.py b/openlp/core/ui/firsttimeform.py index b9b5f5997..531c4b49f 100644 --- a/openlp/core/ui/firsttimeform.py +++ b/openlp/core/ui/firsttimeform.py @@ -199,8 +199,8 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard, RegistryProperties): self.no_internet_label.setText(self.no_internet_text + self.cancelWizardText) elif page_id == FirstTimePage.Defaults: self.theme_combo_box.clear() - for iter in range(self.themes_list_widget.count()): - item = self.themes_list_widget.item(iter) + for index in range(self.themes_list_widget.count()): + item = self.themes_list_widget.item(index) if item.checkState() == QtCore.Qt.Checked: self.theme_combo_box.addItem(item.text()) if self.has_run_wizard: @@ -292,13 +292,9 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard, RegistryProperties): """ themes = self.config.get('themes', 'files') themes = themes.split(',') - for theme in themes: - filename = self.config.get('theme_%s' % theme, 'filename') + for index, theme in enumerate(themes): screenshot = self.config.get('theme_%s' % theme, 'screenshot') - for index in range(self.themes_list_widget.count()): - item = self.themes_list_widget.item(index) - if item.data(QtCore.Qt.UserRole) == filename: - break + item = self.themes_list_widget.item(index) item.setIcon(build_icon(os.path.join(gettempdir(), 'openlp', screenshot))) def _get_file_size(self, url): diff --git a/openlp/core/ui/mainwindow.py b/openlp/core/ui/mainwindow.py index 81e822c16..9c193b079 100644 --- a/openlp/core/ui/mainwindow.py +++ b/openlp/core/ui/mainwindow.py @@ -1334,7 +1334,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow, RegistryProperties): if self.copy_data: log.info('Copying data to new path') try: - self.showStatusMessage( + self.show_status_message( translate('OpenLP.MainWindow', 'Copying OpenLP data to new data directory location - %s ' '- Please wait for copy to finish').replace('%s', self.new_data_path)) dir_util.copy_tree(old_data_path, self.new_data_path) @@ -1364,8 +1364,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow, RegistryProperties): args = [] for a in self.arguments: args.extend([a]) - for arg in args: - filename = arg + for filename in args: if not isinstance(filename, str): filename = str(filename, sys.getfilesystemencoding()) if filename.endswith(('.osz', '.oszl')): diff --git a/openlp/core/ui/media/mediaplayer.py b/openlp/core/ui/media/mediaplayer.py index 3246d58c4..22ea7ecfc 100644 --- a/openlp/core/ui/media/mediaplayer.py +++ b/openlp/core/ui/media/mediaplayer.py @@ -29,8 +29,6 @@ """ The :mod:`~openlp.core.ui.media.mediaplayer` module contains the MediaPlayer class. """ -import os - from openlp.core.common import RegistryProperties from openlp.core.ui.media import MediaState diff --git a/openlp/core/ui/media/phononplayer.py b/openlp/core/ui/media/phononplayer.py index 5e94dbd0e..b343755a0 100644 --- a/openlp/core/ui/media/phononplayer.py +++ b/openlp/core/ui/media/phononplayer.py @@ -33,10 +33,8 @@ import logging import mimetypes from datetime import datetime -from PyQt4 import QtGui from PyQt4.phonon import Phonon -from openlp.core.common import Settings from openlp.core.lib import translate from openlp.core.ui.media import MediaState diff --git a/openlp/core/ui/printserviceform.py b/openlp/core/ui/printserviceform.py index 489eefa78..41caaab52 100644 --- a/openlp/core/ui/printserviceform.py +++ b/openlp/core/ui/printserviceform.py @@ -242,7 +242,7 @@ class PrintServiceForm(QtGui.QDialog, Ui_PrintServiceDialog, RegistryProperties) Creates a html element. If ``text`` is given, the element's text will set and if a ``parent`` is given, the element is appended. - :param tag: The html tag, e. g. ``u'span'``. Defaults to ``None``. + :param tag: The html tag, e. g. ``'span'``. Defaults to ``None``. :param text: The text for the tag. Defaults to ``None``. :param parent: The parent element. Defaults to ``None``. :param classId: Value for the class attribute diff --git a/openlp/core/ui/shortcutlistform.py b/openlp/core/ui/shortcutlistform.py index 4b64c3b54..dbfbbd439 100644 --- a/openlp/core/ui/shortcutlistform.py +++ b/openlp/core/ui/shortcutlistform.py @@ -244,10 +244,10 @@ class ShortcutListForm(QtGui.QDialog, Ui_ShortcutListDialog, RegistryProperties) self.primary_push_button.setChecked(False) self.alternate_push_button.setChecked(False) else: - if action.defaultShortcuts: - primary_label_text = action.defaultShortcuts[0].toString() - if len(action.defaultShortcuts) == 2: - alternate_label_text = action.defaultShortcuts[1].toString() + if action.default_shortcuts: + primary_label_text = action.default_shortcuts[0].toString() + if len(action.default_shortcuts) == 2: + alternate_label_text = action.default_shortcuts[1].toString() shortcuts = self._action_shortcuts(action) # We do not want to loose pending changes, that is why we have to keep the text when, this function has not # been triggered by a signal. @@ -292,7 +292,7 @@ class ShortcutListForm(QtGui.QDialog, Ui_ShortcutListDialog, RegistryProperties) self._adjust_button(self.alternate_push_button, False, text='') for category in self.action_list.categories: for action in category.actions: - self.changed_actions[action] = action.defaultShortcuts + self.changed_actions[action] = action.default_shortcuts self.refresh_shortcut_list() def on_default_radio_button_clicked(self, toggled): @@ -306,7 +306,7 @@ class ShortcutListForm(QtGui.QDialog, Ui_ShortcutListDialog, RegistryProperties) if action is None: return temp_shortcuts = self._action_shortcuts(action) - self.changed_actions[action] = action.defaultShortcuts + self.changed_actions[action] = action.default_shortcuts self.refresh_shortcut_list() primary_button_text = '' alternate_button_text = '' @@ -357,8 +357,8 @@ class ShortcutListForm(QtGui.QDialog, Ui_ShortcutListDialog, RegistryProperties) return shortcuts = self._action_shortcuts(action) new_shortcuts = [] - if action.defaultShortcuts: - new_shortcuts.append(action.defaultShortcuts[0]) + if action.default_shortcuts: + new_shortcuts.append(action.default_shortcuts[0]) # We have to check if the primary default shortcut is available. But we only have to check, if the action # has a default primary shortcut (an "empty" shortcut is always valid and if the action does not have a # default primary shortcut, then the alternative shortcut (not the default one) will become primary @@ -383,8 +383,8 @@ class ShortcutListForm(QtGui.QDialog, Ui_ShortcutListDialog, RegistryProperties) new_shortcuts = [] if shortcuts: new_shortcuts.append(shortcuts[0]) - if len(action.defaultShortcuts) == 2: - new_shortcuts.append(action.defaultShortcuts[1]) + if len(action.default_shortcuts) == 2: + new_shortcuts.append(action.default_shortcuts[1]) if len(new_shortcuts) == 2: if not self._validiate_shortcut(action, new_shortcuts[1]): return diff --git a/openlp/core/ui/themestab.py b/openlp/core/ui/themestab.py index 1f54f984b..0478f0ed0 100644 --- a/openlp/core/ui/themestab.py +++ b/openlp/core/ui/themestab.py @@ -190,7 +190,7 @@ class ThemesTab(SettingsTab): :param theme_list: The list of available themes:: - [u'Bible Theme', u'Song Theme'] + ['Bible Theme', 'Song Theme'] """ # Reload as may have been triggered by the ThemeManager. self.global_theme = Settings().value(self.settings_section + '/global theme') diff --git a/openlp/core/ui/wizard.py b/openlp/core/ui/wizard.py index 5815457b5..5996296b5 100644 --- a/openlp/core/ui/wizard.py +++ b/openlp/core/ui/wizard.py @@ -279,7 +279,7 @@ class OpenLPWizard(QtGui.QWizard, RegistryProperties): :param filters: The file extension filters. It should contain the file description as well as the file extension. For example:: - u'OpenLP 2.0 Databases (*.sqlite)' + 'OpenLP 2.0 Databases (*.sqlite)' """ if filters: filters += ';;' diff --git a/openlp/core/utils/__init__.py b/openlp/core/utils/__init__.py index a5b5f356a..add663132 100644 --- a/openlp/core/utils/__init__.py +++ b/openlp/core/utils/__init__.py @@ -113,7 +113,7 @@ def get_application_version(): """ Returns the application version of the running instance of OpenLP:: - {u'full': u'1.9.4-bzr1249', u'version': u'1.9.4', u'build': u'bzr1249'} + {'full': '1.9.4-bzr1249', 'version': '1.9.4', 'build': 'bzr1249'} """ global APPLICATION_VERSION if APPLICATION_VERSION: diff --git a/openlp/core/utils/actions.py b/openlp/core/utils/actions.py index 29f2d279b..d81e16b2e 100644 --- a/openlp/core/utils/actions.py +++ b/openlp/core/utils/actions.py @@ -65,20 +65,14 @@ class CategoryActionList(object): self.index = 0 self.actions = [] - def __getitem__(self, key): - """ - Implement the __getitem__() method to make this class a dictionary type - """ - for weight, action in self.actions: - if action.text() == key: - return action - raise KeyError('Action "%s" does not exist.' % key) - - def __contains__(self, item): + def __contains__(self, key): """ Implement the __contains__() method to make this class a dictionary type """ - return item in self + for weight, action in self.actions: + if action == key: + return True + return False def __len__(self): """ @@ -103,23 +97,14 @@ class CategoryActionList(object): self.index += 1 return self.actions[self.index - 1][1] - def has_key(self, key): - """ - Implement the has_key() method to make this class a dictionary type - """ - for weight, action in self.actions: - if action.text() == key: - return True - return False - - def append(self, name): + def append(self, action): """ Append an action """ weight = 0 if self.actions: weight = self.actions[-1][0] + 1 - self.add(name, weight) + self.add(action, weight) def add(self, action, weight=0): """ @@ -128,14 +113,15 @@ class CategoryActionList(object): self.actions.append((weight, action)) self.actions.sort(key=lambda act: act[0]) - def remove(self, remove_action): + def remove(self, action): """ Remove an action """ - for action in self.actions: - if action[1] == remove_action: - self.actions.remove(action) + for item in self.actions: + if item[1] == action: + self.actions.remove(item) return + raise ValueError('Action "%s" does not exist.' % action) class CategoryList(object): @@ -184,9 +170,9 @@ class CategoryList(object): self.index += 1 return self.categories[self.index - 1] - def has_key(self, key): + def __contains__(self, key): """ - Implement the has_key() method to make this class like a dictionary + Implement the __contains__() method to make this class like a dictionary """ for category in self.categories: if category.name == key: @@ -200,10 +186,7 @@ class CategoryList(object): weight = 0 if self.categories: weight = self.categories[-1].weight + 1 - if actions: - self.add(name, weight, actions) - else: - self.add(name, weight) + self.add(name, weight, actions) def add(self, name, weight=0, actions=None): """ @@ -226,6 +209,8 @@ class CategoryList(object): for category in self.categories: if category.name == name: self.categories.remove(category) + return + raise ValueError('Category "%s" does not exist.' % name) class ActionList(object): @@ -270,7 +255,7 @@ class ActionList(object): settings = Settings() settings.beginGroup('shortcuts') # Get the default shortcut from the config. - action.defaultShortcuts = settings.get_default_value(action.objectName()) + action.default_shortcuts = settings.get_default_value(action.objectName()) if weight is None: self.categories[category].actions.append(action) else: diff --git a/openlp/plugins/alerts/forms/__init__.py b/openlp/plugins/alerts/forms/__init__.py index 55d44372e..fbe3c7170 100644 --- a/openlp/plugins/alerts/forms/__init__.py +++ b/openlp/plugins/alerts/forms/__init__.py @@ -32,7 +32,7 @@ other class holds all the functional code, like slots and loading and saving. The first class, commonly known as the **Dialog** class, is typically named ``Ui_Dialog``. It is a slightly modified version of the class that the ``pyuic4`` command produces from Qt4's .ui file. Typical modifications will be -converting most strings from "" to u'' and using OpenLP's ``translate()`` function for translating strings. +converting most strings from "" to '' and using OpenLP's ``translate()`` function for translating strings. The second class, commonly known as the **Form** class, is typically named ``Form``. This class is the one which is instantiated and used. It uses dual inheritance to inherit from (usually) QtGui.QDialog and the Ui class mentioned diff --git a/openlp/plugins/bibles/forms/__init__.py b/openlp/plugins/bibles/forms/__init__.py index 974d6d7a5..d6c77a9a3 100644 --- a/openlp/plugins/bibles/forms/__init__.py +++ b/openlp/plugins/bibles/forms/__init__.py @@ -33,7 +33,7 @@ other class holds all the functional code, like slots and loading and saving. The first class, commonly known as the **Dialog** class, is typically named ``Ui_Dialog``. It is a slightly modified version of the class that the ``pyuic4`` command produces from Qt4's .ui file. Typical modifications will be -converting most strings from "" to u'' and using OpenLP's ``translate()`` function for translating strings. +converting most strings from "" to '' and using OpenLP's ``translate()`` function for translating strings. The second class, commonly known as the **Form** class, is typically named ``Form``. This class is the one which is instantiated and used. It uses dual inheritance to inherit from (usually) QtGui.QDialog and the Ui class mentioned diff --git a/openlp/plugins/bibles/lib/__init__.py b/openlp/plugins/bibles/lib/__init__.py index 50a0e2a63..d67319797 100644 --- a/openlp/plugins/bibles/lib/__init__.py +++ b/openlp/plugins/bibles/lib/__init__.py @@ -262,7 +262,7 @@ def parse_reference(reference, bible, language_selection, book_ref_id=False): For example:: - [(u'John', 3, 16, 18), (u'John', 4, 1, 1)] + [('John', 3, 16, 18), ('John', 4, 1, 1)] **Reference string details:** @@ -311,7 +311,7 @@ def parse_reference(reference, bible, language_selection, book_ref_id=False): ``(?P[0-9]+)`` The ``to_verse`` reference is equivalent to group 2. - The full reference is matched against get_reference_match(u'full'). This regular expression looks like this: + The full reference is matched against get_reference_match('full'). This regular expression looks like this: ``^\s*(?!\s)(?P[\d]*[^\d]+)(?Dialog``. It is a slightly modified version of the class that the ``pyuic4`` command produces from Qt4's .ui file. Typical modifications will be -converting most strings from "" to u'' and using OpenLP's ``translate()`` function for translating strings. +converting most strings from "" to '' and using OpenLP's ``translate()`` function for translating strings. The second class, commonly known as the **Form** class, is typically named ``Form``. This class is the one which is instantiated and used. It uses dual inheritance to inherit from (usually) QtGui.QDialog and the Ui class mentioned diff --git a/openlp/plugins/images/lib/db.py b/openlp/plugins/images/lib/db.py index 68ca3d11d..896f93b17 100644 --- a/openlp/plugins/images/lib/db.py +++ b/openlp/plugins/images/lib/db.py @@ -31,7 +31,7 @@ The :mod:`db` module provides the database and schema that is the backend for th """ from sqlalchemy import Column, ForeignKey, Table, types -from sqlalchemy.orm import mapper, relation, reconstructor +from sqlalchemy.orm import mapper from openlp.core.lib.db import BaseModel, init_db diff --git a/openlp/plugins/media/lib/mediaitem.py b/openlp/plugins/media/lib/mediaitem.py index 87d8e3311..92426334a 100644 --- a/openlp/plugins/media/lib/mediaitem.py +++ b/openlp/plugins/media/lib/mediaitem.py @@ -314,7 +314,6 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties): def get_list(self, type=MediaType.Audio): media = Settings().value(self.settings_section + '/media files') media.sort(key=lambda filename: get_locale_key(os.path.split(str(filename))[1])) - extension = [] if type == MediaType.Audio: extension = self.media_controller.audio_extensions_list else: diff --git a/openlp/plugins/presentations/lib/presentationcontroller.py b/openlp/plugins/presentations/lib/presentationcontroller.py index 6c8d7fb8c..3060bcdb0 100644 --- a/openlp/plugins/presentations/lib/presentationcontroller.py +++ b/openlp/plugins/presentations/lib/presentationcontroller.py @@ -354,7 +354,7 @@ class PresentationController(object): class MyPresentationController(PresentationController): def __init__(self, plugin): PresentationController.__init( - self, plugin, u'My Presenter App') + self, plugin, 'My Presenter App') :param plugin: Defaults to *None*. The presentationplugin object :param name: Name of the application, to appear in the application diff --git a/openlp/plugins/songs/forms/__init__.py b/openlp/plugins/songs/forms/__init__.py index 3094a0eda..eb29c12bf 100644 --- a/openlp/plugins/songs/forms/__init__.py +++ b/openlp/plugins/songs/forms/__init__.py @@ -34,7 +34,7 @@ code, like slots and loading and saving. The first class, commonly known as the **Dialog** class, is typically named ``Ui_Dialog``. It is a slightly modified version of the class that the ``pyuic4`` command produces from Qt4's .ui file. Typical modifications will be -converting most strings from "" to u'' and using OpenLP's ``translate()`` +converting most strings from "" to '' and using OpenLP's ``translate()`` function for translating strings. The second class, commonly known as the **Form** class, is typically named diff --git a/openlp/plugins/songs/forms/editverseform.py b/openlp/plugins/songs/forms/editverseform.py index 79a69a015..bc2532b0d 100644 --- a/openlp/plugins/songs/forms/editverseform.py +++ b/openlp/plugins/songs/forms/editverseform.py @@ -122,8 +122,6 @@ class EditVerseForm(QtGui.QDialog, Ui_EditVerseDialog): text = text[:position + 4] match = VERSE_REGEX.match(text) if match: - # TODO: Not used, remove? - # verse_tag = match.group(1) try: verse_num = int(match.group(2)) + 1 except ValueError: diff --git a/openlp/plugins/songs/forms/songimportform.py b/openlp/plugins/songs/forms/songimportform.py index 21569a034..2a05f06cd 100644 --- a/openlp/plugins/songs/forms/songimportform.py +++ b/openlp/plugins/songs/forms/songimportform.py @@ -231,11 +231,11 @@ class SongImportForm(OpenLPWizard, RegistryProperties): """ Opens a QFileDialog and writes the filenames to the given listbox. - :param title: The title of the dialog (unicode). + :param title: The title of the dialog (str). :param listbox: A listbox (QListWidget). - :param filters: The file extension filters. It should contain the file descriptions - as well as the file extensions. For example:: - u'SongBeamer Files (*.sng)' + :param filters: The file extension filters. It should contain the file descriptions as well as the file + extensions. For example:: + 'SongBeamer Files (*.sng)' """ if filters: filters += ';;' diff --git a/openlp/plugins/songs/forms/songselectform.py b/openlp/plugins/songs/forms/songselectform.py index d3ff5ab52..f9f658c5b 100755 --- a/openlp/plugins/songs/forms/songselectform.py +++ b/openlp/plugins/songs/forms/songselectform.py @@ -319,8 +319,6 @@ class SongSelectForm(QtGui.QDialog, Ui_SongSelectDialog): def on_search_finished(self): """ Slot which is called when the search is completed. - - :param songs: """ self.application.process_events() self.search_progress_bar.setVisible(False) diff --git a/openlp/plugins/songs/lib/__init__.py b/openlp/plugins/songs/lib/__init__.py index aa9fbc4c9..95868ffa7 100644 --- a/openlp/plugins/songs/lib/__init__.py +++ b/openlp/plugins/songs/lib/__init__.py @@ -434,7 +434,7 @@ def strip_rtf(text, default_encoding=None): # Current font is the font tag we last met. font = '' # Character encoding is defined inside fonttable. - # font_table could contain eg u'0': u'cp1252' + # font_table could contain eg '0': u'cp1252' font_table = {'': ''} # Stack of things to keep track of when entering/leaving groups. stack = [] diff --git a/openlp/plugins/songs/lib/easyslidesimport.py b/openlp/plugins/songs/lib/easyslidesimport.py index e28e7bf97..ca9a9b755 100644 --- a/openlp/plugins/songs/lib/easyslidesimport.py +++ b/openlp/plugins/songs/lib/easyslidesimport.py @@ -292,7 +292,7 @@ class EasySlidesImport(SongImport): return True def _extract_region(self, line): - # this was true already: line[0:7] == u'[region': + # this was true already: line[0:7] == '[region': """ Extract the region from text diff --git a/openlp/plugins/songs/lib/ewimport.py b/openlp/plugins/songs/lib/ewimport.py index ca201279a..faa4122c8 100644 --- a/openlp/plugins/songs/lib/ewimport.py +++ b/openlp/plugins/songs/lib/ewimport.py @@ -34,13 +34,13 @@ EasyWorship song databases into the current installation database. import os import struct import re +import zlib from openlp.core.lib import translate from openlp.plugins.songs.lib import VerseType from openlp.plugins.songs.lib import retrieve_windows_encoding, strip_rtf from .songimport import SongImport -RTF_STRIPPING_REGEX = re.compile(r'\{\\tx[^}]*\}') # regex: at least two newlines, can have spaces between them SLIDE_BREAK_REGEX = re.compile(r'\n *?\n[\n ]*') NUMBER_REGEX = re.compile(r'[0-9]+') @@ -77,9 +77,121 @@ class EasyWorshipSongImport(SongImport): def do_import(self): """ - Import the songs + Determines the type of file to import and calls the appropiate method + """ + if self.import_source.lower().endswith('ews'): + self.import_ews() + else: + self.import_db() - :return: + def import_ews(self): + """ + Import the songs from service file + The full spec of the ews files can be found here: + https://github.com/meinders/lithium-ews/blob/master/docs/ews%20file%20format.md + or here: http://wiki.openlp.org/Development:EasyWorship_EWS_Format + """ + # Open ews file if it exists + if not os.path.isfile(self.import_source): + log.debug('Given ews file does not exists.') + return + # Make sure there is room for at least a header and one entry + if os.path.getsize(self.import_source) < 892: + log.debug('Given ews file is to small to contain valid data.') + return + # Take a stab at how text is encoded + self.encoding = 'cp1252' + self.encoding = retrieve_windows_encoding(self.encoding) + if not self.encoding: + log.debug('No encoding set.') + return + self.ews_file = open(self.import_source, 'rb') + # EWS header, version '1.6'/' 3'/' 5': + # Offset Field Data type Length Details + # -------------------------------------------------------------------------------------------------- + # 0 Filetype string 38 Specifies the file type and version. + # "EasyWorship Schedule File Version 1.6" or + # "EasyWorship Schedule File Version 3" or + # "EasyWorship Schedule File Version 5" + # 40/48/56 Entry count int32le 4 Number of items in the schedule + # 44/52/60 Entry length int16le 2 Length of schedule entries: 0x0718 = 1816 + # Get file version + type, = struct.unpack('<38s', self.ews_file.read(38)) + version = type.decode()[-3:] + # Set fileposition based on filetype/version + file_pos = 0 + if version == ' 5': + file_pos = 56 + elif version == ' 3': + file_pos = 48 + elif version == '1.6': + file_pos = 40 + else: + log.debug('Given ews file is of unknown version.') + return + entry_count = self.get_i32(file_pos) + entry_length = self.get_i16(file_pos+4) + file_pos += 6 + self.import_wizard.progress_bar.setMaximum(entry_count) + # Loop over songs + for i in range(entry_count): + # Load EWS entry metadata: + # Offset Field Data type Length Details + # ------------------------------------------------------------------------------------------------ + # 0 Title cstring 50 + # 307 Author cstring 50 + # 358 Copyright cstring 100 + # 459 Administrator cstring 50 + # 800 Content pointer int32le 4 Position of the content for this entry. + # 820 Content type int32le 4 0x01 = Song, 0x02 = Scripture, 0x03 = Presentation, + # 0x04 = Video, 0x05 = Live video, 0x07 = Image, + # 0x08 = Audio, 0x09 = Web + # 1410 Song number cstring 10 + self.set_defaults() + self.title = self.get_string(file_pos + 0, 50) + authors = self.get_string(file_pos + 307, 50) + copyright = self.get_string(file_pos + 358, 100) + admin = self.get_string(file_pos + 459, 50) + cont_ptr = self.get_i32(file_pos + 800) + cont_type = self.get_i32(file_pos + 820) + self.ccli_number = self.get_string(file_pos + 1410, 10) + # Only handle content type 1 (songs) + if cont_type != 1: + file_pos += entry_length + continue + # Load song content + # Offset Field Data type Length Details + # ------------------------------------------------------------------------------------------------ + # 0 Length int32le 4 Length (L) of content, including the compressed content + # and the following fields (14 bytes total). + # 4 Content string L-14 Content compressed with deflate. + # Checksum int32be 4 Alder-32 checksum. + # (unknown) 4 0x51 0x4b 0x03 0x04 + # Content length int32le 4 Length of content after decompression + content_length = self.get_i32(cont_ptr) + deflated_content = self.get_bytes(cont_ptr + 4, content_length - 10) + deflated_length = self.get_i32(cont_ptr + 4 + content_length - 6) + inflated_content = zlib.decompress(deflated_content, 15, deflated_length) + if copyright: + self.copyright = copyright + if admin: + if copyright: + self.copyright += ', ' + self.copyright += translate('SongsPlugin.EasyWorshipSongImport', + 'Administered by %s') % admin + # Set the SongImport object members. + self.set_song_import_object(authors, inflated_content) + if self.stop_import_flag: + break + if not self.finish(): + self.log_error(self.import_source) + # Set file_pos for next entry + file_pos += entry_length + self.ews_file.close() + + def import_db(self): + """ + Import the songs from the database """ # Open the DB and MB files if they exist import_source_mb = self.import_source.replace('.DB', '.MB') @@ -176,7 +288,6 @@ class EasyWorshipSongImport(SongImport): ccli = self.get_field(fi_ccli) authors = self.get_field(fi_author) words = self.get_field(fi_words) - # Set the SongImport object members. if copy: self.copyright = copy.decode() if admin: @@ -187,55 +298,11 @@ class EasyWorshipSongImport(SongImport): if ccli: self.ccli_number = ccli.decode() if authors: - # Split up the authors - author_list = authors.split(b'/') - if len(author_list) < 2: - author_list = authors.split(b';') - if len(author_list) < 2: - author_list = authors.split(b',') - for author_name in author_list: - self.add_author(author_name.decode().strip()) - if words: - # Format the lyrics - result = strip_rtf(words.decode(), self.encoding) - if result is None: - return - words, self.encoding = result - verse_type = VerseType.tags[VerseType.Verse] - for verse in SLIDE_BREAK_REGEX.split(words): - verse = verse.strip() - if not verse: - continue - verse_split = verse.split('\n', 1) - first_line_is_tag = False - # EW tags: verse, chorus, pre-chorus, bridge, tag, - # intro, ending, slide - for tag in VerseType.tags + ['tag', 'slide']: - tag = tag.lower() - ew_tag = verse_split[0].strip().lower() - if ew_tag.startswith(tag): - verse_type = tag[0] - if tag == 'tag' or tag == 'slide': - verse_type = VerseType.tags[VerseType.Other] - first_line_is_tag = True - number_found = False - # check if tag is followed by number and/or note - if len(ew_tag) > len(tag): - match = NUMBER_REGEX.search(ew_tag) - if match: - number = match.group() - verse_type += number - number_found = True - match = NOTE_REGEX.search(ew_tag) - if match: - self.comments += ew_tag + '\n' - if not number_found: - verse_type += '1' - break - self.add_verse(verse_split[-1].strip() if first_line_is_tag else verse, verse_type) - if len(self.comments) > 5: - self.comments += str(translate('SongsPlugin.EasyWorshipSongImport', - '\n[above are Song Tags with notes imported from EasyWorship]')) + authors = authors.decode() + else: + authors = '' + # Set the SongImport object members. + self.set_song_import_object(authors, words) if self.stop_import_flag: break if not self.finish(): @@ -243,12 +310,69 @@ class EasyWorshipSongImport(SongImport): db_file.close() self.memo_file.close() + def set_song_import_object(self, authors, words): + """ + Set the SongImport object members. + + :param authors: String with authons + :param words: Bytes with rtf-encoding + """ + if authors: + # Split up the authors + author_list = authors.split('/') + if len(author_list) < 2: + author_list = authors.split(';') + if len(author_list) < 2: + author_list = authors.split(',') + for author_name in author_list: + self.add_author(author_name.strip()) + if words: + # Format the lyrics + result = strip_rtf(words.decode(), self.encoding) + if result is None: + return + words, self.encoding = result + verse_type = VerseType.tags[VerseType.Verse] + for verse in SLIDE_BREAK_REGEX.split(words): + verse = verse.strip() + if not verse: + continue + verse_split = verse.split('\n', 1) + first_line_is_tag = False + # EW tags: verse, chorus, pre-chorus, bridge, tag, + # intro, ending, slide + for tag in VerseType.tags + ['tag', 'slide']: + tag = tag.lower() + ew_tag = verse_split[0].strip().lower() + if ew_tag.startswith(tag): + verse_type = tag[0] + if tag == 'tag' or tag == 'slide': + verse_type = VerseType.tags[VerseType.Other] + first_line_is_tag = True + number_found = False + # check if tag is followed by number and/or note + if len(ew_tag) > len(tag): + match = NUMBER_REGEX.search(ew_tag) + if match: + number = match.group() + verse_type += number + number_found = True + match = NOTE_REGEX.search(ew_tag) + if match: + self.comments += ew_tag + '\n' + if not number_found: + verse_type += '1' + break + self.add_verse(verse_split[-1].strip() if first_line_is_tag else verse, verse_type) + if len(self.comments) > 5: + self.comments += str(translate('SongsPlugin.EasyWorshipSongImport', + '\n[above are Song Tags with notes imported from EasyWorship]')) + def find_field(self, field_name): """ Find a field in the descriptions :param field_name: field to find - :return: """ return [i for i, x in enumerate(self.field_descriptions) if x.name == field_name][0] @@ -285,7 +409,7 @@ class EasyWorshipSongImport(SongImport): Extract the field :param field_desc_index: Field index value - :return: + :return: The field value """ field = self.fields[field_desc_index] field_desc = self.field_descriptions[field_desc_index] @@ -323,3 +447,52 @@ class EasyWorshipSongImport(SongImport): return self.memo_file.read(blob_size) else: return 0 + + def get_bytes(self, pos, length): + """ + Get bytes from ews_file + + :param pos: Position to read from + :param length: Bytes to read + :return: Bytes read + """ + self.ews_file.seek(pos) + return self.ews_file.read(length) + + def get_string(self, pos, length): + """ + Get string from ews_file + + :param pos: Position to read from + :param length: Characters to read + :return: String read + """ + bytes = self.get_bytes(pos, length) + mask = '<' + str(length) + 's' + byte_str, = struct.unpack(mask, bytes) + return byte_str.decode('unicode-escape').replace('\0', '').strip() + + def get_i16(self, pos): + """ + Get short int from ews_file + + :param pos: Position to read from + :return: Short integer read + """ + + bytes = self.get_bytes(pos, 2) + mask = '