← Back to team overview

openlp-core team mailing list archive

[Merge] lp:~trb143/openlp/refactor into lp:openlp

 

Tim Bentley has proposed merging lp:~trb143/openlp/refactor into lp:openlp.

Requested reviews:
  Raoul Snyman (raoul-snyman)

For more details, see:
https://code.launchpad.net/~trb143/openlp/refactor/+merge/194548

This is work in progress and not complete.

This is a start to add trace functionality and clean up logging.

Move de_hump as it has more uses than theme!

add register decorator.

Add some mixins!
Add a common Error handling mixin
Change some invalid error handling as exception should be called only within a except block.

Registry needs to be moved and split up,  Need to agree direction before moving code.
-- 
https://code.launchpad.net/~trb143/openlp/refactor/+merge/194548
Your team OpenLP Core is subscribed to branch lp:openlp.
=== modified file 'openlp/core/__init__.py'
--- openlp/core/__init__.py	2013-10-30 20:34:23 +0000
+++ openlp/core/__init__.py	2013-11-08 17:01:40 +0000
@@ -248,13 +248,13 @@
     usage = 'Usage: %prog [options] [qt-options]'
     parser = OptionParser(usage=usage)
     parser.add_option('-e', '--no-error-form', dest='no_error_form', action='store_true',
-        help='Disable the error notification form.')
+                      help='Disable the error notification form.')
     parser.add_option('-l', '--log-level', dest='loglevel', default='warning', metavar='LEVEL',
-        help='Set logging to LEVEL level. Valid values are "debug", "info", "warning".')
+                      help='Set logging to LEVEL level. Valid values are "debug", "info", "warning".')
     parser.add_option('-p', '--portable', dest='portable', action='store_true',
-        help='Specify if this should be run as a portable app, off a USB flash drive (not implemented).')
+                      help='Specify if this should be run as a portable app, off a USB flash drive (not implemented).')
     parser.add_option('-d', '--dev-version', dest='dev_version', action='store_true',
-        help='Ignore the version file and pull the version directly from Bazaar')
+                      help='Ignore the version file and pull the version directly from Bazaar')
     parser.add_option('-s', '--style', dest='style', help='Set the Qt4 style (passed directly to Qt4).')
     # Parse command line options and deal with them.
     # Use args supplied programatically if possible.

=== modified file 'openlp/core/common/__init__.py'
--- openlp/core/common/__init__.py	2013-10-13 20:36:42 +0000
+++ openlp/core/common/__init__.py	2013-11-08 17:01:40 +0000
@@ -27,17 +27,22 @@
 # Temple Place, Suite 330, Boston, MA 02111-1307 USA                          #
 ###############################################################################
 """
-The :mod:`common` module contains most of the components and libraries that make
-OpenLP work.
+The :mod:`common` module contains most of the components and libraries that make OpenLP work.
 """
 import os
 import logging
+import re
 import sys
 
 from PyQt4 import QtCore
 
+from openlp.core.common.errormixin import ErrorMixin
+
 log = logging.getLogger(__name__)
 
+FIRST_CAMEL_REGEX = re.compile('(.)([A-Z][a-z]+)')
+SECOND_CAMEL_REGEX = re.compile('([a-z0-9])([A-Z])')
+
 
 def check_directory_exists(directory, do_not_log=False):
     """
@@ -55,7 +60,7 @@
         if not os.path.exists(directory):
             os.makedirs(directory)
     except IOError:
-        pass
+        log.exception('Unable to create directory %s ' % directory)
 
 
 def get_frozen_path(frozen_option, non_frozen_option):
@@ -94,6 +99,14 @@
     return qt_translate(context, text, comment, encoding, n)
 
 
+def de_hump(name):
+    """
+    Change any Camel Case string to python string
+    """
+    sub_name = FIRST_CAMEL_REGEX.sub(r'\1_\2', name)
+    return SECOND_CAMEL_REGEX.sub(r'\1_\2', sub_name).lower()
+
+
 class SlideLimits(object):
     """
     Provides an enumeration for behaviour of OpenLP at the end limits of each service item when pressing the up/down
@@ -103,7 +116,8 @@
     Wrap = 2
     Next = 3
 
+from .errormixin import ErrorMixin
+from .logger import trace
 from .uistrings import UiStrings
 from .settings import Settings
 from .applocation import AppLocation
-

=== added file 'openlp/core/common/errormixin.py'
--- openlp/core/common/errormixin.py	1970-01-01 00:00:00 +0000
+++ openlp/core/common/errormixin.py	2013-11-08 17:01:40 +0000
@@ -0,0 +1,55 @@
+# -*- coding: utf-8 -*-
+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
+
+###############################################################################
+# OpenLP - Open Source Lyrics Projection                                      #
+# --------------------------------------------------------------------------- #
+# Copyright (c) 2008-2013 Raoul Snyman                                        #
+# Portions copyright (c) 2008-2013 Tim Bentley, Gerald Britton, Jonathan      #
+# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub,      #
+# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer.   #
+# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru,          #
+# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith,             #
+# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock,              #
+# Frode Woldsund, Martin Zibricky, Patrick Zimmermann                         #
+# --------------------------------------------------------------------------- #
+# This program is free software; you can redistribute it and/or modify it     #
+# under the terms of the GNU General Public License as published by the Free  #
+# Software Foundation; version 2 of the License.                              #
+#                                                                             #
+# This program is distributed in the hope that it will be useful, but WITHOUT #
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or       #
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for    #
+# more details.                                                               #
+#                                                                             #
+# You should have received a copy of the GNU General Public License along     #
+# with this program; if not, write to the Free Software Foundation, Inc., 59  #
+# Temple Place, Suite 330, Boston, MA 02111-1307 USA                          #
+###############################################################################
+"""
+Provide Error Handling Services
+"""
+import traceback
+import logging
+
+
+class ErrorMixin(object):
+    """
+    Base Calling object for OpenLP classes.
+    """
+    def trace_error_handler(self):
+        """
+        Log the calling path of an exception
+        """
+        log = logging.getLogger(self.__module__)
+        for tb in traceback.extract_stack():
+            log.error('Called by ' + tb[3] + ' at line ' + str(tb[1]) + ' in ' + tb[0])
+
+    def log_error(self, message):
+        """
+        Common log exception handler which prints the calling path
+        """
+        log = logging.getLogger(self.__module__)
+        self.trace_error_handler()
+        log.error(message)
+

=== added file 'openlp/core/common/logger.py'
--- openlp/core/common/logger.py	1970-01-01 00:00:00 +0000
+++ openlp/core/common/logger.py	2013-11-08 17:01:40 +0000
@@ -0,0 +1,64 @@
+# -*- coding: utf-8 -*-
+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
+
+###############################################################################
+# OpenLP - Open Source Lyrics Projection                                      #
+# --------------------------------------------------------------------------- #
+# Copyright (c) 2008-2013 Raoul Snyman                                        #
+# Portions copyright (c) 2008-2013 Tim Bentley, Gerald Britton, Jonathan      #
+# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub,      #
+# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer.   #
+# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru,          #
+# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith,             #
+# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock,              #
+# Frode Woldsund, Martin Zibricky, Patrick Zimmermann                         #
+# --------------------------------------------------------------------------- #
+# This program is free software; you can redistribute it and/or modify it     #
+# under the terms of the GNU General Public License as published by the Free  #
+# Software Foundation; version 2 of the License.                              #
+#                                                                             #
+# This program is distributed in the hope that it will be useful, but WITHOUT #
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or       #
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for    #
+# more details.                                                               #
+#                                                                             #
+# You should have received a copy of the GNU General Public License along     #
+# with this program; if not, write to the Free Software Foundation, Inc., 59  #
+# Temple Place, Suite 330, Boston, MA 02111-1307 USA                          #
+###############################################################################
+"""
+This class contains the core logger functions.
+"""
+
+import inspect
+import logging
+
+# Events to never be logged.  Usually timer events which will flood the log!
+DO_NOT_TRACE_EVENTS = ['timerEvent', 'paintEvent']
+
+
+def log_wrapper(func):
+    """
+    Code to added debug wrapper to work on called functions within a decorated class.
+    """
+    def wrapped(*args, **kwargs):
+        log = logging.getLogger(args[0].__module__)
+        if log.getEffectiveLevel() == logging.DEBUG:
+            log.debug("Entering %s" % func.__name__)
+        try:
+            return func(*args, **kwargs)
+        except Exception as e:
+            if log.getEffectiveLevel() <= logging.ERROR:
+                log.error('Exception in %s : %s' % (func.__name__, e))
+            raise e
+    return wrapped
+
+
+def trace(cls):
+    """
+    Decorator to add log_wrapper to all methods in calls unless they are banned!
+    """
+    for name, m in inspect.getmembers(cls, inspect.isfunction):
+        if name not in DO_NOT_TRACE_EVENTS:
+            setattr(cls, name, log_wrapper(m))
+    return cls

=== modified file 'openlp/core/lib/__init__.py'
--- openlp/core/lib/__init__.py	2013-10-13 20:36:42 +0000
+++ openlp/core/lib/__init__.py	2013-11-08 17:01:40 +0000
@@ -272,8 +272,8 @@
         The message to give the user if no item is selected
     """
     if not list_widget.selectedIndexes():
-        QtGui.QMessageBox.information(list_widget.parent(),
-            translate('OpenLP.MediaManagerItem', 'No Items Selected'), message)
+        QtGui.QMessageBox.information(list_widget.parent(), translate('OpenLP.MediaManagerItem',
+                                                                      'No Items Selected'), message)
         return False
     return True
 
@@ -319,17 +319,17 @@
         return string_list[0]
     elif len(string_list) == 2:
         return translate('OpenLP.core.lib', '%s and %s',
-            'Locale list separator: 2 items') % (string_list[0], string_list[1])
+                         'Locale list separator: 2 items') % (string_list[0], string_list[1])
     else:
         merged = translate('OpenLP.core.lib', '%s, and %s',
-            'Locale list separator: end') % (string_list[-2], string_list[-1])
+                           'Locale list separator: end') % (string_list[-2], string_list[-1])
         for index in reversed(list(range(1, len(string_list) - 2))):
             merged = translate('OpenLP.core.lib', '%s, %s',
-                'Locale list separator: middle') % (string_list[index], merged)
+                               'Locale list separator: middle') % (string_list[index], merged)
         return translate('OpenLP.core.lib', '%s, %s', 'Locale list separator: start') % (string_list[0], merged)
 
 
-from .registry import Registry
+from .registry import Registry, RegistryMixin, register
 from .screen import ScreenList
 from .listwidgetwithdnd import ListWidgetWithDnD
 from .treewidgetwithdnd import TreeWidgetWithDnD

=== modified file 'openlp/core/lib/imagemanager.py'
--- openlp/core/lib/imagemanager.py	2013-08-31 18:17:38 +0000
+++ openlp/core/lib/imagemanager.py	2013-11-08 17:01:40 +0000
@@ -39,7 +39,8 @@
 
 from PyQt4 import QtCore
 
-from openlp.core.lib import Registry, ScreenList, resize_image, image_to_byte
+from openlp.core.common import trace
+from openlp.core.lib import Registry, ScreenList, resize_image, image_to_byte, register
 
 log = logging.getLogger(__name__)
 
@@ -172,19 +173,18 @@
         if (image.priority, image.secondary_priority, image) in self.queue:
             self.queue.remove((image.priority, image.secondary_priority, image))
 
-
+@trace
 class ImageManager(QtCore.QObject):
     """
     Image Manager handles the conversion and sizing of images.
     """
     log.info('Image Manager loaded')
-
+    @register
     def __init__(self):
         """
         Constructor for the image manager.
         """
         super(ImageManager, self).__init__()
-        Registry().register('image_manager', self)
         current_screen = ScreenList().current
         self.width = current_screen['size'].width()
         self.height = current_screen['size'].height()
@@ -198,7 +198,6 @@
         """
         Screen has changed size so rebuild the cache to new size.
         """
-        log.debug('update_display')
         current_screen = ScreenList().current
         self.width = current_screen['size'].width()
         self.height = current_screen['size'].height()
@@ -210,7 +209,6 @@
         """
         Border has changed so update all the images affected.
         """
-        log.debug('update_images_border')
         # Mark the images as dirty for a rebuild by setting the image and byte stream to None.
         for image in list(self._cache.values()):
             if image.source == source:
@@ -221,7 +219,6 @@
         """
         Border has changed so update the image affected.
         """
-        log.debug('update_image_border')
         # Mark the image as dirty for a rebuild by setting the image and byte stream to None.
         image = self._cache[(path, source)]
         if image.source == source:
@@ -302,7 +299,6 @@
         """
         Controls the processing called from a ``QtCore.QThread``.
         """
-        log.debug('_process - started')
         while not self._conversion_queue.empty() and not self.stop_manager:
             self._process_cache()
         log.debug('_process - ended')
@@ -311,7 +307,6 @@
         """
         Actually does the work.
         """
-        log.debug('_processCache')
         image = self._conversion_queue.get()[2]
         # Generate the QImage for the image.
         if image.image is None:

=== modified file 'openlp/core/lib/pluginmanager.py'
--- openlp/core/lib/pluginmanager.py	2013-10-13 13:51:13 +0000
+++ openlp/core/lib/pluginmanager.py	2013-11-08 17:01:40 +0000
@@ -34,7 +34,7 @@
 import logging
 import imp
 
-from openlp.core.lib import Plugin, PluginStatus, Registry
+from openlp.core.lib import Plugin, PluginStatus, Registry, register
 from openlp.core.common import AppLocation
 
 log = logging.getLogger(__name__)
@@ -47,13 +47,13 @@
     """
     log.info('Plugin manager loaded')
 
+    @register
     def __init__(self):
         """
         The constructor for the plugin manager. Passes the controllers on to
         the plugins for them to interact with via their ServiceItems.
         """
         log.info('Plugin manager Initialising')
-        Registry().register('plugin_manager', self)
         Registry().register_function('bootstrap_initialise', self.bootstrap_initialise)
         self.base_path = os.path.abspath(AppLocation.get_directory(AppLocation.PluginsDir))
         log.debug('Base path %s ', self.base_path)

=== modified file 'openlp/core/lib/registry.py'
--- openlp/core/lib/registry.py	2013-08-31 18:17:38 +0000
+++ openlp/core/lib/registry.py	2013-11-08 17:01:40 +0000
@@ -31,11 +31,74 @@
 """
 import logging
 import sys
+import os
+
+from openlp.core.common import ErrorMixin, de_hump
 
 log = logging.getLogger(__name__)
 
 
-class Registry(object):
+def register(func):
+    """
+    Code to register the class in the registry.
+    """
+    def wrapper(*args, **kwargs):
+        Registry().register(de_hump(args[0].__class__.__name__), args[0])
+        ret = func(*args, **kwargs)
+        return ret
+    return wrapper
+
+
+class RegistryMixin(ErrorMixin):
+    """
+    This adds registry components to classes to use at run time.
+    """
+    def _get_main_window(self):
+        """
+        Adds the main window to the class dynamically
+        """
+        if not hasattr(self, '_main_window'):
+            self._main_window = Registry().get('main_window')
+        return self._main_window
+
+    main_window = property(_get_main_window)
+
+    def _get_service_manager(self):
+        """
+        Adds the plugin manager to the class dynamically
+        """
+        if not hasattr(self, '_service_manager'):
+            self._service_manager = Registry().get('service_manager')
+        return self._service_manager
+
+    service_manager = property(_get_service_manager)
+
+    def _get_plugin_manager(self):
+        """
+        Adds the plugin manager to the class dynamically
+        """
+        if not hasattr(self, '_plugin_manager'):
+            self._plugin_manager = Registry().get('plugin_manager')
+        return self._plugin_manager
+
+    plugin_manager = property(_get_plugin_manager)
+
+    def _get_application(self):
+        """
+        Adds the openlp to the class dynamically.
+        Windows needs to access the application in a dynamic manner.
+        """
+        if os.name == 'nt':
+            return Registry().get('application')
+        else:
+            if not hasattr(self, '_application'):
+                self._application = Registry().get('application')
+            return self._application
+
+    application = property(_get_application)
+
+
+class Registry(ErrorMixin):
     """
     This is the Component Registry.  It is a singleton object and is used to provide a look up service for common
     objects.
@@ -76,7 +139,7 @@
         if key in self.service_list:
             return self.service_list[key]
         else:
-            log.error('Service %s not found in list' % key)
+            self.log_error('Service %s not found in list' % key)
             raise KeyError('Service %s not found in list' % key)
 
     def register(self, key, reference):
@@ -90,7 +153,7 @@
             The service address to be saved.
         """
         if key in self.service_list:
-            log.error('Duplicate service exception %s' % key)
+            self.log_error('Duplicate service exception %s' % key)
             raise KeyError('Duplicate service exception %s' % key)
         else:
             self.service_list[key] = reference
@@ -134,7 +197,7 @@
             The function to be called when the event happens.
         """
         if self.running_under_test is False:
-            log.error('Invalid Method call for key %s' % event)
+            self.log_error('Invalid Method call for key %s' % event)
             raise KeyError('Invalid Method call for key %s' % event)
         if event in self.functions_list:
             self.functions_list[event].remove(function)
@@ -161,7 +224,8 @@
                         results.append(result)
                 except TypeError:
                     # Who has called me can help in debugging
-                    import inspect
-                    log.debug(inspect.currentframe().f_back.f_locals)
+                    self.trace_error_handler()
                     log.exception('Exception for function %s', function)
+        else:
+            self.log_error("Event %s not found" % event)
         return results

=== modified file 'openlp/core/lib/renderer.py'
--- openlp/core/lib/renderer.py	2013-10-13 20:36:42 +0000
+++ openlp/core/lib/renderer.py	2013-11-08 17:01:40 +0000
@@ -33,7 +33,7 @@
 
 from openlp.core.common import Settings
 from openlp.core.lib import FormattingTags, ImageSource, ItemCapabilities, Registry, ScreenList, \
-    ServiceItem, expand_tags, build_lyrics_format_css, build_lyrics_outline_css
+    ServiceItem, expand_tags, build_lyrics_format_css, build_lyrics_outline_css, register
 from openlp.core.common import ThemeLevel
 from openlp.core.ui import MainDisplay
 
@@ -57,13 +57,13 @@
     """
     log.info('Renderer Loaded')
 
+    @register
     def __init__(self):
         """
         Initialise the renderer.
         """
         log.debug('Initialisation started')
         self.screens = ScreenList()
-        Registry().register('renderer', self)
         self.theme_level = ThemeLevel.Global
         self.global_theme_name = ''
         self.service_theme_name = ''

=== modified file 'openlp/core/lib/theme.py'
--- openlp/core/lib/theme.py	2013-10-18 18:10:47 +0000
+++ openlp/core/lib/theme.py	2013-11-08 17:01:40 +0000
@@ -36,7 +36,7 @@
 
 from xml.dom.minidom import Document
 from lxml import etree, objectify
-from openlp.core.common import AppLocation
+from openlp.core.common import AppLocation, de_hump
 
 from openlp.core.lib import str_to_bool, ScreenList, get_text_file_string
 
@@ -157,9 +157,6 @@
     """
     A class to encapsulate the Theme XML.
     """
-    FIRST_CAMEL_REGEX = re.compile('(.)([A-Z][a-z]+)')
-    SECOND_CAMEL_REGEX = re.compile('([a-z0-9])([A-Z])')
-
     def __init__(self):
         """
         Initialise the theme object.
@@ -532,7 +529,7 @@
         reject, master, element, value = self._translate_tags(master, element, value)
         if reject:
             return
-        field = self._de_hump(element)
+        field = de_hump(element)
         tag = master + '_' + field
         if field in BOOLEAN_LIST:
             setattr(self, tag, str_to_bool(value))
@@ -557,13 +554,6 @@
                 theme_strings.append('%30s: %s' % (key, getattr(self, key)))
         return '\n'.join(theme_strings)
 
-    def _de_hump(self, name):
-        """
-        Change Camel Case string to python string
-        """
-        sub_name = ThemeXML.FIRST_CAMEL_REGEX.sub(r'\1_\2', name)
-        return ThemeXML.SECOND_CAMEL_REGEX.sub(r'\1_\2', sub_name).lower()
-
     def _build_xml_from_attrs(self):
         """
         Build the XML from the varables in the object

=== modified file 'openlp/core/ui/mainwindow.py'
--- openlp/core/ui/mainwindow.py	2013-10-13 21:07:28 +0000
+++ openlp/core/ui/mainwindow.py	2013-11-08 17:01:40 +0000
@@ -42,12 +42,12 @@
 from PyQt4 import QtCore, QtGui
 
 from openlp.core.lib import Renderer, OpenLPDockWidget, PluginManager, ImageManager, PluginStatus, Registry, \
-    ScreenList, build_icon
+    RegistryMixin, ScreenList, build_icon, register
 from openlp.core.lib.ui import UiStrings, create_action
 from openlp.core.ui import AboutForm, SettingsForm, ServiceManager, ThemeManager, SlideController, PluginForm, \
     MediaDockManager, ShortcutListForm, FormattingTagForm
 
-from openlp.core.common import AppLocation, Settings, check_directory_exists, translate
+from openlp.core.common import AppLocation, Settings, check_directory_exists, translate, trace
 from openlp.core.ui.media import MediaController
 from openlp.core.utils import LanguageManager, add_actions, get_application_version
 from openlp.core.utils.actions import ActionList, CategoryOrder
@@ -96,15 +96,15 @@
         # Set up the main container, which contains all the other form widgets.
         self.main_content = QtGui.QWidget(main_window)
         self.main_content.setObjectName('main_content')
-        self.main_contentLayout = QtGui.QHBoxLayout(self.main_content)
-        self.main_contentLayout.setSpacing(0)
-        self.main_contentLayout.setMargin(0)
-        self.main_contentLayout.setObjectName('main_contentLayout')
+        self.main_content_layout = QtGui.QHBoxLayout(self.main_content)
+        self.main_content_layout.setSpacing(0)
+        self.main_content_layout.setMargin(0)
+        self.main_content_layout.setObjectName('main_contentLayout')
         main_window.setCentralWidget(self.main_content)
         self.control_splitter = QtGui.QSplitter(self.main_content)
         self.control_splitter.setOrientation(QtCore.Qt.Horizontal)
         self.control_splitter.setObjectName('control_splitter')
-        self.main_contentLayout.addWidget(self.control_splitter)
+        self.main_content_layout.addWidget(self.control_splitter)
         # Create slide controllers
         self.preview_controller = SlideController(self)
         self.live_controller = SlideController(self, True)
@@ -127,8 +127,8 @@
         # View Menu
         self.view_menu = QtGui.QMenu(self.menu_bar)
         self.view_menu.setObjectName('viewMenu')
-        self.view_modeMenu = QtGui.QMenu(self.view_menu)
-        self.view_modeMenu.setObjectName('viewModeMenu')
+        self.view_mode_menu = QtGui.QMenu(self.view_menu)
+        self.view_mode_menu.setObjectName('viewModeMenu')
         # Tools Menu
         self.tools_menu = QtGui.QMenu(self.menu_bar)
         self.tools_menu.setObjectName('tools_menu')
@@ -155,7 +155,7 @@
         self.status_bar.addPermanentWidget(self.default_theme_label)
         # Create the MediaManager
         self.media_manager_dock = OpenLPDockWidget(main_window, 'media_manager_dock',
-            ':/system/system_mediamanager.png')
+                                                   ':/system/system_mediamanager.png')
         self.media_manager_dock.setStyleSheet(MEDIA_MANAGER_STYLE)
         # Create the media toolbox
         self.media_tool_box = QtGui.QToolBox(self.media_manager_dock)
@@ -164,7 +164,7 @@
         main_window.addDockWidget(QtCore.Qt.LeftDockWidgetArea, self.media_manager_dock)
         # Create the service manager
         self.service_manager_dock = OpenLPDockWidget(main_window, 'service_manager_dock',
-            ':/system/system_servicemanager.png')
+                                                     ':/system/system_servicemanager.png')
         self.service_manager_contents = ServiceManager(self.service_manager_dock)
         self.service_manager_dock.setWidget(self.service_manager_contents)
         main_window.addDockWidget(QtCore.Qt.RightDockWidgetArea, self.service_manager_dock)
@@ -243,17 +243,17 @@
             category=UiStrings().View,
             triggers=self.set_lock_panel)
         action_list.add_category(UiStrings().ViewMode, CategoryOrder.standard_menu)
-        self.mode_default_Item = create_action(
+        self.mode_default_item = create_action(
             main_window, 'modeDefaultItem', checked=False, category=UiStrings().ViewMode, can_shortcuts=True)
         self.mode_setup_item = create_action(
             main_window, 'modeSetupItem', checked=False, category=UiStrings().ViewMode, can_shortcuts=True)
         self.mode_live_item = create_action(
             main_window, 'modeLiveItem', checked=True, category=UiStrings().ViewMode, can_shortcuts=True)
         self.mode_group = QtGui.QActionGroup(main_window)
-        self.mode_group.addAction(self.mode_default_Item)
+        self.mode_group.addAction(self.mode_default_item)
         self.mode_group.addAction(self.mode_setup_item)
         self.mode_group.addAction(self.mode_live_item)
-        self.mode_default_Item.setChecked(True)
+        self.mode_default_item.setChecked(True)
         action_list.add_category(UiStrings().Tools, CategoryOrder.standard_menu)
         self.tools_add_tool_item = create_action(main_window,
             'toolsAddToolItem', icon=':/tools/tools_add.png', category=UiStrings().Tools, can_shortcuts=True)
@@ -323,8 +323,8 @@
             self.file_save_item, self.file_save_as_item, self.recent_files_menu.menuAction(), None,
             self.file_import_menu.menuAction(), self.file_export_menu.menuAction(), None, self.print_service_order_item,
             self.file_exit_item))
-        add_actions(self.view_modeMenu, (self.mode_default_Item, self.mode_setup_item, self.mode_live_item))
-        add_actions(self.view_menu, (self.view_modeMenu.menuAction(), None, self.view_media_manager_item,
+        add_actions(self.view_mode_menu, (self.mode_default_item, self.mode_setup_item, self.mode_live_item))
+        add_actions(self.view_menu, (self.view_mode_menu.menuAction(), None, self.view_media_manager_item,
             self.view_service_manager_item, self.view_theme_manager_item, None, self.view_preview_panel,
             self.view_live_panel, None, self.lock_panel))
         # i18n add Language Actions
@@ -372,7 +372,7 @@
         self.file_export_menu.setTitle(translate('OpenLP.MainWindow', '&Export'))
         self.recent_files_menu.setTitle(translate('OpenLP.MainWindow', '&Recent Files'))
         self.view_menu.setTitle(translate('OpenLP.MainWindow', '&View'))
-        self.view_modeMenu.setTitle(translate('OpenLP.MainWindow', 'M&ode'))
+        self.view_mode_menu.setTitle(translate('OpenLP.MainWindow', 'M&ode'))
         self.tools_menu.setTitle(translate('OpenLP.MainWindow', '&Tools'))
         self.settings_menu.setTitle(translate('OpenLP.MainWindow', '&Settings'))
         self.settings_language_menu.setTitle(translate('OpenLP.MainWindow', '&Language'))
@@ -459,26 +459,25 @@
         self.update_theme_images.setText(translate('OpenLP.MainWindow', 'Update Theme Images'))
         self.update_theme_images.setStatusTip(translate('OpenLP.MainWindow',
             'Update the preview images for all themes.'))
-        self.mode_default_Item.setText(translate('OpenLP.MainWindow', '&Default'))
-        self.mode_default_Item.setStatusTip(translate('OpenLP.MainWindow', 'Set the view mode back to the default.'))
+        self.mode_default_item.setText(translate('OpenLP.MainWindow', '&Default'))
+        self.mode_default_item.setStatusTip(translate('OpenLP.MainWindow', 'Set the view mode back to the default.'))
         self.mode_setup_item.setText(translate('OpenLP.MainWindow', '&Setup'))
         self.mode_setup_item.setStatusTip(translate('OpenLP.MainWindow', 'Set the view mode to Setup.'))
         self.mode_live_item.setText(translate('OpenLP.MainWindow', '&Live'))
         self.mode_live_item.setStatusTip(translate('OpenLP.MainWindow', 'Set the view mode to Live.'))
 
 
-class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
+@trace
+class MainWindow(QtGui.QMainWindow, Ui_MainWindow, RegistryMixin):
     """
     The main window.
     """
-    log.info('MainWindow loaded')
-
+    @register
     def __init__(self):
         """
         This constructor sets up the interface, the various managers, and the plugins.
         """
         super(MainWindow, self).__init__()
-        Registry().register('main_window', self)
         self.clipboard = self.application.clipboard()
         self.arguments = self.application.args
         # Set up settings sections for the main application (not for use by plugins).
@@ -504,8 +503,8 @@
         self.formatting_tag_form = FormattingTagForm(self)
         self.shortcut_form = ShortcutListForm(self)
         # Set up the path with plugins
-        self.plugin_manager = PluginManager()
-        self.image_manager = ImageManager()
+        PluginManager()
+        ImageManager()
         # Set up the interface
         self.setupUi(self)
         # Define the media Dock Manager
@@ -532,7 +531,7 @@
         self.settings_export_item.triggered.connect(self.on_settings_export_item_clicked)
         # i18n set signals for languages
         self.language_group.triggered.connect(LanguageManager.set_language)
-        self.mode_default_Item.triggered.connect(self.on_mode_default_item_clicked)
+        self.mode_default_item.triggered.connect(self.on_mode_default_item_clicked)
         self.mode_setup_item.triggered.connect(self.on_mode_setup_item_clicked)
         self.mode_live_item.triggered.connect(self.on_mode_live_item_clicked)
         # Media Manager
@@ -551,7 +550,6 @@
         """
         Called on start up to restore the last active media plugin.
         """
-        log.info('Load data from Settings')
         if Settings().value('advanced/save current plugin'):
             saved_plugin_id = Settings().value('advanced/current media plugin')
             if saved_plugin_id != -1:
@@ -581,9 +579,9 @@
         Notifies the user that a newer version of OpenLP is available.
         Triggered by delay thread and cannot display popup.
         """
-        log.debug('version_notice')
         version_text = translate('OpenLP.MainWindow', 'Version %s of OpenLP is now available for download (you are '
-            'currently running version %s). \n\nYou can download the latest version from http://openlp.org/.')
+                                 'currently running version %s). \n\nYou can download the latest version from '
+                                 'http://openlp.org/.')
         self.version_text = version_text % (version, get_application_version()['full'])
 
     def show(self):
@@ -607,12 +605,12 @@
         self.timer_version_id = self.startTimer(1000)
         view_mode = Settings().value('%s/view mode' % self.general_settings_section)
         if view_mode == 'default':
-            self.mode_default_Item.setChecked(True)
+            self.mode_default_item.setChecked(True)
         elif view_mode == 'setup':
-            self.setViewMode(True, True, False, True, False)
+            self.set_view_mode(True, True, False, True, False)
             self.mode_setup_item.setChecked(True)
         elif view_mode == 'live':
-            self.setViewMode(False, True, False, False, True)
+            self.set_view_mode(False, True, False, False, True)
             self.mode_live_item.setChecked(True)
 
     def app_startup(self):
@@ -648,9 +646,9 @@
         answer = QtGui.QMessageBox.warning(self,
             translate('OpenLP.MainWindow', 'Re-run First Time Wizard?'),
             translate('OpenLP.MainWindow', 'Are you sure you want to re-run the First Time Wizard?\n\n'
-                'Re-running this wizard may make changes to your current '
-                'OpenLP configuration and possibly add songs to your '
-                'existing songs list and change your default theme.'),
+                      'Re-running this wizard may make changes to your current '
+                      'OpenLP configuration and possibly add songs to your '
+                      'existing songs list and change your default theme.'),
             QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Yes | QtGui.QMessageBox.No),
                 QtGui.QMessageBox.No)
         if answer == QtGui.QMessageBox.No:
@@ -663,15 +661,15 @@
         self.application.set_busy_cursor()
         self.first_time()
         for plugin in self.plugin_manager.plugins:
-            self.activePlugin = plugin
-            oldStatus = self.activePlugin.status
-            self.activePlugin.set_status()
-            if oldStatus != self.activePlugin.status:
-                if self.activePlugin.status == PluginStatus.Active:
-                    self.activePlugin.toggle_status(PluginStatus.Active)
-                    self.activePlugin.app_startup()
+            self.active_plugin = plugin
+            old_status = self.active_plugin.status
+            self.active_plugin.set_status()
+            if old_status != self.active_plugin.status:
+                if self.active_plugin.status == PluginStatus.Active:
+                    self.active_plugin.toggle_status(PluginStatus.Active)
+                    self.active_plugin.app_startup()
                 else:
-                    self.activePlugin.toggle_status(PluginStatus.Inactive)
+                    self.active_plugin.toggle_status(PluginStatus.Inactive)
         # Set global theme and
         Registry().execute('theme_update_global', self.theme_manager_contents.global_theme)
         self.theme_manager_contents.load_first_time_themes()
@@ -967,21 +965,21 @@
         """
         Put OpenLP into "Default" view mode.
         """
-        self.setViewMode(True, True, True, True, True, 'default')
+        self.set_view_mode(True, True, True, True, True, 'default')
 
     def on_mode_setup_item_clicked(self):
         """
         Put OpenLP into "Setup" view mode.
         """
-        self.setViewMode(True, True, False, True, False, 'setup')
+        self.set_view_mode(True, True, False, True, False, 'setup')
 
     def on_mode_live_item_clicked(self):
         """
         Put OpenLP into "Live" view mode.
         """
-        self.setViewMode(False, True, False, False, True, 'live')
+        self.set_view_mode(False, True, False, False, True, 'live')
 
-    def setViewMode(self, media=True, service=True, theme=True, preview=True, live=True, mode=''):
+    def set_view_mode(self, media=True, service=True, theme=True, preview=True, live=True, mode=''):
         """
         Set OpenLP to a different view mode.
         """
@@ -998,7 +996,6 @@
         """
         The screen has changed so we have to update components such as the renderer.
         """
-        log.debug('screen_changed')
         self.application.set_busy_cursor()
         self.image_manager.update_display()
         self.renderer.update_display()
@@ -1065,7 +1062,7 @@
             if Settings().value('advanced/save current plugin'):
                 Settings().setValue('advanced/current media plugin', self.media_tool_box.currentIndex())
         # Call the cleanup method to shutdown plugins.
-        log.info('cleanup plugins')
+        log.info('Cleanup plugins')
         self.plugin_manager.finalise_plugins()
         if save_settings:
             # Save settings
@@ -1108,7 +1105,7 @@
         Update the default theme indicator in the status bar
         """
         self.default_theme_label.setText(translate('OpenLP.MainWindow', 'Default Theme: %s') %
-            Settings().value('themes/global theme'))
+                                         Settings().value('themes/global theme'))
 
     def toggle_media_manager(self):
         """
@@ -1182,7 +1179,6 @@
         """
         Load the main window settings.
         """
-        log.debug('Loading QSettings')
         settings = Settings()
         # Remove obsolete entries.
         settings.remove('custom slide')
@@ -1209,7 +1205,6 @@
         # Exit if we just did a settings import.
         if self.settingsImported:
             return
-        log.debug('Saving QSettings')
         settings = Settings()
         settings.beginGroup(self.general_settings_section)
         settings.setValue('recent files', self.recent_files)
@@ -1349,7 +1344,7 @@
                 log.info('Copy sucessful')
             except (IOError, os.error, DistutilsFileError) as why:
                 self.application.set_normal_cursor()
-                log.exception('Data copy failed %s' % str(why))
+                log.error('Data copy failed %s' % str(why))
                 QtGui.QMessageBox.critical(self, translate('OpenLP.MainWindow', 'New Data Directory Error'),
                     translate('OpenLP.MainWindow',
                         'OpenLP Data directory copy failed\n\n%s').replace('%s', str(why)),
@@ -1364,17 +1359,3 @@
         if self.new_data_path == AppLocation.get_directory(AppLocation.DataDir):
             settings.remove('advanced/data path')
         self.application.set_normal_cursor()
-
-    def _get_application(self):
-        """
-        Adds the openlp to the class dynamically.
-        Windows needs to access the application in a dynamic manner.
-        """
-        if os.name == 'nt':
-            return Registry().get('application')
-        else:
-            if not hasattr(self, '_application'):
-                self._application = Registry().get('application')
-            return self._application
-
-    application = property(_get_application)

=== modified file 'openlp/core/ui/media/mediacontroller.py'
--- openlp/core/ui/media/mediacontroller.py	2013-10-13 20:36:42 +0000
+++ openlp/core/ui/media/mediacontroller.py	2013-11-08 17:01:40 +0000
@@ -36,7 +36,7 @@
 from PyQt4 import QtCore, QtGui
 
 from openlp.core.common import Settings, UiStrings, translate
-from openlp.core.lib import OpenLPToolbar, Registry
+from openlp.core.lib import OpenLPToolbar, Registry, register
 from openlp.core.lib.ui import critical_error_message_box
 from openlp.core.ui.media import MediaState, MediaInfo, MediaType, get_media_players, set_media_players
 from openlp.core.ui.media.mediaplayer import MediaPlayer
@@ -94,11 +94,11 @@
     current_media_players is an array of player instances keyed on ControllerType.
 
     """
+    @register
     def __init__(self):
         """
         Constructor
         """
-        Registry().register('media_controller', self)
         Registry().register_function('bootstrap_initialise', self.check_available_media_players)
         self.media_players = {}
         self.display_controllers = {}

=== modified file 'openlp/core/ui/pluginform.py'
--- openlp/core/ui/pluginform.py	2013-10-13 21:07:28 +0000
+++ openlp/core/ui/pluginform.py	2013-11-08 17:01:40 +0000
@@ -50,8 +50,8 @@
         Constructor
         """
         super(PluginForm, self).__init__(parent)
-        self.activePlugin = None
-        self.programaticChange = False
+        self.active_plugin = None
+        self.programatic_change = False
         self.setupUi(self)
         self.load()
         self._clearDetails()
@@ -64,9 +64,9 @@
         Load the plugin details into the screen
         """
         self.pluginListWidget.clear()
-        self.programaticChange = True
+        self.programatic_change = True
         self._clearDetails()
-        self.programaticChange = True
+        self.programatic_change = True
         pluginListWidth = 0
         for plugin in self.plugin_manager.plugins:
             item = QtGui.QListWidgetItem(self.pluginListWidget)
@@ -103,16 +103,16 @@
         """
         Set the details of the currently selected plugin
         """
-        log.debug('PluginStatus: %s', str(self.activePlugin.status))
-        self.versionNumberLabel.setText(self.activePlugin.version)
-        self.aboutTextBrowser.setHtml(self.activePlugin.about())
-        self.programaticChange = True
+        log.debug('PluginStatus: %s', str(self.active_plugin.status))
+        self.versionNumberLabel.setText(self.active_plugin.version)
+        self.aboutTextBrowser.setHtml(self.active_plugin.about())
+        self.programatic_change = True
         status = PluginStatus.Active
-        if self.activePlugin.status == PluginStatus.Active:
+        if self.active_plugin.status == PluginStatus.Active:
             status = PluginStatus.Inactive
         self.statusComboBox.setCurrentIndex(status)
         self.statusComboBox.setEnabled(True)
-        self.programaticChange = False
+        self.programatic_change = False
 
     def onPluginListWidgetSelectionChanged(self):
         """
@@ -122,13 +122,13 @@
             self._clearDetails()
             return
         plugin_name_singular = self.pluginListWidget.currentItem().text().split('(')[0][:-1]
-        self.activePlugin = None
+        self.active_plugin = None
         for plugin in self.plugin_manager.plugins:
             if plugin.status != PluginStatus.Disabled:
                 if plugin.name_strings['singular'] == plugin_name_singular:
-                    self.activePlugin = plugin
+                    self.active_plugin = plugin
                     break
-        if self.activePlugin:
+        if self.active_plugin:
             self._setDetails()
         else:
             self._clearDetails()
@@ -137,24 +137,24 @@
         """
         If the status of a plugin is altered, apply the change
         """
-        if self.programaticChange or status == PluginStatus.Disabled:
+        if self.programatic_change or status == PluginStatus.Disabled:
             return
         if status == PluginStatus.Inactive:
             self.application.set_busy_cursor()
-            self.activePlugin.toggle_status(PluginStatus.Active)
+            self.active_plugin.toggle_status(PluginStatus.Active)
             self.application.set_normal_cursor()
-            self.activePlugin.app_startup()
+            self.active_plugin.app_startup()
         else:
-            self.activePlugin.toggle_status(PluginStatus.Inactive)
+            self.active_plugin.toggle_status(PluginStatus.Inactive)
         status_text = translate('OpenLP.PluginForm', '%s (Inactive)')
-        if self.activePlugin.status == PluginStatus.Active:
+        if self.active_plugin.status == PluginStatus.Active:
             status_text = translate('OpenLP.PluginForm', '%s (Active)')
-        elif self.activePlugin.status == PluginStatus.Inactive:
+        elif self.active_plugin.status == PluginStatus.Inactive:
             status_text = translate('OpenLP.PluginForm', '%s (Inactive)')
-        elif self.activePlugin.status == PluginStatus.Disabled:
+        elif self.active_plugin.status == PluginStatus.Disabled:
             status_text = translate('OpenLP.PluginForm', '%s (Disabled)')
         self.pluginListWidget.currentItem().setText(
-            status_text % self.activePlugin.name_strings['singular'])
+            status_text % self.active_plugin.name_strings['singular'])
 
     def _get_plugin_manager(self):
         """

=== modified file 'openlp/core/ui/servicemanager.py'
--- openlp/core/ui/servicemanager.py	2013-10-13 21:07:28 +0000
+++ openlp/core/ui/servicemanager.py	2013-11-08 17:01:40 +0000
@@ -43,7 +43,7 @@
 from PyQt4 import QtCore, QtGui
 
 from openlp.core.common import AppLocation, Settings, ThemeLevel, check_directory_exists, UiStrings, translate
-from openlp.core.lib import OpenLPToolbar, ServiceItem, ItemCapabilities, PluginStatus, Registry, build_icon
+from openlp.core.lib import OpenLPToolbar, ServiceItem, ItemCapabilities, PluginStatus, Registry, build_icon, register
 from openlp.core.lib.ui import critical_error_message_box, create_widget_action, find_and_set_in_combo_box
 from openlp.core.ui import ServiceNoteForm, ServiceItemEditForm, StartTimeForm
 from openlp.core.ui.printserviceform import PrintServiceForm
@@ -289,6 +289,7 @@
     can then be zipped up with all the resources used into one OSZ or oszl file for use on any OpenLP v2 installation.
     Also handles the UI tasks of moving things up and down etc.
     """
+    @register
     def __init__(self, parent=None):
         """
         Sets up the service manager, toolbars, list view, et al.
@@ -296,7 +297,6 @@
         super(ServiceManager, self).__init__(parent)
         self.active = build_icon(':/media/auto-start_active.png')
         self.inactive = build_icon(':/media/auto-start_inactive.png')
-        Registry().register('service_manager', self)
         self.service_items = []
         self.suffixes = []
         self.drop_position = 0
@@ -730,7 +730,7 @@
                 Settings().setValue('servicemanager/last file', file_name)
             else:
                 critical_error_message_box(message=translate('OpenLP.ServiceManager', 'File is not a valid service.'))
-                log.exception('File contains no service data')
+                log.error('File contains no service data')
         except (IOError, NameError, zipfile.BadZipfile):
             log.exception('Problem loading service file %s' % file_name)
             critical_error_message_box(message=translate('OpenLP.ServiceManager',

=== modified file 'openlp/core/ui/settingsform.py'
--- openlp/core/ui/settingsform.py	2013-08-31 18:17:38 +0000
+++ openlp/core/ui/settingsform.py	2013-11-08 17:01:40 +0000
@@ -33,7 +33,7 @@
 
 from PyQt4 import QtGui
 
-from openlp.core.lib import PluginStatus, Registry, build_icon
+from openlp.core.lib import PluginStatus, Registry, RegistryMixin, build_icon, register
 from openlp.core.ui import AdvancedTab, GeneralTab, ThemesTab
 from openlp.core.ui.media import PlayerTab
 from .settingsdialog import Ui_SettingsDialog
@@ -41,15 +41,15 @@
 log = logging.getLogger(__name__)
 
 
-class SettingsForm(QtGui.QDialog, Ui_SettingsDialog):
+class SettingsForm(QtGui.QDialog, Ui_SettingsDialog, RegistryMixin):
     """
     Provide the form to manipulate the settings for OpenLP
     """
+    @register
     def __init__(self, parent=None):
         """
         Initialise the settings form
         """
-        Registry().register('settings_form', self)
         Registry().register_function('bootstrap_post_set_up', self.post_set_up)
         super(SettingsForm, self).__init__(parent)
         self.processes = []
@@ -153,32 +153,4 @@
         if not function in self.processes:
             self.processes.append(function)
 
-    def _get_main_window(self):
-        """
-        Adds the main window to the class dynamically
-        """
-        if not hasattr(self, '_main_window'):
-            self._main_window = Registry().get('main_window')
-        return self._main_window
-
-    main_window = property(_get_main_window)
-
-    def _get_service_manager(self):
-        """
-        Adds the plugin manager to the class dynamically
-        """
-        if not hasattr(self, '_service_manager'):
-            self._service_manager = Registry().get('service_manager')
-        return self._service_manager
-
-    service_manager = property(_get_service_manager)
-
-    def _get_plugin_manager(self):
-        """
-        Adds the plugin manager to the class dynamically
-        """
-        if not hasattr(self, '_plugin_manager'):
-            self._plugin_manager = Registry().get('plugin_manager')
-        return self._plugin_manager
-
-    plugin_manager = property(_get_plugin_manager)
+

=== modified file 'openlp/core/ui/slidecontroller.py'
--- openlp/core/ui/slidecontroller.py	2013-10-13 21:07:28 +0000
+++ openlp/core/ui/slidecontroller.py	2013-11-08 17:01:40 +0000
@@ -48,7 +48,7 @@
 log = logging.getLogger(__name__)
 
 # Threshold which has to be trespassed to toggle.
-HIDE_MENU_THRESHOLD  = 27
+HIDE_MENU_THRESHOLD = 27
 AUDIO_TIME_LABEL_STYLESHEET = 'background-color: palette(background); ' \
     'border-top-color: palette(shadow); ' \
     'border-left-color: palette(shadow); ' \
@@ -123,7 +123,7 @@
         self.slide_limits = None
         self.update_slide_limits()
         self.panel = QtGui.QWidget(parent.control_splitter)
-        self.slideList = {}
+        self.slide_list = {}
         self.slide_count = 0
         self.slide_image = None
         # Layout for holding panel
@@ -331,9 +331,9 @@
         self.grid.addLayout(self.slide_layout, 0, 0, 1, 1)
         if self.is_live:
             self.current_shortcut = ''
-            self.shortcutTimer = QtCore.QTimer()
-            self.shortcutTimer.setObjectName('shortcutTimer')
-            self.shortcutTimer.setSingleShot(True)
+            self.shortcut_timer = QtCore.QTimer()
+            self.shortcut_timer.setObjectName('shortcutTimer')
+            self.shortcut_timer.setSingleShot(True)
             shortcuts = [
                 {'key': 'V', 'configurable': True, 'text': translate('OpenLP.SlideController', 'Go to "Verse"')},
                 {'key': 'C', 'configurable': True, 'text': translate('OpenLP.SlideController', 'Go to "Chorus"')},
@@ -345,13 +345,11 @@
                 {'key': 'O', 'configurable': True, 'text': translate('OpenLP.SlideController', 'Go to "Other"')}
             ]
             shortcuts.extend([{'key': str(number)} for number in range(10)])
-            self.controller.addActions([create_action(self,
-                'shortcutAction_%s' % s['key'], text=s.get('text'),
-                can_shortcuts=True,
-                context=QtCore.Qt.WidgetWithChildrenShortcut,
-                category=self.category if s.get('configurable') else None,
-                triggers=self._slide_shortcut_activated) for s in shortcuts])
-            self.shortcutTimer.timeout.connect(self._slide_shortcut_activated)
+            self.controller.addActions([create_action(self, 'shortcutAction_%s' % s['key'], text=s.get('text'),
+                                        can_shortcuts=True, context=QtCore.Qt.WidgetWithChildrenShortcut,
+                                        category=self.category if s.get('configurable') else None,
+                                        triggers=self._slide_shortcut_activated) for s in shortcuts])
+            self.shortcut_timer.timeout.connect(self._slide_shortcut_activated)
         # Signals
         self.preview_widget.clicked.connect(self.on_slide_selected)
         if self.is_live:
@@ -374,11 +372,11 @@
         Registry().register_function('slidecontroller_%s_unblank' % self.type_prefix, self.on_slide_unblank)
         Registry().register_function('slidecontroller_update_slide_limits', self.update_slide_limits)
         QtCore.QObject.connect(self, QtCore.SIGNAL('slidecontroller_%s_set' % self.type_prefix),
-            self.on_slide_selected_index)
+                               self.on_slide_selected_index)
         QtCore.QObject.connect(self, QtCore.SIGNAL('slidecontroller_%s_next' % self.type_prefix),
-            self.on_slide_selected_next)
+                               self.on_slide_selected_next)
         QtCore.QObject.connect(self, QtCore.SIGNAL('slidecontroller_%s_previous' % self.type_prefix),
-            self.on_slide_selected_previous)
+                               self.on_slide_selected_previous)
 
     def _slide_shortcut_activated(self):
         """
@@ -420,23 +418,22 @@
             self.current_shortcut += verse_type
         elif verse_type:
             self.current_shortcut = verse_type
-        keys = list(self.slideList.keys())
-        matches = [match for match in keys
-            if match.startswith(self.current_shortcut)]
+        keys = list(self.slide_list.keys())
+        matches = [match for match in keys if match.startswith(self.current_shortcut)]
         if len(matches) == 1:
-            self.shortcutTimer.stop()
+            self.shortcut_timer.stop()
             self.current_shortcut = ''
-            self.preview_widget.change_slide(self.slideList[matches[0]])
+            self.preview_widget.change_slide(self.slide_list[matches[0]])
             self.slide_selected()
         elif sender_name != 'shortcutTimer':
             # Start the time as we did not have any match.
-            self.shortcutTimer.start(350)
+            self.shortcut_timer.start(350)
         else:
             # The timer timed out.
             if self.current_shortcut in keys:
                 # We had more than one match for example "V1" and "V10", but
                 # "V1" was the slide we wanted to go.
-                self.preview_widget.change_slide(self.slideList[self.current_shortcut])
+                self.preview_widget.change_slide(self.slide_list[self.current_shortcut])
                 self.slide_selected()
            # Reset the shortcut.
             self.current_shortcut = ''
@@ -445,18 +442,18 @@
         """
         Set the live hotkeys
         """
-        self.previousService = create_action(parent, 'previousService',
-            text=translate('OpenLP.SlideController', 'Previous Service'),
-            can_shortcuts=True, context=QtCore.Qt.WidgetWithChildrenShortcut, category=self.category,
-            triggers=self.service_previous)
-        self.nextService = create_action(parent, 'nextService',
-            text=translate('OpenLP.SlideController', 'Next Service'),
-            can_shortcuts=True, context=QtCore.Qt.WidgetWithChildrenShortcut, category=self.category,
-            triggers=self.service_next)
-        self.escapeItem = create_action(parent, 'escapeItem',
-            text=translate('OpenLP.SlideController', 'Escape Item'),
-            can_shortcuts=True, context=QtCore.Qt.WidgetWithChildrenShortcut, category=self.category,
-            triggers=self.live_escape)
+        self.previous_service = create_action(parent, 'previousService',
+                                              text=translate('OpenLP.SlideController', 'Previous Service'),
+                                              can_shortcuts=True, context=QtCore.Qt.WidgetWithChildrenShortcut,
+                                              category=self.category, triggers=self.service_previous)
+        self.next_service = create_action(parent, 'nextService',
+                                          text=translate('OpenLP.SlideController', 'Next Service'),
+                                          can_shortcuts=True, context=QtCore.Qt.WidgetWithChildrenShortcut,
+                                          category=self.category, triggers=self.service_next)
+        self.escape_item = create_action(parent, 'escapeItem',
+                                         text=translate('OpenLP.SlideController', 'Escape Item'),
+                                         can_shortcuts=True, context=QtCore.Qt.WidgetWithChildrenShortcut,
+                                         category=self.category, triggers=self.live_escape)
 
     def live_escape(self):
         """
@@ -502,10 +499,10 @@
         if self.keypress_queue:
             while len(self.keypress_queue) and not self.keypress_loop:
                 self.keypress_loop = True
-                keypressCommand = self.keypress_queue.popleft()
-                if keypressCommand == ServiceItemAction.Previous:
+                keypress_command = self.keypress_queue.popleft()
+                if keypress_command == ServiceItemAction.Previous:
                     self.service_manager.previous_item()
-                elif keypressCommand == ServiceItemAction.PreviousLastSlide:
+                elif keypress_command == ServiceItemAction.PreviousLastSlide:
                     # Go to the last slide of the previous item
                     self.service_manager.previous_item(last_slide=True)
                 else:
@@ -535,7 +532,7 @@
         self.preview_display.setup()
         service_item = ServiceItem()
         self.preview_display.web_view.setHtml(build_html(service_item, self.preview_display.screen, None, self.is_live,
-            plugins=self.plugin_manager.plugins))
+                                              plugins=self.plugin_manager.plugins))
         self.media_controller.setup_display(self.preview_display, True)
         if self.service_item:
             self.refresh_service_item()
@@ -546,14 +543,13 @@
         """
         widget.addActions([
             self.previous_item, self.nextItem,
-            self.previousService, self.nextService,
-            self.escapeItem])
+            self.previous_service, self.next_service,
+            self.escape_item])
 
     def preview_size_changed(self):
         """
-        Takes care of the SlidePreview's size. Is called when one of the the
-        splitters is moved or when the screen size is changed. Note, that this
-        method is (also) called frequently from the mainwindow *paintEvent*.
+        Takes care of the SlidePreview's size. Is called when one of the the splitters is moved or when the screen
+        size is changed. Note, that this method is (also) called frequently from the mainwindow *paintEvent*.
         """
         if self.ratio < self.preview_frame.width() / self.preview_frame.height():
             # We have to take the height as limit.
@@ -591,7 +587,7 @@
         Some song handler
         """
         request = self.sender().text()
-        slide_no = self.slideList[request]
+        slide_no = self.slide_list[request]
         width = self.main_window.control_splitter.sizes()[self.split]
         self.preview_widget.replace_service_item(self.service_item, width, slide_no)
         self.slide_selected()
@@ -634,7 +630,7 @@
         self.play_slides_loop.setChecked(False)
         self.play_slides_loop.setIcon(build_icon(':/media/media_time.png'))
         if item.is_text():
-            if Settings().value(self.main_window.songs_settings_section + '/display songbar') and self.slideList:
+            if Settings().value(self.main_window.songs_settings_section + '/display songbar') and self.slide_list:
                 self.toolbar.set_widget_visible(['song_menu'], True)
         if item.is_capable(ItemCapabilities.CanLoop) and len(item.get_frames()) > 1:
             self.toolbar.set_widget_visible(self.loop_list)
@@ -734,7 +730,7 @@
             self._reset_blank()
         Registry().execute(
             '%s_start' % service_item.name.lower(), [service_item, self.is_live, self.hide_mode(), slideno])
-        self.slideList = {}
+        self.slide_list = {}
         if self.is_live:
             self.song_menu.menu().clear()
             self.display.audio_player.reset()
@@ -767,16 +763,16 @@
                     verse_def = '%s%s' % (verse_def[0], verse_def[1:])
                     two_line_def = '%s\n%s' % (verse_def[0], verse_def[1:])
                     row = two_line_def
-                    if verse_def not in self.slideList:
-                        self.slideList[verse_def] = framenumber
+                    if verse_def not in self.slide_list:
+                        self.slide_list[verse_def] = framenumber
                         if self.is_live:
                             self.song_menu.menu().addAction(verse_def, self.on_song_bar_handler)
                 else:
                     row += 1
-                    self.slideList[str(row)] = row - 1
+                    self.slide_list[str(row)] = row - 1
             else:
                 row += 1
-                self.slideList[str(row)] = row - 1
+                self.slide_list[str(row)] = row - 1
                 # If current slide set background to image
                 if not self.service_item.is_command() and framenumber == slideno:
                     self.service_item.bg_image_bytes = self.image_manager.get_image_bytes(frame['path'],

=== modified file 'openlp/core/ui/thememanager.py'
--- openlp/core/ui/thememanager.py	2013-10-13 21:07:28 +0000
+++ openlp/core/ui/thememanager.py	2013-11-08 17:01:40 +0000
@@ -40,7 +40,7 @@
 
 from openlp.core.common import AppLocation, Settings, check_directory_exists, UiStrings, translate
 from openlp.core.lib import ImageSource, OpenLPToolbar, Registry, get_text_file_string, build_icon, \
-    check_item_selected, create_thumb, validate_thumb
+    check_item_selected, create_thumb, validate_thumb, register
 from openlp.core.lib.theme import ThemeXML, BackgroundType
 from openlp.core.lib.ui import critical_error_message_box, create_widget_action
 from openlp.core.ui import FileRenameForm, ThemeForm, ThemeManagerHelper
@@ -53,12 +53,12 @@
     """
     Manages the orders of Theme.
     """
+    @register
     def __init__(self, parent=None):
         """
         Constructor
         """
         super(ThemeManager, self).__init__(parent)
-        Registry().register('theme_manager', self)
         Registry().register_function('bootstrap_initialise', self.load_first_time_themes)
         Registry().register_function('bootstrap_post_set_up', self._push_themes)
         self.settings_section = 'themes'
@@ -498,8 +498,8 @@
             theme_zip = zipfile.ZipFile(file_name)
             xml_file = [name for name in theme_zip.namelist() if os.path.splitext(name)[1].lower() == '.xml']
             if len(xml_file) != 1:
-                log.exception('Theme contains "%s" XML files' % len(xml_file))
-                raise Exception('validation')
+                log.error('Theme contains "%s" XML files' % len(xml_file))
+                raise Exception('Theme validation error')
             xml_tree = ElementTree(element=XML(theme_zip.read(xml_file[0]))).getroot()
             theme_name = xml_tree.find('name').text.strip()
             theme_folder = os.path.join(directory, theme_name)
@@ -551,7 +551,7 @@
                     critical_error_message_box(
                         translate('OpenLP.ThemeManager', 'Validation Error'),
                         translate('OpenLP.ThemeManager', 'File is not a valid theme.'))
-                    log.exception('Theme file does not contain XML data %s' % file_name)
+                    log.error('Theme file does not contain XML data %s' % file_name)
 
     def check_if_theme_exists(self, theme_name):
         """

=== modified file 'openlp/core/utils/__init__.py'
--- openlp/core/utils/__init__.py	2013-10-13 21:07:28 +0000
+++ openlp/core/utils/__init__.py	2013-11-08 17:01:40 +0000
@@ -61,7 +61,6 @@
 IMAGES_FILTER = None
 ICU_COLLATOR = None
 UNO_CONNECTION_TYPE = 'pipe'
-#UNO_CONNECTION_TYPE = u'socket'
 CONTROL_CHARS = re.compile(r'[\x00-\x1F\x7F-\x9F]', re.UNICODE)
 INVALID_FILE_CHARS = re.compile(r'[\\/:\*\?"<>\|\+\[\]%]', re.UNICODE)
 DIGITS_OR_NONDIGITS = re.compile(r'\d+|\D+', re.UNICODE)
@@ -104,7 +103,6 @@
         code = bzr.wait()
         if code != 0:
             raise Exception('Error running bzr log')
-
         # Get all tags.
         bzr = Popen(('bzr', 'tags'), stdout=PIPE)
         output, error = bzr.communicate()
@@ -124,23 +122,23 @@
         # If they are equal, then this tree is tarball with the source for the release. We do not want the revision
         # number in the full version.
         if tree_revision == tag_revision:
-            full_version =  tag_version
+            full_version = tag_version
         else:
-            full_version =  '%s-bzr%s' % (tag_version, tree_revision)
+            full_version = '%s-bzr%s' % (tag_version, tree_revision)
     else:
         # We're not running the development version, let's use the file.
-        filepath = AppLocation.get_directory(AppLocation.VersionDir)
-        filepath = os.path.join(filepath, '.version')
-        fversion = None
+        file_path = AppLocation.get_directory(AppLocation.VersionDir)
+        file_path = os.path.join(file_path, '.version')
+        file_version = None
         try:
-            fversion = open(filepath, 'r')
-            full_version = str(fversion.read()).rstrip()
+            file_version = open(file_path, 'r')
+            full_version = str(file_version.read()).rstrip()
         except IOError:
             log.exception('Error in version file.')
             full_version = '0.0.0-bzr000'
         finally:
-            if fversion:
-                fversion.close()
+            if file_version:
+                file_version.close()
     bits = full_version.split('-')
     APPLICATION_VERSION = {
         'full': full_version,

=== modified file 'openlp/plugins/alerts/lib/alertsmanager.py'
--- openlp/plugins/alerts/lib/alertsmanager.py	2013-10-13 21:07:28 +0000
+++ openlp/plugins/alerts/lib/alertsmanager.py	2013-11-08 17:01:40 +0000
@@ -36,7 +36,7 @@
 from PyQt4 import QtCore
 
 from openlp.core.common import translate
-from openlp.core.lib import Registry
+from openlp.core.lib import Registry, register
 
 
 log = logging.getLogger(__name__)
@@ -48,9 +48,9 @@
     """
     log.info('Alert Manager loaded')
 
+    @register
     def __init__(self, parent):
         super(AlertsManager, self).__init__(parent)
-        Registry().register('alerts_manager', self)
         self.timer_id = 0
         self.alert_list = []
         Registry().register_function('live_display_active', self.generate_alert)

=== modified file 'openlp/plugins/bibles/lib/csvbible.py'
--- openlp/plugins/bibles/lib/csvbible.py	2013-10-13 21:07:28 +0000
+++ openlp/plugins/bibles/lib/csvbible.py	2013-11-08 17:01:40 +0000
@@ -93,7 +93,7 @@
         success = True
         language_id = self.get_language(bible_name)
         if not language_id:
-            log.exception('Importing books from "%s" failed' % self.filename)
+            log.error('Importing books from "%s" failed' % self.filename)
             return False
         books_file = None
         book_list = {}
@@ -112,7 +112,7 @@
                     str(line[2], details['encoding']))
                 book_ref_id = self.get_book_ref_id_by_name(str(line[2], details['encoding']), 67, language_id)
                 if not book_ref_id:
-                    log.exception('Importing books from "%s" failed' % self.booksfile)
+                    log.error('Importing books from "%s" failed' % self.booksfile)
                     return False
                 book_details = BiblesResourcesDB.get_book_by_id(book_ref_id)
                 self.create_book(str(line[2], details['encoding']), book_ref_id, book_details['testament_id'])

=== modified file 'openlp/plugins/bibles/lib/http.py'
--- openlp/plugins/bibles/lib/http.py	2013-10-13 21:07:28 +0000
+++ openlp/plugins/bibles/lib/http.py	2013-11-08 17:01:40 +0000
@@ -553,7 +553,7 @@
             handler = BSExtract(self.proxy_server)
         books = handler.get_books_from_http(self.download_name)
         if not books:
-            log.exception('Importing books from %s - download name: "%s" '\
+            log.error('Importing books from %s - download name: "%s" '\
                 'failed' % (self.download_source, self.download_name))
             return False
         self.wizard.progress_bar.setMaximum(len(books) + 2)
@@ -565,7 +565,7 @@
         else:
             language_id = self.get_language(bible_name)
         if not language_id:
-            log.exception('Importing books from %s failed' % self.filename)
+            log.error('Importing books from %s failed' % self.filename)
             return False
         for book in books:
             if self.stop_import_flag:
@@ -574,7 +574,7 @@
                 'BiblesPlugin.HTTPBible', 'Importing %s...', 'Importing <book name>...') % book)
             book_ref_id = self.get_book_ref_id_by_name(book, len(books), language_id)
             if not book_ref_id:
-                log.exception('Importing books from %s - download name: "%s" '\
+                log.error('Importing books from %s - download name: "%s" '\
                     'failed' % (self.download_source, self.download_name))
                 return False
             book_details = BiblesResourcesDB.get_book_by_id(book_ref_id)

=== modified file 'openlp/plugins/bibles/lib/opensong.py'
--- openlp/plugins/bibles/lib/opensong.py	2013-10-13 21:07:28 +0000
+++ openlp/plugins/bibles/lib/opensong.py	2013-11-08 17:01:40 +0000
@@ -84,14 +84,14 @@
             bible = opensong.getroot()
             language_id = self.get_language(bible_name)
             if not language_id:
-                log.exception('Importing books from "%s" failed' % self.filename)
+                log.error('Importing books from "%s" failed' % self.filename)
                 return False
             for book in bible.b:
                 if self.stop_import_flag:
                     break
                 book_ref_id = self.get_book_ref_id_by_name(str(book.attrib['n']), len(bible.b), language_id)
                 if not book_ref_id:
-                    log.exception('Importing books from "%s" failed' % self.filename)
+                    log.error('Importing books from "%s" failed' % self.filename)
                     return False
                 book_details = BiblesResourcesDB.get_book_by_id(book_ref_id)
                 db_book = self.create_book(str(book.attrib['n']), book_ref_id, book_details['testament_id'])

=== modified file 'openlp/plugins/bibles/lib/osis.py'
--- openlp/plugins/bibles/lib/osis.py	2013-10-13 21:07:28 +0000
+++ openlp/plugins/bibles/lib/osis.py	2013-11-08 17:01:40 +0000
@@ -131,7 +131,7 @@
                     if not language_id:
                         language_id = self.get_language(bible_name)
                         if not language_id:
-                            log.exception('Importing books from "%s" failed' % self.filename)
+                            log.error('Importing books from "%s" failed' % self.filename)
                             return False
                     match_count += 1
                     book = str(match.group(1))
@@ -140,7 +140,7 @@
                     verse_text = match.group(4)
                     book_ref_id = self.get_book_ref_id_by_name(book, book_count, language_id)
                     if not book_ref_id:
-                        log.exception('Importing books from "%s" failed' % self.filename)
+                        log.error('Importing books from "%s" failed' % self.filename)
                         return False
                     book_details = BiblesResourcesDB.get_book_by_id(book_ref_id)
                     if not db_book or db_book.name != book_details['name']:

=== modified file 'openlp/plugins/songs/lib/importer.py'
--- openlp/plugins/songs/lib/importer.py	2013-10-13 20:36:42 +0000
+++ openlp/plugins/songs/lib/importer.py	2013-11-08 17:01:40 +0000
@@ -82,6 +82,7 @@
     except ImportError:
         log.exception('Error importing %s', 'WorshipCenterProImport')
 
+
 class SongFormatSelect(object):
     """
     This is a special enumeration class listing available file selection modes.

=== modified file 'openlp/plugins/songs/lib/oooimport.py'
--- openlp/plugins/songs/lib/oooimport.py	2013-10-13 20:36:42 +0000
+++ openlp/plugins/songs/lib/oooimport.py	2013-11-08 17:01:40 +0000
@@ -127,7 +127,7 @@
                     manager = uno_instance.ServiceManager
                     self.desktop = manager.createInstanceWithContext("com.sun.star.frame.Desktop", uno_instance)
                     return
-            raise
+            raise Exception("Unable to start LibreOffice")
 
     def startOooProcess(self):
         try:

=== modified file 'tests/functional/openlp_core_common/__init__.py'
--- tests/functional/openlp_core_common/__init__.py	2013-10-13 17:02:12 +0000
+++ tests/functional/openlp_core_common/__init__.py	2013-11-08 17:01:40 +0000
@@ -1,1 +0,0 @@
-__author__ = 'tim'

=== modified file 'tests/functional/openlp_core_common/test_applocation.py'
--- tests/functional/openlp_core_common/test_applocation.py	2013-10-14 05:01:01 +0000
+++ tests/functional/openlp_core_common/test_applocation.py	2013-11-08 17:01:40 +0000
@@ -59,7 +59,6 @@
 
             # WHEN: we call AppLocation.get_data_path()
             data_path = AppLocation.get_data_path()
-            print(data_path)
 
             # THEN: check that all the correct methods were called, and the result is correct
             mocked_settings.contains.assert_called_with('advanced/data path')

=== added file 'tests/functional/openlp_core_common/test_init.py'
--- tests/functional/openlp_core_common/test_init.py	1970-01-01 00:00:00 +0000
+++ tests/functional/openlp_core_common/test_init.py	2013-11-08 17:01:40 +0000
@@ -0,0 +1,65 @@
+# -*- coding: utf-8 -*-
+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
+
+###############################################################################
+# OpenLP - Open Source Lyrics Projection                                      #
+# --------------------------------------------------------------------------- #
+# Copyright (c) 2008-2013 Raoul Snyman                                        #
+# Portions copyright (c) 2008-2013 Tim Bentley, Gerald Britton, Jonathan      #
+# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub,      #
+# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer.   #
+# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru,          #
+# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith,             #
+# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock,              #
+# Frode Woldsund, Martin Zibricky, Patrick Zimmermann                         #
+# --------------------------------------------------------------------------- #
+# This program is free software; you can redistribute it and/or modify it     #
+# under the terms of the GNU General Public License as published by the Free  #
+# Software Foundation; version 2 of the License.                              #
+#                                                                             #
+# This program is distributed in the hope that it will be useful, but WITHOUT #
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or       #
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for    #
+# more details.                                                               #
+#                                                                             #
+# You should have received a copy of the GNU General Public License along     #
+# with this program; if not, write to the Free Software Foundation, Inc., 59  #
+# Temple Place, Suite 330, Boston, MA 02111-1307 USA                          #
+###############################################################################
+"""
+Functional tests to test the AppLocation class and related methods.
+"""
+
+from unittest import TestCase
+
+from openlp.core.common import de_hump
+
+class TestInitFunctions(TestCase):
+    """
+    A test suite to test out various functions in the __init__ class.
+    """
+    def de_hump_conversion_test(self):
+        """
+        Test the de_hump function with a class name
+        """
+        # GIVEN: a Class name in Camel Case
+        string = "MyClass"
+
+        # WHEN: we call de_hump
+        new_string = de_hump(string)
+
+        # THEN: the new string should be converted to python format
+        self.assertTrue(new_string == "my_class", 'The class name should have been converted')
+
+    def de_hump_static_test(self):
+        """
+        Test the de_hump function with a python string
+        """
+        # GIVEN: a Class name in Camel Case
+        string = "my_class"
+
+        # WHEN: we call de_hump
+        new_string = de_hump(string)
+
+        # THEN: the new string should be converted to python format
+        self.assertTrue(new_string == "my_class", 'The class name should have been preserved')

=== modified file 'tests/functional/openlp_core_lib/test_registry.py'
--- tests/functional/openlp_core_lib/test_registry.py	2013-10-02 21:37:00 +0000
+++ tests/functional/openlp_core_lib/test_registry.py	2013-11-08 17:01:40 +0000
@@ -99,10 +99,10 @@
 
         # WHEN: I execute the a 2nd function with the different reference and execute the function
         Registry().register_function('test2', self.dummy_function_2)
-        return_value = Registry().execute('test2')
+        return_value = Registry().execute('test2a')
 
         # THEN: I expect then function to have been called and a return given
-        self.assertEqual(return_value[0], 'function_2', 'A return value is provided and matches')
+        self.assertEqual(return_value[0], 'function_2', 'A return value should be returned and provides a matche')
 
     def dummy_function_1(self):
         return "function_1"


Follow ups