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 = '