← Back to team overview

openlp-core team mailing list archive

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

 

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

Requested reviews:
  OpenLP Core (openlp-core)

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

Fine refactor merge 

lp:~trb143/openlp/refactor26 (revision 2684)
[SUCCESS] https://ci.openlp.io/job/Branch-01-Pull/1480/
[SUCCESS] https://ci.openlp.io/job/Branch-02-Functional-Tests/1391/
[SUCCESS] https://ci.openlp.io/job/Branch-03-Interface-Tests/1329/
[SUCCESS] https://ci.openlp.io/job/Branch-04a-Windows_Functional_Tests/1129/
[SUCCESS] https://ci.openlp.io/job/Branch-04b-Windows_Interface_Tests/720/
[SUCCESS] https://ci.openlp.io/job/Branch-05a-Code_Analysis/787/
[SUCCESS] https://ci.openlp.io/job/Branch-05b-Test_Coverage/655/

-- 
Your team OpenLP Core is requested to review the proposed merge of lp:~trb143/openlp/refactor26 into lp:openlp.
=== modified file 'openlp/core/lib/__init__.py'
--- openlp/core/lib/__init__.py	2015-12-31 22:46:06 +0000
+++ openlp/core/lib/__init__.py	2016-04-22 19:42:45 +0000
@@ -312,12 +312,9 @@
         return translate('OpenLP.core.lib', '%s, %s', 'Locale list separator: start') % (string_list[0], merged)
 
 
-from .colorbutton import ColorButton
 from .exceptions import ValidationError
 from .filedialog import FileDialog
 from .screen import ScreenList
-from .listwidgetwithdnd import ListWidgetWithDnD
-from .treewidgetwithdnd import TreeWidgetWithDnD
 from .formattingtags import FormattingTags
 from .spelltextedit import SpellTextEdit
 from .plugin import PluginStatus, StringContent, Plugin
@@ -325,8 +322,6 @@
 from .settingstab import SettingsTab
 from .serviceitem import ServiceItem, ServiceItemType, ItemCapabilities
 from .htmlbuilder import build_html, build_lyrics_format_css, build_lyrics_outline_css
-from .toolbar import OpenLPToolbar
-from .dockwidget import OpenLPDockWidget
 from .imagemanager import ImageManager
 from .renderer import Renderer
 from .mediamanageritem import MediaManagerItem

=== removed file 'openlp/core/lib/colorbutton.py'
--- openlp/core/lib/colorbutton.py	2015-12-31 22:46:06 +0000
+++ openlp/core/lib/colorbutton.py	1970-01-01 00:00:00 +0000
@@ -1,82 +0,0 @@
-# -*- coding: utf-8 -*-
-# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
-
-###############################################################################
-# OpenLP - Open Source Lyrics Projection                                      #
-# --------------------------------------------------------------------------- #
-# Copyright (c) 2008-2016 OpenLP Developers                                   #
-# --------------------------------------------------------------------------- #
-# 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 a custom widget based on QPushButton for the selection of colors
-"""
-from PyQt5 import QtCore, QtGui, QtWidgets
-
-from openlp.core.common import translate
-
-
-class ColorButton(QtWidgets.QPushButton):
-    """
-    Subclasses QPushbutton to create a "Color Chooser" button
-    """
-
-    colorChanged = QtCore.pyqtSignal(str)
-
-    def __init__(self, parent=None):
-        """
-        Initialise the ColorButton
-        """
-        super(ColorButton, self).__init__()
-        self.parent = parent
-        self.change_color('#ffffff')
-        self.setToolTip(translate('OpenLP.ColorButton', 'Click to select a color.'))
-        self.clicked.connect(self.on_clicked)
-
-    def change_color(self, color):
-        """
-        Sets the _color variable and the background color.
-
-        :param color:  String representation of a hexidecimal color
-        """
-        self._color = color
-        self.setStyleSheet('background-color: %s' % color)
-
-    @property
-    def color(self):
-        """
-        Property method to return the color variable
-
-        :return:  String representation of a hexidecimal color
-        """
-        return self._color
-
-    @color.setter
-    def color(self, color):
-        """
-        Property setter to change the instance color
-
-        :param color:  String representation of a hexidecimal color
-        """
-        self.change_color(color)
-
-    def on_clicked(self):
-        """
-        Handle the PushButton clicked signal, showing the ColorDialog and validating the input
-        """
-        new_color = QtWidgets.QColorDialog.getColor(QtGui.QColor(self._color), self.parent)
-        if new_color.isValid() and self._color != new_color.name():
-            self.change_color(new_color.name())
-            self.colorChanged.emit(new_color.name())

=== removed file 'openlp/core/lib/dockwidget.py'
--- openlp/core/lib/dockwidget.py	2015-12-31 22:46:06 +0000
+++ openlp/core/lib/dockwidget.py	1970-01-01 00:00:00 +0000
@@ -1,56 +0,0 @@
-# -*- coding: utf-8 -*-
-# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
-
-###############################################################################
-# OpenLP - Open Source Lyrics Projection                                      #
-# --------------------------------------------------------------------------- #
-# Copyright (c) 2008-2016 OpenLP Developers                                   #
-# --------------------------------------------------------------------------- #
-# 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 additional functionality required by OpenLP from the inherited QDockWidget.
-"""
-
-import logging
-
-from PyQt5 import QtWidgets
-
-from openlp.core.lib import ScreenList, build_icon
-
-log = logging.getLogger(__name__)
-
-
-class OpenLPDockWidget(QtWidgets.QDockWidget):
-    """
-    Custom DockWidget class to handle events
-    """
-    def __init__(self, parent=None, name=None, icon=None):
-        """
-        Initialise the DockWidget
-        """
-        log.debug('Initialise the %s widget' % name)
-        super(OpenLPDockWidget, self).__init__(parent)
-        if name:
-            self.setObjectName(name)
-        if icon:
-            self.setWindowIcon(build_icon(icon))
-        # Sort out the minimum width.
-        screens = ScreenList()
-        main_window_docbars = screens.current['size'].width() // 5
-        if main_window_docbars > 300:
-            self.setMinimumWidth(300)
-        else:
-            self.setMinimumWidth(main_window_docbars)

=== removed file 'openlp/core/lib/listwidgetwithdnd.py'
--- openlp/core/lib/listwidgetwithdnd.py	2015-12-31 22:46:06 +0000
+++ openlp/core/lib/listwidgetwithdnd.py	1970-01-01 00:00:00 +0000
@@ -1,107 +0,0 @@
-# -*- coding: utf-8 -*-
-# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
-
-###############################################################################
-# OpenLP - Open Source Lyrics Projection                                      #
-# --------------------------------------------------------------------------- #
-# Copyright (c) 2008-2016 OpenLP Developers                                   #
-# --------------------------------------------------------------------------- #
-# 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                          #
-###############################################################################
-"""
-Extend QListWidget to handle drag and drop functionality
-"""
-import os
-
-from PyQt5 import QtCore, QtGui, QtWidgets
-
-from openlp.core.common import Registry
-
-
-class ListWidgetWithDnD(QtWidgets.QListWidget):
-    """
-    Provide a list widget to store objects and handle drag and drop events
-    """
-    def __init__(self, parent=None, name=''):
-        """
-        Initialise the list widget
-        """
-        super(ListWidgetWithDnD, self).__init__(parent)
-        self.mime_data_text = name
-
-    def activateDnD(self):
-        """
-        Activate DnD of widget
-        """
-        self.setAcceptDrops(True)
-        self.setDragDropMode(QtWidgets.QAbstractItemView.DragDrop)
-        Registry().register_function(('%s_dnd' % self.mime_data_text), self.parent().load_file)
-
-    def mouseMoveEvent(self, event):
-        """
-        Drag and drop event does not care what data is selected as the recipient will use events to request the data
-        move just tell it what plugin to call
-        """
-        if event.buttons() != QtCore.Qt.LeftButton:
-            event.ignore()
-            return
-        if not self.selectedItems():
-            event.ignore()
-            return
-        drag = QtGui.QDrag(self)
-        mime_data = QtCore.QMimeData()
-        drag.setMimeData(mime_data)
-        mime_data.setText(self.mime_data_text)
-        drag.exec(QtCore.Qt.CopyAction)
-
-    def dragEnterEvent(self, event):
-        """
-        When something is dragged into this object, check if you should be able to drop it in here.
-        """
-        if event.mimeData().hasUrls():
-            event.accept()
-        else:
-            event.ignore()
-
-    def dragMoveEvent(self, event):
-        """
-        Make an object droppable, and set it to copy the contents of the object, not move it.
-        """
-        if event.mimeData().hasUrls():
-            event.setDropAction(QtCore.Qt.CopyAction)
-            event.accept()
-        else:
-            event.ignore()
-
-    def dropEvent(self, event):
-        """
-        Receive drop event check if it is a file and process it if it is.
-
-        :param event:  Handle of the event pint passed
-        """
-        if event.mimeData().hasUrls():
-            event.setDropAction(QtCore.Qt.CopyAction)
-            event.accept()
-            files = []
-            for url in event.mimeData().urls():
-                local_file = os.path.normpath(url.toLocalFile())
-                if os.path.isfile(local_file):
-                    files.append(local_file)
-                elif os.path.isdir(local_file):
-                    listing = os.listdir(local_file)
-                    for file in listing:
-                        files.append(os.path.join(local_file, file))
-            Registry().execute('%s_dnd' % self.mime_data_text, {'files': files, 'target': self.itemAt(event.pos())})
-        else:
-            event.ignore()

=== modified file 'openlp/core/lib/mediamanageritem.py'
--- openlp/core/lib/mediamanageritem.py	2016-04-03 19:12:37 +0000
+++ openlp/core/lib/mediamanageritem.py	2016-04-22 19:42:45 +0000
@@ -29,10 +29,11 @@
 from PyQt5 import QtCore, QtGui, QtWidgets
 
 from openlp.core.common import Registry, RegistryProperties, Settings, UiStrings, translate
-from openlp.core.lib import FileDialog, OpenLPToolbar, ServiceItem, StringContent, ListWidgetWithDnD, \
-    ServiceItemContext
+from openlp.core.lib import FileDialog, ServiceItem, StringContent, ServiceItemContext
 from openlp.core.lib.searchedit import SearchEdit
 from openlp.core.lib.ui import create_widget_action, critical_error_message_box
+from openlp.core.ui.lib.listwidgetwithdnd import ListWidgetWithDnD
+from openlp.core.ui.lib.toolbar import OpenLPToolbar
 
 log = logging.getLogger(__name__)
 

=== removed file 'openlp/core/lib/toolbar.py'
--- openlp/core/lib/toolbar.py	2015-12-31 22:46:06 +0000
+++ openlp/core/lib/toolbar.py	1970-01-01 00:00:00 +0000
@@ -1,90 +0,0 @@
-# -*- coding: utf-8 -*-
-# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
-
-###############################################################################
-# OpenLP - Open Source Lyrics Projection                                      #
-# --------------------------------------------------------------------------- #
-# Copyright (c) 2008-2016 OpenLP Developers                                   #
-# --------------------------------------------------------------------------- #
-# 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 common toolbar handling for OpenLP
-"""
-import logging
-
-from PyQt5 import QtCore, QtWidgets
-
-from openlp.core.lib.ui import create_widget_action
-
-log = logging.getLogger(__name__)
-
-
-class OpenLPToolbar(QtWidgets.QToolBar):
-    """
-    Lots of toolbars around the place, so it makes sense to have a common way to manage them. This is the base toolbar
-    class.
-    """
-    def __init__(self, parent):
-        """
-        Initialise the toolbar.
-        """
-        super(OpenLPToolbar, self).__init__(parent)
-        # useful to be able to reuse button icons...
-        self.setIconSize(QtCore.QSize(20, 20))
-        self.actions = {}
-        log.debug('Init done for %s' % parent.__class__.__name__)
-
-    def add_toolbar_action(self, name, **kwargs):
-        """
-        A method to help developers easily add a button to the toolbar. A new QAction is created by calling
-        ``create_action()``. The action is added to the toolbar and the toolbar is set as parent. For more details
-        please look at openlp.core.lib.ui.create_action()
-        """
-        action = create_widget_action(self, name, **kwargs)
-        self.actions[name] = action
-        return action
-
-    def add_toolbar_widget(self, widget):
-        """
-        Add a widget and store it's handle under the widgets object name.
-        """
-        action = self.addWidget(widget)
-        self.actions[widget.objectName()] = action
-
-    def set_widget_visible(self, widgets, visible=True):
-        """
-        Set the visibility for a widget or a list of widgets.
-
-        :param widgets: A list of string with widget object names.
-        :param visible: The new state as bool.
-        """
-        for handle in widgets:
-            if handle in self.actions:
-                self.actions[handle].setVisible(visible)
-            else:
-                log.warning('No handle "%s" in actions list.', str(handle))
-
-    def set_widget_enabled(self, widgets, enabled=True):
-        """
-        Set the enabled state for a widget or a list of widgets.
-
-        :param widgets: A list of string with widget object names.
-        :param enabled: The new state as bool.
-        """
-        for handle in widgets:
-            if handle in self.actions:
-                self.actions[handle].setEnabled(enabled)
-            else:
-                log.warning('No handle "%s" in actions list.', str(handle))

=== removed file 'openlp/core/lib/treewidgetwithdnd.py'
--- openlp/core/lib/treewidgetwithdnd.py	2015-12-31 22:46:06 +0000
+++ openlp/core/lib/treewidgetwithdnd.py	1970-01-01 00:00:00 +0000
@@ -1,139 +0,0 @@
-# -*- coding: utf-8 -*-
-# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
-
-###############################################################################
-# OpenLP - Open Source Lyrics Projection                                      #
-# --------------------------------------------------------------------------- #
-# Copyright (c) 2008-2016 OpenLP Developers                                   #
-# --------------------------------------------------------------------------- #
-# 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                          #
-###############################################################################
-"""
-Extend QTreeWidget to handle drag and drop functionality
-"""
-import os
-
-from PyQt5 import QtCore, QtGui, QtWidgets
-
-from openlp.core.common import Registry
-
-
-class TreeWidgetWithDnD(QtWidgets.QTreeWidget):
-    """
-    Provide a tree widget to store objects and handle drag and drop events
-    """
-    def __init__(self, parent=None, name=''):
-        """
-        Initialise the tree widget
-        """
-        super(TreeWidgetWithDnD, self).__init__(parent)
-        self.mime_data_text = name
-        self.allow_internal_dnd = False
-        self.header().close()
-        self.default_indentation = self.indentation()
-        self.setIndentation(0)
-        self.setAnimated(True)
-
-    def activateDnD(self):
-        """
-        Activate DnD of widget
-        """
-        self.setAcceptDrops(True)
-        self.setDragDropMode(QtWidgets.QAbstractItemView.DragDrop)
-        Registry().register_function(('%s_dnd' % self.mime_data_text), self.parent().load_file)
-        Registry().register_function(('%s_dnd_internal' % self.mime_data_text), self.parent().dnd_move_internal)
-
-    def mouseMoveEvent(self, event):
-        """
-        Drag and drop event does not care what data is selected as the recipient will use events to request the data
-        move just tell it what plugin to call
-
-        :param event: The event that occurred
-        """
-        if event.buttons() != QtCore.Qt.LeftButton:
-            event.ignore()
-            return
-        if not self.selectedItems():
-            event.ignore()
-            return
-        drag = QtGui.QDrag(self)
-        mime_data = QtCore.QMimeData()
-        drag.setMimeData(mime_data)
-        mime_data.setText(self.mime_data_text)
-        drag.exec(QtCore.Qt.CopyAction)
-
-    def dragEnterEvent(self, event):
-        """
-        Receive drag enter event, check if it is a file or internal object and allow it if it is.
-
-        :param event:  The event that occurred
-        """
-        if event.mimeData().hasUrls():
-            event.accept()
-        elif self.allow_internal_dnd:
-            event.accept()
-        else:
-            event.ignore()
-
-    def dragMoveEvent(self, event):
-        """
-        Receive drag move event, check if it is a file or internal object and allow it if it is.
-
-        :param event: The event that occurred
-        """
-        QtWidgets.QTreeWidget.dragMoveEvent(self, event)
-        if event.mimeData().hasUrls():
-            event.setDropAction(QtCore.Qt.CopyAction)
-            event.accept()
-        elif self.allow_internal_dnd:
-            event.setDropAction(QtCore.Qt.CopyAction)
-            event.accept()
-        else:
-            event.ignore()
-
-    def dropEvent(self, event):
-        """
-        Receive drop event, check if it is a file or internal object and process it if it is.
-
-        :param event: Handle of the event pint passed
-        """
-        if event.mimeData().hasUrls():
-            event.setDropAction(QtCore.Qt.CopyAction)
-            event.accept()
-            files = []
-            for url in event.mimeData().urls():
-                local_file = url.toLocalFile()
-                if os.path.isfile(local_file):
-                    files.append(local_file)
-                elif os.path.isdir(local_file):
-                    listing = os.listdir(local_file)
-                    for file_name in listing:
-                        files.append(os.path.join(local_file, file_name))
-            Registry().execute('%s_dnd' % self.mime_data_text, {'files': files, 'target': self.itemAt(event.pos())})
-        elif self.allow_internal_dnd:
-            event.setDropAction(QtCore.Qt.CopyAction)
-            event.accept()
-            Registry().execute('%s_dnd_internal' % self.mime_data_text, self.itemAt(event.pos()))
-        else:
-            event.ignore()
-
-    # Convenience methods for emulating a QListWidget. This helps keeping MediaManagerItem simple.
-    def addItem(self, item):
-        self.addTopLevelItem(item)
-
-    def count(self):
-        return self.topLevelItemCount()
-
-    def item(self, index):
-        return self.topLevelItem(index)

=== modified file 'openlp/core/ui/__init__.py'
--- openlp/core/ui/__init__.py	2016-02-16 21:14:38 +0000
+++ openlp/core/ui/__init__.py	2016-04-22 19:42:45 +0000
@@ -113,7 +113,6 @@
 from .formattingtagform import FormattingTagForm
 from .formattingtagcontroller import FormattingTagController
 from .shortcutlistform import ShortcutListForm
-from .mediadockmanager import MediaDockManager
 from .servicemanager import ServiceManager
 from .thememanager import ThemeManager
 from .projector.manager import ProjectorManager
@@ -121,7 +120,7 @@
 from .projector.editform import ProjectorEditForm
 
 __all__ = ['SplashScreen', 'AboutForm', 'SettingsForm', 'MainDisplay', 'SlideController', 'ServiceManager', 'ThemeForm',
-           'ThemeManager', 'MediaDockManager', 'ServiceItemEditForm', 'FirstTimeForm', 'FirstTimeLanguageForm',
+           'ThemeManager', 'ServiceItemEditForm', 'FirstTimeForm', 'FirstTimeLanguageForm',
            'Display', 'ServiceNoteForm', 'ThemeLayoutForm', 'FileRenameForm', 'StartTimeForm', 'MainDisplay',
            'SlideController', 'DisplayController', 'GeneralTab', 'ThemesTab', 'AdvancedTab', 'PluginForm',
            'FormattingTagForm', 'ShortcutListForm', 'FormattingTagController', 'SingleColumnTableWidget',

=== modified file 'openlp/core/ui/generaltab.py'
--- openlp/core/ui/generaltab.py	2016-04-13 15:50:04 +0000
+++ openlp/core/ui/generaltab.py	2016-04-22 19:42:45 +0000
@@ -27,7 +27,8 @@
 from PyQt5 import QtCore, QtGui, QtWidgets
 
 from openlp.core.common import Registry, Settings, UiStrings, translate, get_images_filter
-from openlp.core.lib import SettingsTab, ScreenList, ColorButton, build_icon
+from openlp.core.lib import SettingsTab, ScreenList, build_icon
+from openlp.core.ui.lib.colorbutton import ColorButton
 
 log = logging.getLogger(__name__)
 

=== modified file 'openlp/core/ui/lib/__init__.py'
--- openlp/core/ui/lib/__init__.py	2016-04-01 17:09:47 +0000
+++ openlp/core/ui/lib/__init__.py	2016-04-22 19:42:45 +0000
@@ -19,3 +19,15 @@
 # with this program; if not, write to the Free Software Foundation, Inc., 59  #
 # Temple Place, Suite 330, Boston, MA 02111-1307 USA                          #
 ###############################################################################
+
+from .colorbutton import ColorButton
+from .listwidgetwithdnd import ListWidgetWithDnD
+from .treewidgetwithdnd import TreeWidgetWithDnD
+from .toolbar import OpenLPToolbar
+from .dockwidget import OpenLPDockWidget
+from .wizard import OpenLPWizard, WizardStrings
+from .mediadockmanager import MediaDockManager
+from .listpreviewwidget import ListPreviewWidget
+
+__all__ = ['ColorButton', 'ListPreviewWidget', 'ListWidgetWithDnD', 'OpenLPToolbar', 'OpenLPDockWidget',
+           'OpenLPWizard', 'WizardStrings', 'MediaDockManager', 'ListPreviewWidget']

=== added file 'openlp/core/ui/lib/colorbutton.py'
--- openlp/core/ui/lib/colorbutton.py	1970-01-01 00:00:00 +0000
+++ openlp/core/ui/lib/colorbutton.py	2016-04-22 19:42:45 +0000
@@ -0,0 +1,82 @@
+# -*- coding: utf-8 -*-
+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
+
+###############################################################################
+# OpenLP - Open Source Lyrics Projection                                      #
+# --------------------------------------------------------------------------- #
+# Copyright (c) 2008-2016 OpenLP Developers                                   #
+# --------------------------------------------------------------------------- #
+# 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 a custom widget based on QPushButton for the selection of colors
+"""
+from PyQt5 import QtCore, QtGui, QtWidgets
+
+from openlp.core.common import translate
+
+
+class ColorButton(QtWidgets.QPushButton):
+    """
+    Subclasses QPushbutton to create a "Color Chooser" button
+    """
+
+    colorChanged = QtCore.pyqtSignal(str)
+
+    def __init__(self, parent=None):
+        """
+        Initialise the ColorButton
+        """
+        super(ColorButton, self).__init__()
+        self.parent = parent
+        self.change_color('#ffffff')
+        self.setToolTip(translate('OpenLP.ColorButton', 'Click to select a color.'))
+        self.clicked.connect(self.on_clicked)
+
+    def change_color(self, color):
+        """
+        Sets the _color variable and the background color.
+
+        :param color:  String representation of a hexidecimal color
+        """
+        self._color = color
+        self.setStyleSheet('background-color: %s' % color)
+
+    @property
+    def color(self):
+        """
+        Property method to return the color variable
+
+        :return:  String representation of a hexidecimal color
+        """
+        return self._color
+
+    @color.setter
+    def color(self, color):
+        """
+        Property setter to change the instance color
+
+        :param color:  String representation of a hexidecimal color
+        """
+        self.change_color(color)
+
+    def on_clicked(self):
+        """
+        Handle the PushButton clicked signal, showing the ColorDialog and validating the input
+        """
+        new_color = QtWidgets.QColorDialog.getColor(QtGui.QColor(self._color), self.parent)
+        if new_color.isValid() and self._color != new_color.name():
+            self.change_color(new_color.name())
+            self.colorChanged.emit(new_color.name())

=== added file 'openlp/core/ui/lib/dockwidget.py'
--- openlp/core/ui/lib/dockwidget.py	1970-01-01 00:00:00 +0000
+++ openlp/core/ui/lib/dockwidget.py	2016-04-22 19:42:45 +0000
@@ -0,0 +1,56 @@
+# -*- coding: utf-8 -*-
+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
+
+###############################################################################
+# OpenLP - Open Source Lyrics Projection                                      #
+# --------------------------------------------------------------------------- #
+# Copyright (c) 2008-2016 OpenLP Developers                                   #
+# --------------------------------------------------------------------------- #
+# 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 additional functionality required by OpenLP from the inherited QDockWidget.
+"""
+
+import logging
+
+from PyQt5 import QtWidgets
+
+from openlp.core.lib import ScreenList, build_icon
+
+log = logging.getLogger(__name__)
+
+
+class OpenLPDockWidget(QtWidgets.QDockWidget):
+    """
+    Custom DockWidget class to handle events
+    """
+    def __init__(self, parent=None, name=None, icon=None):
+        """
+        Initialise the DockWidget
+        """
+        log.debug('Initialise the %s widget' % name)
+        super(OpenLPDockWidget, self).__init__(parent)
+        if name:
+            self.setObjectName(name)
+        if icon:
+            self.setWindowIcon(build_icon(icon))
+        # Sort out the minimum width.
+        screens = ScreenList()
+        main_window_docbars = screens.current['size'].width() // 5
+        if main_window_docbars > 300:
+            self.setMinimumWidth(300)
+        else:
+            self.setMinimumWidth(main_window_docbars)

=== added file 'openlp/core/ui/lib/listpreviewwidget.py'
--- openlp/core/ui/lib/listpreviewwidget.py	1970-01-01 00:00:00 +0000
+++ openlp/core/ui/lib/listpreviewwidget.py	2016-04-22 19:42:45 +0000
@@ -0,0 +1,226 @@
+# -*- coding: utf-8 -*-
+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
+
+###############################################################################
+# OpenLP - Open Source Lyrics Projection                                      #
+# --------------------------------------------------------------------------- #
+# Copyright (c) 2008-2016 OpenLP Developers                                   #
+# --------------------------------------------------------------------------- #
+# 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                          #
+###############################################################################
+"""
+The :mod:`listpreviewwidget` is a widget that lists the slides in the slide controller.
+It is based on a QTableWidget but represents its contents in list form.
+"""
+
+from PyQt5 import QtCore, QtGui, QtWidgets
+
+from openlp.core.common import RegistryProperties, Settings
+from openlp.core.lib import ImageSource, ServiceItem
+
+
+class ListPreviewWidget(QtWidgets.QTableWidget, RegistryProperties):
+    """
+    A special type of QTableWidget which lists the slides in the slide controller
+
+    :param parent:
+    :param screen_ratio:
+    """
+
+    def __init__(self, parent, screen_ratio):
+        """
+        Initializes the widget to default state.
+
+        An empty ``ServiceItem`` is used by default. replace_service_manager_item() needs to be called to make this
+        widget display something.
+        """
+        super(QtWidgets.QTableWidget, self).__init__(parent)
+        self._setup(screen_ratio)
+
+    def _setup(self, screen_ratio):
+        """
+        Set up the widget
+        """
+        self.setColumnCount(1)
+        self.horizontalHeader().setVisible(False)
+        self.setColumnWidth(0, self.parent().width())
+        self.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
+        self.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection)
+        self.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
+        self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
+        self.setAlternatingRowColors(True)
+        # Initialize variables.
+        self.service_item = ServiceItem()
+        self.screen_ratio = screen_ratio
+        # Connect signals
+        self.verticalHeader().sectionResized.connect(self.row_resized)
+
+    def resizeEvent(self, event):
+        """
+        Overloaded method from QTableWidget. Will recalculate the layout.
+        """
+        self.__recalculate_layout()
+
+    def __recalculate_layout(self):
+        """
+        Recalculates the layout of the table widget. It will set height and width
+        of the table cells. QTableWidget does not adapt the cells to the widget size on its own.
+        """
+        self.setColumnWidth(0, self.viewport().width())
+        if self.service_item:
+            # Sort out songs, bibles, etc.
+            if self.service_item.is_text():
+                self.resizeRowsToContents()
+            # Sort out image heights.
+            else:
+                height = self.viewport().width() // self.screen_ratio
+                max_img_row_height = Settings().value('advanced/slide max height')
+                # Adjust for row height cap if in use.
+                if isinstance(max_img_row_height, int) and max_img_row_height > 0 and height > max_img_row_height:
+                    height = max_img_row_height
+                # Apply new height to slides
+                for frame_number in range(len(self.service_item.get_frames())):
+                    self.setRowHeight(frame_number, height)
+
+    def row_resized(self, row, old_height, new_height):
+        """
+        Will scale non-image slides.
+        """
+        # Only for non-text slides when row height cap in use
+        max_img_row_height = Settings().value('advanced/slide max height')
+        if self.service_item.is_text() or not isinstance(max_img_row_height, int) or max_img_row_height <= 0:
+            return
+        # Get and validate label widget containing slide & adjust max width
+        try:
+            self.cellWidget(row, 0).children()[1].setMaximumWidth(new_height * self.screen_ratio)
+        except:
+            return
+
+    def screen_size_changed(self, screen_ratio):
+        """
+        This method is called whenever the live screen size changes, which then makes a layout recalculation necessary
+
+        :param screen_ratio: The new screen ratio
+        """
+        self.screen_ratio = screen_ratio
+        self.__recalculate_layout()
+
+    def replace_service_item(self, service_item, width, slide_number):
+        """
+        Replace the current preview items with the ones in service_item and display the given slide
+
+        :param service_item: The service item to insert
+        :param width: The width of the column
+        :param slide_number: The slide number to pre-select
+        """
+        self.service_item = service_item
+        self.setRowCount(0)
+        self.clear()
+        self.setColumnWidth(0, width)
+        row = 0
+        text = []
+        for frame_number, frame in enumerate(self.service_item.get_frames()):
+            self.setRowCount(self.slide_count() + 1)
+            item = QtWidgets.QTableWidgetItem()
+            slide_height = 0
+            if self.service_item.is_text():
+                if frame['verseTag']:
+                    # These tags are already translated.
+                    verse_def = frame['verseTag']
+                    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
+                else:
+                    row += 1
+                item.setText(frame['text'])
+            else:
+                label = QtWidgets.QLabel()
+                label.setContentsMargins(4, 4, 4, 4)
+                if self.service_item.is_media():
+                    label.setAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter)
+                else:
+                    label.setScaledContents(True)
+                if self.service_item.is_command():
+                    pixmap = QtGui.QPixmap(frame['image'])
+                    pixmap.setDevicePixelRatio(label.devicePixelRatio())
+                    label.setPixmap(pixmap)
+                else:
+                    image = self.image_manager.get_image(frame['path'], ImageSource.ImagePlugin)
+                    pixmap = QtGui.QPixmap.fromImage(image)
+                    pixmap.setDevicePixelRatio(label.devicePixelRatio())
+                    label.setPixmap(pixmap)
+                slide_height = width // self.screen_ratio
+                # Setup and validate row height cap if in use.
+                max_img_row_height = Settings().value('advanced/slide max height')
+                if isinstance(max_img_row_height, int) and max_img_row_height > 0:
+                    if slide_height > max_img_row_height:
+                        slide_height = max_img_row_height
+                    label.setMaximumWidth(max_img_row_height * self.screen_ratio)
+                    label.resize(max_img_row_height * self.screen_ratio, max_img_row_height)
+                    # Build widget with stretch padding
+                    container = QtWidgets.QWidget()
+                    hbox = QtWidgets.QHBoxLayout()
+                    hbox.setContentsMargins(0, 0, 0, 0)
+                    hbox.addWidget(label, stretch=1)
+                    hbox.addStretch(0)
+                    container.setLayout(hbox)
+                    # Add to table
+                    self.setCellWidget(frame_number, 0, container)
+                else:
+                    # Add to table
+                    self.setCellWidget(frame_number, 0, label)
+                row += 1
+            text.append(str(row))
+            self.setItem(frame_number, 0, item)
+            if slide_height:
+                self.setRowHeight(frame_number, slide_height)
+        self.setVerticalHeaderLabels(text)
+        if self.service_item.is_text():
+            self.resizeRowsToContents()
+        self.setColumnWidth(0, self.viewport().width())
+        self.change_slide(slide_number)
+
+    def change_slide(self, slide):
+        """
+        Switches to the given row.
+        """
+        # Retrieve setting
+        autoscrolling = Settings().value('advanced/autoscrolling')
+        # Check if auto-scroll disabled (None) and validate value as dict containing 'dist' and 'pos'
+        # 'dist' represents the slide to scroll to relative to the new slide (-1 = previous, 0 = current, 1 = next)
+        # 'pos' represents the vert position of of the slide (0 = in view, 1 = top, 2 = middle, 3 = bottom)
+        if not (isinstance(autoscrolling, dict) and 'dist' in autoscrolling and 'pos' in autoscrolling and
+                isinstance(autoscrolling['dist'], int) and isinstance(autoscrolling['pos'], int)):
+            return
+        # prevent scrolling past list bounds
+        scroll_to_slide = slide + autoscrolling['dist']
+        if scroll_to_slide < 0:
+            scroll_to_slide = 0
+        if scroll_to_slide >= self.slide_count():
+            scroll_to_slide = self.slide_count() - 1
+        # Scroll to item if possible.
+        self.scrollToItem(self.item(scroll_to_slide, 0), autoscrolling['pos'])
+        self.selectRow(slide)
+
+    def current_slide_number(self):
+        """
+        Returns the position of the currently active item. Will return -1 if the widget is empty.
+        """
+        return super(ListPreviewWidget, self).currentRow()
+
+    def slide_count(self):
+        """
+        Returns the number of slides this widget holds.
+        """
+        return super(ListPreviewWidget, self).rowCount()

=== added file 'openlp/core/ui/lib/listwidgetwithdnd.py'
--- openlp/core/ui/lib/listwidgetwithdnd.py	1970-01-01 00:00:00 +0000
+++ openlp/core/ui/lib/listwidgetwithdnd.py	2016-04-22 19:42:45 +0000
@@ -0,0 +1,107 @@
+# -*- coding: utf-8 -*-
+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
+
+###############################################################################
+# OpenLP - Open Source Lyrics Projection                                      #
+# --------------------------------------------------------------------------- #
+# Copyright (c) 2008-2016 OpenLP Developers                                   #
+# --------------------------------------------------------------------------- #
+# 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                          #
+###############################################################################
+"""
+Extend QListWidget to handle drag and drop functionality
+"""
+import os
+
+from PyQt5 import QtCore, QtGui, QtWidgets
+
+from openlp.core.common import Registry
+
+
+class ListWidgetWithDnD(QtWidgets.QListWidget):
+    """
+    Provide a list widget to store objects and handle drag and drop events
+    """
+    def __init__(self, parent=None, name=''):
+        """
+        Initialise the list widget
+        """
+        super(ListWidgetWithDnD, self).__init__(parent)
+        self.mime_data_text = name
+
+    def activateDnD(self):
+        """
+        Activate DnD of widget
+        """
+        self.setAcceptDrops(True)
+        self.setDragDropMode(QtWidgets.QAbstractItemView.DragDrop)
+        Registry().register_function(('%s_dnd' % self.mime_data_text), self.parent().load_file)
+
+    def mouseMoveEvent(self, event):
+        """
+        Drag and drop event does not care what data is selected as the recipient will use events to request the data
+        move just tell it what plugin to call
+        """
+        if event.buttons() != QtCore.Qt.LeftButton:
+            event.ignore()
+            return
+        if not self.selectedItems():
+            event.ignore()
+            return
+        drag = QtGui.QDrag(self)
+        mime_data = QtCore.QMimeData()
+        drag.setMimeData(mime_data)
+        mime_data.setText(self.mime_data_text)
+        drag.exec(QtCore.Qt.CopyAction)
+
+    def dragEnterEvent(self, event):
+        """
+        When something is dragged into this object, check if you should be able to drop it in here.
+        """
+        if event.mimeData().hasUrls():
+            event.accept()
+        else:
+            event.ignore()
+
+    def dragMoveEvent(self, event):
+        """
+        Make an object droppable, and set it to copy the contents of the object, not move it.
+        """
+        if event.mimeData().hasUrls():
+            event.setDropAction(QtCore.Qt.CopyAction)
+            event.accept()
+        else:
+            event.ignore()
+
+    def dropEvent(self, event):
+        """
+        Receive drop event check if it is a file and process it if it is.
+
+        :param event:  Handle of the event pint passed
+        """
+        if event.mimeData().hasUrls():
+            event.setDropAction(QtCore.Qt.CopyAction)
+            event.accept()
+            files = []
+            for url in event.mimeData().urls():
+                local_file = os.path.normpath(url.toLocalFile())
+                if os.path.isfile(local_file):
+                    files.append(local_file)
+                elif os.path.isdir(local_file):
+                    listing = os.listdir(local_file)
+                    for file in listing:
+                        files.append(os.path.join(local_file, file))
+            Registry().execute('%s_dnd' % self.mime_data_text, {'files': files, 'target': self.itemAt(event.pos())})
+        else:
+            event.ignore()

=== added file 'openlp/core/ui/lib/mediadockmanager.py'
--- openlp/core/ui/lib/mediadockmanager.py	1970-01-01 00:00:00 +0000
+++ openlp/core/ui/lib/mediadockmanager.py	2016-04-22 19:42:45 +0000
@@ -0,0 +1,71 @@
+# -*- coding: utf-8 -*-
+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
+
+###############################################################################
+# OpenLP - Open Source Lyrics Projection                                      #
+# --------------------------------------------------------------------------- #
+# Copyright (c) 2008-2016 OpenLP Developers                                   #
+# --------------------------------------------------------------------------- #
+# 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                          #
+###############################################################################
+"""
+The media manager dock.
+"""
+import logging
+
+from openlp.core.lib import StringContent
+
+log = logging.getLogger(__name__)
+
+
+class MediaDockManager(object):
+    """
+    Provide a repository for MediaManagerItems
+    """
+    def __init__(self, media_dock):
+        """
+        Initialise the media dock
+        """
+        self.media_dock = media_dock
+
+    def add_item_to_dock(self, media_item):
+        """
+        Add a MediaManagerItem to the dock
+        If the item has been added before, it's silently skipped
+
+        :param media_item: The item to add to the dock
+        """
+        visible_title = media_item.plugin.get_string(StringContent.VisibleName)
+        log.debug('Inserting %s dock' % visible_title['title'])
+        match = False
+        for dock_index in range(self.media_dock.count()):
+            if self.media_dock.widget(dock_index).settings_section == media_item.plugin.name:
+                match = True
+                break
+        if not match:
+            self.media_dock.addItem(media_item, visible_title['title'])
+
+    def remove_dock(self, media_item):
+        """
+        Removes a MediaManagerItem from the dock
+
+        :param media_item: The item to add to the dock
+        """
+        visible_title = media_item.plugin.get_string(StringContent.VisibleName)
+        log.debug('remove %s dock' % visible_title['title'])
+        for dock_index in range(self.media_dock.count()):
+            if self.media_dock.widget(dock_index):
+                if self.media_dock.widget(dock_index).settings_section == media_item.plugin.name:
+                    self.media_dock.widget(dock_index).setVisible(False)
+                    self.media_dock.removeItem(dock_index)

=== added file 'openlp/core/ui/lib/toolbar.py'
--- openlp/core/ui/lib/toolbar.py	1970-01-01 00:00:00 +0000
+++ openlp/core/ui/lib/toolbar.py	2016-04-22 19:42:45 +0000
@@ -0,0 +1,90 @@
+# -*- coding: utf-8 -*-
+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
+
+###############################################################################
+# OpenLP - Open Source Lyrics Projection                                      #
+# --------------------------------------------------------------------------- #
+# Copyright (c) 2008-2016 OpenLP Developers                                   #
+# --------------------------------------------------------------------------- #
+# 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 common toolbar handling for OpenLP
+"""
+import logging
+
+from PyQt5 import QtCore, QtWidgets
+
+from openlp.core.lib.ui import create_widget_action
+
+log = logging.getLogger(__name__)
+
+
+class OpenLPToolbar(QtWidgets.QToolBar):
+    """
+    Lots of toolbars around the place, so it makes sense to have a common way to manage them. This is the base toolbar
+    class.
+    """
+    def __init__(self, parent):
+        """
+        Initialise the toolbar.
+        """
+        super(OpenLPToolbar, self).__init__(parent)
+        # useful to be able to reuse button icons...
+        self.setIconSize(QtCore.QSize(20, 20))
+        self.actions = {}
+        log.debug('Init done for %s' % parent.__class__.__name__)
+
+    def add_toolbar_action(self, name, **kwargs):
+        """
+        A method to help developers easily add a button to the toolbar. A new QAction is created by calling
+        ``create_action()``. The action is added to the toolbar and the toolbar is set as parent. For more details
+        please look at openlp.core.lib.ui.create_action()
+        """
+        action = create_widget_action(self, name, **kwargs)
+        self.actions[name] = action
+        return action
+
+    def add_toolbar_widget(self, widget):
+        """
+        Add a widget and store it's handle under the widgets object name.
+        """
+        action = self.addWidget(widget)
+        self.actions[widget.objectName()] = action
+
+    def set_widget_visible(self, widgets, visible=True):
+        """
+        Set the visibility for a widget or a list of widgets.
+
+        :param widgets: A list of string with widget object names.
+        :param visible: The new state as bool.
+        """
+        for handle in widgets:
+            if handle in self.actions:
+                self.actions[handle].setVisible(visible)
+            else:
+                log.warning('No handle "%s" in actions list.', str(handle))
+
+    def set_widget_enabled(self, widgets, enabled=True):
+        """
+        Set the enabled state for a widget or a list of widgets.
+
+        :param widgets: A list of string with widget object names.
+        :param enabled: The new state as bool.
+        """
+        for handle in widgets:
+            if handle in self.actions:
+                self.actions[handle].setEnabled(enabled)
+            else:
+                log.warning('No handle "%s" in actions list.', str(handle))

=== added file 'openlp/core/ui/lib/treewidgetwithdnd.py'
--- openlp/core/ui/lib/treewidgetwithdnd.py	1970-01-01 00:00:00 +0000
+++ openlp/core/ui/lib/treewidgetwithdnd.py	2016-04-22 19:42:45 +0000
@@ -0,0 +1,139 @@
+# -*- coding: utf-8 -*-
+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
+
+###############################################################################
+# OpenLP - Open Source Lyrics Projection                                      #
+# --------------------------------------------------------------------------- #
+# Copyright (c) 2008-2016 OpenLP Developers                                   #
+# --------------------------------------------------------------------------- #
+# 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                          #
+###############################################################################
+"""
+Extend QTreeWidget to handle drag and drop functionality
+"""
+import os
+
+from PyQt5 import QtCore, QtGui, QtWidgets
+
+from openlp.core.common import Registry
+
+
+class TreeWidgetWithDnD(QtWidgets.QTreeWidget):
+    """
+    Provide a tree widget to store objects and handle drag and drop events
+    """
+    def __init__(self, parent=None, name=''):
+        """
+        Initialise the tree widget
+        """
+        super(TreeWidgetWithDnD, self).__init__(parent)
+        self.mime_data_text = name
+        self.allow_internal_dnd = False
+        self.header().close()
+        self.default_indentation = self.indentation()
+        self.setIndentation(0)
+        self.setAnimated(True)
+
+    def activateDnD(self):
+        """
+        Activate DnD of widget
+        """
+        self.setAcceptDrops(True)
+        self.setDragDropMode(QtWidgets.QAbstractItemView.DragDrop)
+        Registry().register_function(('%s_dnd' % self.mime_data_text), self.parent().load_file)
+        Registry().register_function(('%s_dnd_internal' % self.mime_data_text), self.parent().dnd_move_internal)
+
+    def mouseMoveEvent(self, event):
+        """
+        Drag and drop event does not care what data is selected as the recipient will use events to request the data
+        move just tell it what plugin to call
+
+        :param event: The event that occurred
+        """
+        if event.buttons() != QtCore.Qt.LeftButton:
+            event.ignore()
+            return
+        if not self.selectedItems():
+            event.ignore()
+            return
+        drag = QtGui.QDrag(self)
+        mime_data = QtCore.QMimeData()
+        drag.setMimeData(mime_data)
+        mime_data.setText(self.mime_data_text)
+        drag.exec(QtCore.Qt.CopyAction)
+
+    def dragEnterEvent(self, event):
+        """
+        Receive drag enter event, check if it is a file or internal object and allow it if it is.
+
+        :param event:  The event that occurred
+        """
+        if event.mimeData().hasUrls():
+            event.accept()
+        elif self.allow_internal_dnd:
+            event.accept()
+        else:
+            event.ignore()
+
+    def dragMoveEvent(self, event):
+        """
+        Receive drag move event, check if it is a file or internal object and allow it if it is.
+
+        :param event: The event that occurred
+        """
+        QtWidgets.QTreeWidget.dragMoveEvent(self, event)
+        if event.mimeData().hasUrls():
+            event.setDropAction(QtCore.Qt.CopyAction)
+            event.accept()
+        elif self.allow_internal_dnd:
+            event.setDropAction(QtCore.Qt.CopyAction)
+            event.accept()
+        else:
+            event.ignore()
+
+    def dropEvent(self, event):
+        """
+        Receive drop event, check if it is a file or internal object and process it if it is.
+
+        :param event: Handle of the event pint passed
+        """
+        if event.mimeData().hasUrls():
+            event.setDropAction(QtCore.Qt.CopyAction)
+            event.accept()
+            files = []
+            for url in event.mimeData().urls():
+                local_file = url.toLocalFile()
+                if os.path.isfile(local_file):
+                    files.append(local_file)
+                elif os.path.isdir(local_file):
+                    listing = os.listdir(local_file)
+                    for file_name in listing:
+                        files.append(os.path.join(local_file, file_name))
+            Registry().execute('%s_dnd' % self.mime_data_text, {'files': files, 'target': self.itemAt(event.pos())})
+        elif self.allow_internal_dnd:
+            event.setDropAction(QtCore.Qt.CopyAction)
+            event.accept()
+            Registry().execute('%s_dnd_internal' % self.mime_data_text, self.itemAt(event.pos()))
+        else:
+            event.ignore()
+
+    # Convenience methods for emulating a QListWidget. This helps keeping MediaManagerItem simple.
+    def addItem(self, item):
+        self.addTopLevelItem(item)
+
+    def count(self):
+        return self.topLevelItemCount()
+
+    def item(self, index):
+        return self.topLevelItem(index)

=== added file 'openlp/core/ui/lib/wizard.py'
--- openlp/core/ui/lib/wizard.py	1970-01-01 00:00:00 +0000
+++ openlp/core/ui/lib/wizard.py	2016-04-22 19:42:45 +0000
@@ -0,0 +1,308 @@
+# -*- coding: utf-8 -*-
+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
+
+###############################################################################
+# OpenLP - Open Source Lyrics Projection                                      #
+# --------------------------------------------------------------------------- #
+# Copyright (c) 2008-2016 OpenLP Developers                                   #
+# --------------------------------------------------------------------------- #
+# 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                          #
+###############################################################################
+"""
+The :mod:``wizard`` module provides generic wizard tools for OpenLP.
+"""
+import logging
+import os
+
+from PyQt5 import QtGui, QtWidgets
+
+from openlp.core.common import Registry, RegistryProperties, Settings, UiStrings, translate, is_macosx
+from openlp.core.lib import build_icon
+from openlp.core.lib.ui import add_welcome_page
+
+log = logging.getLogger(__name__)
+
+
+class WizardStrings(object):
+    """
+    Provide standard strings for wizards to use.
+    """
+    # Applications/Formats we import from or export to. These get used in
+    # multiple places but do not need translating unless you find evidence of
+    # the writers translating their own product name.
+    CSV = 'CSV'
+    OS = 'OpenSong'
+    OSIS = 'OSIS'
+    ZEF = 'Zefania'
+    # These strings should need a good reason to be retranslated elsewhere.
+    FinishedImport = translate('OpenLP.Ui', 'Finished import.')
+    FormatLabel = translate('OpenLP.Ui', 'Format:')
+    HeaderStyle = '<span style="font-size:14pt; font-weight:600;">%s</span>'
+    Importing = translate('OpenLP.Ui', 'Importing')
+    ImportingType = translate('OpenLP.Ui', 'Importing "%s"...')
+    ImportSelect = translate('OpenLP.Ui', 'Select Import Source')
+    ImportSelectLong = translate('OpenLP.Ui', 'Select the import format and the location to import from.')
+    OpenTypeFile = translate('OpenLP.Ui', 'Open %s File')
+    OpenTypeFolder = translate('OpenLP.Ui', 'Open %s Folder')
+    PercentSymbolFormat = translate('OpenLP.Ui', '%p%')
+    Ready = translate('OpenLP.Ui', 'Ready.')
+    StartingImport = translate('OpenLP.Ui', 'Starting import...')
+    YouSpecifyFile = translate('OpenLP.Ui', 'You need to specify one %s file to import from.',
+                               'A file type e.g. OpenSong')
+    YouSpecifyFiles = translate('OpenLP.Ui', 'You need to specify at least one %s file to import from.',
+                                'A file type e.g. OpenSong')
+    YouSpecifyFolder = translate('OpenLP.Ui', 'You need to specify one %s folder to import from.',
+                                 'A song format e.g. PowerSong')
+
+
+class OpenLPWizard(QtWidgets.QWizard, RegistryProperties):
+    """
+    Generic OpenLP wizard to provide generic functionality and a unified look
+    and feel.
+
+    ``parent``
+        The QWidget-derived parent of the wizard.
+
+    ``plugin``
+        Plugin this wizard is part of. The plugin will be saved in the "plugin" variable.
+        The plugin will also be used as basis for the file dialog methods this class provides.
+
+    ``name``
+        The object name this wizard should have.
+
+    ``image``
+        The image to display on the "welcome" page of the wizard. Should be 163x350.
+
+    ``add_progress_page``
+        Whether to add a progress page with a progressbar at the end of the wizard.
+    """
+    def __init__(self, parent, plugin, name, image, add_progress_page=True):
+        """
+        Constructor
+        """
+        super(OpenLPWizard, self).__init__(parent)
+        self.plugin = plugin
+        self.with_progress_page = add_progress_page
+        self.setObjectName(name)
+        self.open_icon = build_icon(':/general/general_open.png')
+        self.delete_icon = build_icon(':/general/general_delete.png')
+        self.finish_button = self.button(QtWidgets.QWizard.FinishButton)
+        self.cancel_button = self.button(QtWidgets.QWizard.CancelButton)
+        self.setupUi(image)
+        self.register_fields()
+        self.custom_init()
+        self.custom_signals()
+        self.currentIdChanged.connect(self.on_current_id_changed)
+        if self.with_progress_page:
+            self.error_copy_to_button.clicked.connect(self.on_error_copy_to_button_clicked)
+            self.error_save_to_button.clicked.connect(self.on_error_save_to_button_clicked)
+
+    def setupUi(self, image):
+        """
+        Set up the wizard UI.
+        :param image: path to start up image
+        """
+        self.setWindowIcon(build_icon(u':/icon/openlp-logo.svg'))
+        self.setModal(True)
+        self.setOptions(QtWidgets.QWizard.IndependentPages |
+                        QtWidgets.QWizard.NoBackButtonOnStartPage | QtWidgets.QWizard.NoBackButtonOnLastPage)
+        if is_macosx():
+            self.setPixmap(QtWidgets.QWizard.BackgroundPixmap, QtGui.QPixmap(':/wizards/openlp-osx-wizard.png'))
+        else:
+            self.setWizardStyle(QtWidgets.QWizard.ModernStyle)
+        add_welcome_page(self, image)
+        self.add_custom_pages()
+        if self.with_progress_page:
+            self.add_progress_page()
+        self.retranslateUi()
+
+    def register_fields(self):
+        """
+        Hook method for wizards to register any fields they need.
+        """
+        pass
+
+    def custom_init(self):
+        """
+        Hook method for custom initialisation
+        """
+        pass
+
+    def custom_signals(self):
+        """
+        Hook method for adding custom signals
+        """
+        pass
+
+    def add_custom_pages(self):
+        """
+        Hook method for wizards to add extra pages
+        """
+        pass
+
+    def add_progress_page(self):
+        """
+        Add the progress page for the wizard. This page informs the user how
+        the wizard is progressing with its task.
+        """
+        self.progress_page = QtWidgets.QWizardPage()
+        self.progress_page.setObjectName('progress_page')
+        self.progress_layout = QtWidgets.QVBoxLayout(self.progress_page)
+        self.progress_layout.setContentsMargins(48, 48, 48, 48)
+        self.progress_layout.setObjectName('progress_layout')
+        self.progress_label = QtWidgets.QLabel(self.progress_page)
+        self.progress_label.setObjectName('progress_label')
+        self.progress_label.setWordWrap(True)
+        self.progress_layout.addWidget(self.progress_label)
+        self.progress_bar = QtWidgets.QProgressBar(self.progress_page)
+        self.progress_bar.setObjectName('progress_bar')
+        self.progress_layout.addWidget(self.progress_bar)
+        # Add a QTextEdit and a copy to file and copy to clipboard button to be
+        # able to provide feedback to the user. Hidden by default.
+        self.error_report_text_edit = QtWidgets.QTextEdit(self.progress_page)
+        self.error_report_text_edit.setObjectName('error_report_text_edit')
+        self.error_report_text_edit.setHidden(True)
+        self.error_report_text_edit.setReadOnly(True)
+        self.progress_layout.addWidget(self.error_report_text_edit)
+        self.error_button_layout = QtWidgets.QHBoxLayout()
+        self.error_button_layout.setObjectName('error_button_layout')
+        spacer = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
+        self.error_button_layout.addItem(spacer)
+        self.error_copy_to_button = QtWidgets.QPushButton(self.progress_page)
+        self.error_copy_to_button.setObjectName('error_copy_to_button')
+        self.error_copy_to_button.setHidden(True)
+        self.error_copy_to_button.setIcon(build_icon(':/system/system_edit_copy.png'))
+        self.error_button_layout.addWidget(self.error_copy_to_button)
+        self.error_save_to_button = QtWidgets.QPushButton(self.progress_page)
+        self.error_save_to_button.setObjectName('error_save_to_button')
+        self.error_save_to_button.setHidden(True)
+        self.error_save_to_button.setIcon(build_icon(':/general/general_save.png'))
+        self.error_button_layout.addWidget(self.error_save_to_button)
+        self.progress_layout.addLayout(self.error_button_layout)
+        self.addPage(self.progress_page)
+
+    def exec(self):
+        """
+        Run the wizard.
+        """
+        self.set_defaults()
+        return QtWidgets.QWizard.exec(self)
+
+    def reject(self):
+        """
+        Stop the wizard on cancel button, close button or ESC key.
+        """
+        log.debug('Wizard cancelled by user.')
+        if self.with_progress_page and self.currentPage() == self.progress_page:
+            Registry().execute('openlp_stop_wizard')
+        self.done(QtWidgets.QDialog.Rejected)
+
+    def on_current_id_changed(self, page_id):
+        """
+        Perform necessary functions depending on which wizard page is active.
+        :param page_id: current page number
+        """
+        if self.with_progress_page and self.page(page_id) == self.progress_page:
+            self.pre_wizard()
+            self.perform_wizard()
+            self.post_wizard()
+        else:
+            self.custom_page_changed(page_id)
+
+    def custom_page_changed(self, page_id):
+        """
+        Called when changing to a page other than the progress page
+        :param page_id: current page number
+        """
+        pass
+
+    def on_error_copy_to_button_clicked(self):
+        """
+        Called when the ``error_copy_to_button`` has been clicked.
+        """
+        pass
+
+    def on_error_save_to_button_clicked(self):
+        """
+        Called when the ``error_save_to_button`` has been clicked.
+        """
+        pass
+
+    def increment_progress_bar(self, status_text, increment=1):
+        """
+        Update the wizard progress page.
+
+        :param status_text: Current status information to display.
+        :param increment: The value to increment the progress bar by.
+        """
+        log.debug('IncrementBar %s', status_text)
+        self.progress_label.setText(status_text)
+        if increment > 0:
+            self.progress_bar.setValue(self.progress_bar.value() + increment)
+        self.application.process_events()
+
+    def pre_wizard(self):
+        """
+        Prepare the UI for the import.
+        """
+        self.finish_button.setVisible(False)
+        self.progress_bar.setMinimum(0)
+        self.progress_bar.setMaximum(1188)
+        self.progress_bar.setValue(0)
+
+    def post_wizard(self):
+        """
+        Clean up the UI after the import has finished.
+        """
+        self.progress_bar.setValue(self.progress_bar.maximum())
+        self.finish_button.setVisible(True)
+        self.cancel_button.setVisible(False)
+        self.application.process_events()
+
+    def get_file_name(self, title, editbox, setting_name, filters=''):
+        """
+        Opens a QFileDialog and saves the filename to the given editbox.
+
+        :param title: The title of the dialog (unicode).
+        :param editbox:  An editbox (QLineEdit).
+        :param setting_name: The place where to save the last opened directory.
+        :param filters: The file extension filters. It should contain the file description
+            as well as the file extension. For example::
+
+                'OpenLP 2 Databases (*.sqlite)'
+        """
+        if filters:
+            filters += ';;'
+        filters += '%s (*)' % UiStrings().AllFiles
+        filename, filter_used = QtWidgets.QFileDialog.getOpenFileName(
+            self, title, os.path.dirname(Settings().value(self.plugin.settings_section + '/' + setting_name)),
+            filters)
+        if filename:
+            editbox.setText(filename)
+        Settings().setValue(self.plugin.settings_section + '/' + setting_name, filename)
+
+    def get_folder(self, title, editbox, setting_name):
+        """
+        Opens a QFileDialog and saves the selected folder to the given editbox.
+
+        :param title: The title of the dialog (unicode).
+        :param editbox: An editbox (QLineEdit).
+        :param setting_name: The place where to save the last opened directory.
+        """
+        folder = QtWidgets.QFileDialog.getExistingDirectory(
+            self, title, Settings().value(self.plugin.settings_section + '/' + setting_name),
+            QtWidgets.QFileDialog.ShowDirsOnly)
+        if folder:
+            editbox.setText(folder)
+        Settings().setValue(self.plugin.settings_section + '/' + setting_name, folder)

=== removed file 'openlp/core/ui/listpreviewwidget.py'
--- openlp/core/ui/listpreviewwidget.py	2016-04-16 16:35:08 +0000
+++ openlp/core/ui/listpreviewwidget.py	1970-01-01 00:00:00 +0000
@@ -1,226 +0,0 @@
-# -*- coding: utf-8 -*-
-# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
-
-###############################################################################
-# OpenLP - Open Source Lyrics Projection                                      #
-# --------------------------------------------------------------------------- #
-# Copyright (c) 2008-2016 OpenLP Developers                                   #
-# --------------------------------------------------------------------------- #
-# 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                          #
-###############################################################################
-"""
-The :mod:`listpreviewwidget` is a widget that lists the slides in the slide controller.
-It is based on a QTableWidget but represents its contents in list form.
-"""
-
-from PyQt5 import QtCore, QtGui, QtWidgets
-
-from openlp.core.common import RegistryProperties, Settings
-from openlp.core.lib import ImageSource, ServiceItem
-
-
-class ListPreviewWidget(QtWidgets.QTableWidget, RegistryProperties):
-    """
-    A special type of QTableWidget which lists the slides in the slide controller
-
-    :param parent:
-    :param screen_ratio:
-    """
-
-    def __init__(self, parent, screen_ratio):
-        """
-        Initializes the widget to default state.
-
-        An empty ``ServiceItem`` is used by default. replace_service_manager_item() needs to be called to make this
-        widget display something.
-        """
-        super(QtWidgets.QTableWidget, self).__init__(parent)
-        self._setup(screen_ratio)
-
-    def _setup(self, screen_ratio):
-        """
-        Set up the widget
-        """
-        self.setColumnCount(1)
-        self.horizontalHeader().setVisible(False)
-        self.setColumnWidth(0, self.parent().width())
-        self.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
-        self.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection)
-        self.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
-        self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
-        self.setAlternatingRowColors(True)
-        # Initialize variables.
-        self.service_item = ServiceItem()
-        self.screen_ratio = screen_ratio
-        # Connect signals
-        self.verticalHeader().sectionResized.connect(self.row_resized)
-
-    def resizeEvent(self, event):
-        """
-        Overloaded method from QTableWidget. Will recalculate the layout.
-        """
-        self.__recalculate_layout()
-
-    def __recalculate_layout(self):
-        """
-        Recalculates the layout of the table widget. It will set height and width
-        of the table cells. QTableWidget does not adapt the cells to the widget size on its own.
-        """
-        self.setColumnWidth(0, self.viewport().width())
-        if self.service_item:
-            # Sort out songs, bibles, etc.
-            if self.service_item.is_text():
-                self.resizeRowsToContents()
-            # Sort out image heights.
-            else:
-                height = self.viewport().width() // self.screen_ratio
-                max_img_row_height = Settings().value('advanced/slide max height')
-                # Adjust for row height cap if in use.
-                if isinstance(max_img_row_height, int) and max_img_row_height > 0 and height > max_img_row_height:
-                    height = max_img_row_height
-                # Apply new height to slides
-                for frame_number in range(len(self.service_item.get_frames())):
-                    self.setRowHeight(frame_number, height)
-
-    def row_resized(self, row, old_height, new_height):
-        """
-        Will scale non-image slides.
-        """
-        # Only for non-text slides when row height cap in use
-        max_img_row_height = Settings().value('advanced/slide max height')
-        if self.service_item.is_text() or not isinstance(max_img_row_height, int) or max_img_row_height <= 0:
-            return
-        # Get and validate label widget containing slide & adjust max width
-        try:
-            self.cellWidget(row, 0).children()[1].setMaximumWidth(new_height * self.screen_ratio)
-        except:
-            return
-
-    def screen_size_changed(self, screen_ratio):
-        """
-        This method is called whenever the live screen size changes, which then makes a layout recalculation necessary
-
-        :param screen_ratio: The new screen ratio
-        """
-        self.screen_ratio = screen_ratio
-        self.__recalculate_layout()
-
-    def replace_service_item(self, service_item, width, slide_number):
-        """
-        Replace the current preview items with the ones in service_item and display the given slide
-
-        :param service_item: The service item to insert
-        :param width: The width of the column
-        :param slide_number: The slide number to pre-select
-        """
-        self.service_item = service_item
-        self.setRowCount(0)
-        self.clear()
-        self.setColumnWidth(0, width)
-        row = 0
-        text = []
-        for frame_number, frame in enumerate(self.service_item.get_frames()):
-            self.setRowCount(self.slide_count() + 1)
-            item = QtWidgets.QTableWidgetItem()
-            slide_height = 0
-            if self.service_item.is_text():
-                if frame['verseTag']:
-                    # These tags are already translated.
-                    verse_def = frame['verseTag']
-                    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
-                else:
-                    row += 1
-                item.setText(frame['text'])
-            else:
-                label = QtWidgets.QLabel()
-                label.setContentsMargins(4, 4, 4, 4)
-                if self.service_item.is_media():
-                    label.setAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter)
-                else:
-                    label.setScaledContents(True)
-                if self.service_item.is_command():
-                    pixmap = QtGui.QPixmap(frame['image'])
-                    pixmap.setDevicePixelRatio(label.devicePixelRatio())
-                    label.setPixmap(pixmap)
-                else:
-                    image = self.image_manager.get_image(frame['path'], ImageSource.ImagePlugin)
-                    pixmap = QtGui.QPixmap.fromImage(image)
-                    pixmap.setDevicePixelRatio(label.devicePixelRatio())
-                    label.setPixmap(pixmap)
-                slide_height = width // self.screen_ratio
-                # Setup and validate row height cap if in use.
-                max_img_row_height = Settings().value('advanced/slide max height')
-                if isinstance(max_img_row_height, int) and max_img_row_height > 0:
-                    if slide_height > max_img_row_height:
-                        slide_height = max_img_row_height
-                    label.setMaximumWidth(max_img_row_height * self.screen_ratio)
-                    label.resize(max_img_row_height * self.screen_ratio, max_img_row_height)
-                    # Build widget with stretch padding
-                    container = QtWidgets.QWidget()
-                    hbox = QtWidgets.QHBoxLayout()
-                    hbox.setContentsMargins(0, 0, 0, 0)
-                    hbox.addWidget(label, stretch=1)
-                    hbox.addStretch(0)
-                    container.setLayout(hbox)
-                    # Add to table
-                    self.setCellWidget(frame_number, 0, container)
-                else:
-                    # Add to table
-                    self.setCellWidget(frame_number, 0, label)
-                row += 1
-            text.append(str(row))
-            self.setItem(frame_number, 0, item)
-            if slide_height:
-                self.setRowHeight(frame_number, slide_height)
-        self.setVerticalHeaderLabels(text)
-        if self.service_item.is_text():
-            self.resizeRowsToContents()
-        self.setColumnWidth(0, self.viewport().width())
-        self.change_slide(slide_number)
-
-    def change_slide(self, slide):
-        """
-        Switches to the given row.
-        """
-        # Retrieve setting
-        autoscrolling = Settings().value('advanced/autoscrolling')
-        # Check if auto-scroll disabled (None) and validate value as dict containing 'dist' and 'pos'
-        # 'dist' represents the slide to scroll to relative to the new slide (-1 = previous, 0 = current, 1 = next)
-        # 'pos' represents the vert position of of the slide (0 = in view, 1 = top, 2 = middle, 3 = bottom)
-        if not (isinstance(autoscrolling, dict) and 'dist' in autoscrolling and 'pos' in autoscrolling and
-                isinstance(autoscrolling['dist'], int) and isinstance(autoscrolling['pos'], int)):
-            return
-        # prevent scrolling past list bounds
-        scroll_to_slide = slide + autoscrolling['dist']
-        if scroll_to_slide < 0:
-            scroll_to_slide = 0
-        if scroll_to_slide >= self.slide_count():
-            scroll_to_slide = self.slide_count() - 1
-        # Scroll to item if possible.
-        self.scrollToItem(self.item(scroll_to_slide, 0), autoscrolling['pos'])
-        self.selectRow(slide)
-
-    def current_slide_number(self):
-        """
-        Returns the position of the currently active item. Will return -1 if the widget is empty.
-        """
-        return super(ListPreviewWidget, self).currentRow()
-
-    def slide_count(self):
-        """
-        Returns the number of slides this widget holds.
-        """
-        return super(ListPreviewWidget, self).rowCount()

=== modified file 'openlp/core/ui/mainwindow.py'
--- openlp/core/ui/mainwindow.py	2016-04-16 13:58:28 +0000
+++ openlp/core/ui/mainwindow.py	2016-04-22 19:42:45 +0000
@@ -38,15 +38,17 @@
     check_directory_exists, translate, is_win, is_macosx, add_actions
 from openlp.core.common.actions import ActionList, CategoryOrder
 from openlp.core.common.versionchecker import get_application_version
-from openlp.core.lib import Renderer, OpenLPDockWidget, PluginManager, ImageManager, PluginStatus, ScreenList, \
-    build_icon
+from openlp.core.lib import Renderer, PluginManager, ImageManager, PluginStatus, ScreenList, build_icon
 from openlp.core.lib.ui import UiStrings, create_action
 from openlp.core.ui import AboutForm, SettingsForm, ServiceManager, ThemeManager, LiveController, PluginForm, \
-    MediaDockManager, ShortcutListForm, FormattingTagForm, PreviewController
+    ShortcutListForm, FormattingTagForm, PreviewController
 from openlp.core.ui.firsttimeform import FirstTimeForm
 from openlp.core.ui.media import MediaController
 from openlp.core.ui.printserviceform import PrintServiceForm
 from openlp.core.ui.projector.manager import ProjectorManager
+from openlp.core.ui.lib.toolbar import OpenLPToolbar
+from openlp.core.ui.lib.dockwidget import OpenLPDockWidget
+from openlp.core.ui.lib.mediadockmanager import MediaDockManager
 
 log = logging.getLogger(__name__)
 

=== modified file 'openlp/core/ui/media/mediacontroller.py'
--- openlp/core/ui/media/mediacontroller.py	2016-04-13 18:38:49 +0000
+++ openlp/core/ui/media/mediacontroller.py	2016-04-22 19:42:45 +0000
@@ -29,14 +29,16 @@
 from PyQt5 import QtCore, QtWidgets
 
 from openlp.core.common import OpenLPMixin, Registry, RegistryMixin, RegistryProperties, Settings, UiStrings, translate
-from openlp.core.lib import OpenLPToolbar, ItemCapabilities
+from openlp.core.lib import ItemCapabilities
 from openlp.core.lib.ui import critical_error_message_box
+from openlp.core.common import AppLocation
+from openlp.core.ui import DisplayControllerType
+from openlp.core.ui.media.vendor.mediainfoWrapper import MediaInfoWrapper
+from openlp.core.ui.media.mediaplayer import MediaPlayer
 from openlp.core.ui.media import MediaState, MediaInfo, MediaType, get_media_players, set_media_players,\
     parse_optical_path
-from openlp.core.ui.media.vendor.mediainfoWrapper import MediaInfoWrapper
-from openlp.core.ui.media.mediaplayer import MediaPlayer
-from openlp.core.common import AppLocation
-from openlp.core.ui import DisplayControllerType
+from openlp.core.ui.lib.toolbar import OpenLPToolbar
+from openlp.core.ui.lib.dockwidget import OpenLPDockWidget
 
 log = logging.getLogger(__name__)
 

=== modified file 'openlp/core/ui/media/playertab.py'
--- openlp/core/ui/media/playertab.py	2016-02-21 09:23:09 +0000
+++ openlp/core/ui/media/playertab.py	2016-04-22 19:42:45 +0000
@@ -26,9 +26,10 @@
 from PyQt5 import QtCore, QtWidgets
 
 from openlp.core.common import Registry, Settings, UiStrings, translate
-from openlp.core.lib import ColorButton, SettingsTab
+from openlp.core.lib import SettingsTab
 from openlp.core.lib.ui import create_button
 from openlp.core.ui.media import get_media_players, set_media_players
+from openlp.core.ui.lib.colorbutton import ColorButton
 
 
 class MediaQCheckBox(QtWidgets.QCheckBox):

=== removed file 'openlp/core/ui/mediadockmanager.py'
--- openlp/core/ui/mediadockmanager.py	2016-01-10 18:01:36 +0000
+++ openlp/core/ui/mediadockmanager.py	1970-01-01 00:00:00 +0000
@@ -1,71 +0,0 @@
-# -*- coding: utf-8 -*-
-# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
-
-###############################################################################
-# OpenLP - Open Source Lyrics Projection                                      #
-# --------------------------------------------------------------------------- #
-# Copyright (c) 2008-2016 OpenLP Developers                                   #
-# --------------------------------------------------------------------------- #
-# 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                          #
-###############################################################################
-"""
-The media manager dock.
-"""
-import logging
-
-from openlp.core.lib import StringContent
-
-log = logging.getLogger(__name__)
-
-
-class MediaDockManager(object):
-    """
-    Provide a repository for MediaManagerItems
-    """
-    def __init__(self, media_dock):
-        """
-        Initialise the media dock
-        """
-        self.media_dock = media_dock
-
-    def add_item_to_dock(self, media_item):
-        """
-        Add a MediaManagerItem to the dock
-        If the item has been added before, it's silently skipped
-
-        :param media_item: The item to add to the dock
-        """
-        visible_title = media_item.plugin.get_string(StringContent.VisibleName)
-        log.debug('Inserting %s dock' % visible_title['title'])
-        match = False
-        for dock_index in range(self.media_dock.count()):
-            if self.media_dock.widget(dock_index).settings_section == media_item.plugin.name:
-                match = True
-                break
-        if not match:
-            self.media_dock.addItem(media_item, visible_title['title'])
-
-    def remove_dock(self, media_item):
-        """
-        Removes a MediaManagerItem from the dock
-
-        :param media_item: The item to add to the dock
-        """
-        visible_title = media_item.plugin.get_string(StringContent.VisibleName)
-        log.debug('remove %s dock' % visible_title['title'])
-        for dock_index in range(self.media_dock.count()):
-            if self.media_dock.widget(dock_index):
-                if self.media_dock.widget(dock_index).settings_section == media_item.plugin.name:
-                    self.media_dock.widget(dock_index).setVisible(False)
-                    self.media_dock.removeItem(dock_index)

=== modified file 'openlp/core/ui/projector/manager.py'
--- openlp/core/ui/projector/manager.py	2016-03-03 17:53:29 +0000
+++ openlp/core/ui/projector/manager.py	2016-04-22 19:42:45 +0000
@@ -35,7 +35,7 @@
 
 from openlp.core.common import RegistryProperties, Settings, OpenLPMixin, \
     RegistryMixin, translate
-from openlp.core.lib import OpenLPToolbar
+from openlp.core.ui.lib import OpenLPToolbar
 from openlp.core.lib.ui import create_widget_action
 from openlp.core.lib.projector import DialogSourceStyle
 from openlp.core.lib.projector.constants import *

=== modified file 'openlp/core/ui/servicemanager.py'
--- openlp/core/ui/servicemanager.py	2016-04-05 17:10:51 +0000
+++ openlp/core/ui/servicemanager.py	2016-04-22 19:42:45 +0000
@@ -35,9 +35,10 @@
 from openlp.core.common import Registry, RegistryProperties, AppLocation, Settings, ThemeLevel, OpenLPMixin, \
     RegistryMixin, check_directory_exists, UiStrings, translate, split_filename, delete_file
 from openlp.core.common.actions import ActionList, CategoryOrder
-from openlp.core.lib import OpenLPToolbar, ServiceItem, ItemCapabilities, PluginStatus, build_icon
+from openlp.core.lib import ServiceItem, ItemCapabilities, PluginStatus, build_icon
 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.lib import OpenLPToolbar
 from openlp.core.common.languagemanager import format_time
 
 

=== modified file 'openlp/core/ui/slidecontroller.py'
--- openlp/core/ui/slidecontroller.py	2016-04-06 05:08:44 +0000
+++ openlp/core/ui/slidecontroller.py	2016-04-22 19:42:45 +0000
@@ -33,11 +33,14 @@
 from openlp.core.common import Registry, RegistryProperties, Settings, SlideLimits, UiStrings, translate, \
     RegistryMixin, OpenLPMixin
 from openlp.core.common.actions import ActionList, CategoryOrder
-from openlp.core.lib import OpenLPToolbar, ItemCapabilities, ServiceItem, ImageSource, ServiceItemAction, \
-    ScreenList, build_icon, build_html
+from openlp.core.lib import ItemCapabilities, ServiceItem, ImageSource, ServiceItemAction, ScreenList, build_icon, \
+    build_html
 from openlp.core.lib.ui import create_action
+from openlp.core.ui.lib.toolbar import OpenLPToolbar
+from openlp.core.ui.lib.dockwidget import OpenLPDockWidget
+from openlp.core.ui.lib.listpreviewwidget import ListPreviewWidget
 from openlp.core.ui import HideMode, MainDisplay, Display, DisplayControllerType
-from openlp.core.ui.listpreviewwidget import ListPreviewWidget
+
 
 # Threshold which has to be trespassed to toggle.
 HIDE_MENU_THRESHOLD = 27

=== modified file 'openlp/core/ui/themeform.py'
--- openlp/core/ui/themeform.py	2016-04-06 05:08:44 +0000
+++ openlp/core/ui/themeform.py	2016-04-22 19:42:45 +0000
@@ -31,6 +31,7 @@
 from openlp.core.lib.theme import BackgroundType, BackgroundGradientType
 from openlp.core.lib.ui import critical_error_message_box
 from openlp.core.ui import ThemeLayoutForm
+from openlp.core.ui.lib.colorbutton import ColorButton
 from .themewizard import Ui_ThemeWizard
 
 log = logging.getLogger(__name__)

=== modified file 'openlp/core/ui/thememanager.py'
--- openlp/core/ui/thememanager.py	2016-04-10 20:24:07 +0000
+++ openlp/core/ui/thememanager.py	2016-04-22 19:42:45 +0000
@@ -31,11 +31,12 @@
 
 from openlp.core.common import Registry, RegistryProperties, AppLocation, Settings, OpenLPMixin, RegistryMixin, \
     check_directory_exists, UiStrings, translate, is_win, get_filesystem_encoding, delete_file
-from openlp.core.lib import FileDialog, ImageSource, OpenLPToolbar, ValidationError, get_text_file_string, build_icon, \
+from openlp.core.lib import FileDialog, ImageSource, ValidationError, get_text_file_string, build_icon, \
     check_item_selected, create_thumb, validate_thumb
 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
+from openlp.core.ui.lib import OpenLPToolbar
 from openlp.core.common.languagemanager import get_locale_key
 
 

=== modified file 'openlp/core/ui/themewizard.py'
--- openlp/core/ui/themewizard.py	2015-12-31 22:46:06 +0000
+++ openlp/core/ui/themewizard.py	2016-04-22 19:42:45 +0000
@@ -25,9 +25,10 @@
 from PyQt5 import QtCore, QtGui, QtWidgets
 
 from openlp.core.common import UiStrings, translate, is_macosx
-from openlp.core.lib import build_icon, ColorButton
+from openlp.core.lib import build_icon
 from openlp.core.lib.theme import HorizontalType, BackgroundType, BackgroundGradientType
 from openlp.core.lib.ui import add_welcome_page, create_valign_selection_widgets
+from openlp.core.ui.lib.colorbutton import ColorButton
 
 
 class Ui_ThemeWizard(object):

=== removed file 'openlp/core/ui/wizard.py'
--- openlp/core/ui/wizard.py	2016-02-27 14:03:52 +0000
+++ openlp/core/ui/wizard.py	1970-01-01 00:00:00 +0000
@@ -1,308 +0,0 @@
-# -*- coding: utf-8 -*-
-# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
-
-###############################################################################
-# OpenLP - Open Source Lyrics Projection                                      #
-# --------------------------------------------------------------------------- #
-# Copyright (c) 2008-2016 OpenLP Developers                                   #
-# --------------------------------------------------------------------------- #
-# 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                          #
-###############################################################################
-"""
-The :mod:``wizard`` module provides generic wizard tools for OpenLP.
-"""
-import logging
-import os
-
-from PyQt5 import QtGui, QtWidgets
-
-from openlp.core.common import Registry, RegistryProperties, Settings, UiStrings, translate, is_macosx
-from openlp.core.lib import build_icon
-from openlp.core.lib.ui import add_welcome_page
-
-log = logging.getLogger(__name__)
-
-
-class WizardStrings(object):
-    """
-    Provide standard strings for wizards to use.
-    """
-    # Applications/Formats we import from or export to. These get used in
-    # multiple places but do not need translating unless you find evidence of
-    # the writers translating their own product name.
-    CSV = 'CSV'
-    OS = 'OpenSong'
-    OSIS = 'OSIS'
-    ZEF = 'Zefania'
-    # These strings should need a good reason to be retranslated elsewhere.
-    FinishedImport = translate('OpenLP.Ui', 'Finished import.')
-    FormatLabel = translate('OpenLP.Ui', 'Format:')
-    HeaderStyle = '<span style="font-size:14pt; font-weight:600;">%s</span>'
-    Importing = translate('OpenLP.Ui', 'Importing')
-    ImportingType = translate('OpenLP.Ui', 'Importing "%s"...')
-    ImportSelect = translate('OpenLP.Ui', 'Select Import Source')
-    ImportSelectLong = translate('OpenLP.Ui', 'Select the import format and the location to import from.')
-    OpenTypeFile = translate('OpenLP.Ui', 'Open %s File')
-    OpenTypeFolder = translate('OpenLP.Ui', 'Open %s Folder')
-    PercentSymbolFormat = translate('OpenLP.Ui', '%p%')
-    Ready = translate('OpenLP.Ui', 'Ready.')
-    StartingImport = translate('OpenLP.Ui', 'Starting import...')
-    YouSpecifyFile = translate('OpenLP.Ui', 'You need to specify one %s file to import from.',
-                               'A file type e.g. OpenSong')
-    YouSpecifyFiles = translate('OpenLP.Ui', 'You need to specify at least one %s file to import from.',
-                                'A file type e.g. OpenSong')
-    YouSpecifyFolder = translate('OpenLP.Ui', 'You need to specify one %s folder to import from.',
-                                 'A song format e.g. PowerSong')
-
-
-class OpenLPWizard(QtWidgets.QWizard, RegistryProperties):
-    """
-    Generic OpenLP wizard to provide generic functionality and a unified look
-    and feel.
-
-    ``parent``
-        The QWidget-derived parent of the wizard.
-
-    ``plugin``
-        Plugin this wizard is part of. The plugin will be saved in the "plugin" variable.
-        The plugin will also be used as basis for the file dialog methods this class provides.
-
-    ``name``
-        The object name this wizard should have.
-
-    ``image``
-        The image to display on the "welcome" page of the wizard. Should be 163x350.
-
-    ``add_progress_page``
-        Whether to add a progress page with a progressbar at the end of the wizard.
-    """
-    def __init__(self, parent, plugin, name, image, add_progress_page=True):
-        """
-        Constructor
-        """
-        super(OpenLPWizard, self).__init__(parent)
-        self.plugin = plugin
-        self.with_progress_page = add_progress_page
-        self.setObjectName(name)
-        self.open_icon = build_icon(':/general/general_open.png')
-        self.delete_icon = build_icon(':/general/general_delete.png')
-        self.finish_button = self.button(QtWidgets.QWizard.FinishButton)
-        self.cancel_button = self.button(QtWidgets.QWizard.CancelButton)
-        self.setupUi(image)
-        self.register_fields()
-        self.custom_init()
-        self.custom_signals()
-        self.currentIdChanged.connect(self.on_current_id_changed)
-        if self.with_progress_page:
-            self.error_copy_to_button.clicked.connect(self.on_error_copy_to_button_clicked)
-            self.error_save_to_button.clicked.connect(self.on_error_save_to_button_clicked)
-
-    def setupUi(self, image):
-        """
-        Set up the wizard UI.
-        :param image: path to start up image
-        """
-        self.setWindowIcon(build_icon(u':/icon/openlp-logo.svg'))
-        self.setModal(True)
-        self.setOptions(QtWidgets.QWizard.IndependentPages |
-                        QtWidgets.QWizard.NoBackButtonOnStartPage | QtWidgets.QWizard.NoBackButtonOnLastPage)
-        if is_macosx():
-            self.setPixmap(QtWidgets.QWizard.BackgroundPixmap, QtGui.QPixmap(':/wizards/openlp-osx-wizard.png'))
-        else:
-            self.setWizardStyle(QtWidgets.QWizard.ModernStyle)
-        add_welcome_page(self, image)
-        self.add_custom_pages()
-        if self.with_progress_page:
-            self.add_progress_page()
-        self.retranslateUi()
-
-    def register_fields(self):
-        """
-        Hook method for wizards to register any fields they need.
-        """
-        pass
-
-    def custom_init(self):
-        """
-        Hook method for custom initialisation
-        """
-        pass
-
-    def custom_signals(self):
-        """
-        Hook method for adding custom signals
-        """
-        pass
-
-    def add_custom_pages(self):
-        """
-        Hook method for wizards to add extra pages
-        """
-        pass
-
-    def add_progress_page(self):
-        """
-        Add the progress page for the wizard. This page informs the user how
-        the wizard is progressing with its task.
-        """
-        self.progress_page = QtWidgets.QWizardPage()
-        self.progress_page.setObjectName('progress_page')
-        self.progress_layout = QtWidgets.QVBoxLayout(self.progress_page)
-        self.progress_layout.setContentsMargins(48, 48, 48, 48)
-        self.progress_layout.setObjectName('progress_layout')
-        self.progress_label = QtWidgets.QLabel(self.progress_page)
-        self.progress_label.setObjectName('progress_label')
-        self.progress_label.setWordWrap(True)
-        self.progress_layout.addWidget(self.progress_label)
-        self.progress_bar = QtWidgets.QProgressBar(self.progress_page)
-        self.progress_bar.setObjectName('progress_bar')
-        self.progress_layout.addWidget(self.progress_bar)
-        # Add a QTextEdit and a copy to file and copy to clipboard button to be
-        # able to provide feedback to the user. Hidden by default.
-        self.error_report_text_edit = QtWidgets.QTextEdit(self.progress_page)
-        self.error_report_text_edit.setObjectName('error_report_text_edit')
-        self.error_report_text_edit.setHidden(True)
-        self.error_report_text_edit.setReadOnly(True)
-        self.progress_layout.addWidget(self.error_report_text_edit)
-        self.error_button_layout = QtWidgets.QHBoxLayout()
-        self.error_button_layout.setObjectName('error_button_layout')
-        spacer = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
-        self.error_button_layout.addItem(spacer)
-        self.error_copy_to_button = QtWidgets.QPushButton(self.progress_page)
-        self.error_copy_to_button.setObjectName('error_copy_to_button')
-        self.error_copy_to_button.setHidden(True)
-        self.error_copy_to_button.setIcon(build_icon(':/system/system_edit_copy.png'))
-        self.error_button_layout.addWidget(self.error_copy_to_button)
-        self.error_save_to_button = QtWidgets.QPushButton(self.progress_page)
-        self.error_save_to_button.setObjectName('error_save_to_button')
-        self.error_save_to_button.setHidden(True)
-        self.error_save_to_button.setIcon(build_icon(':/general/general_save.png'))
-        self.error_button_layout.addWidget(self.error_save_to_button)
-        self.progress_layout.addLayout(self.error_button_layout)
-        self.addPage(self.progress_page)
-
-    def exec(self):
-        """
-        Run the wizard.
-        """
-        self.set_defaults()
-        return QtWidgets.QWizard.exec(self)
-
-    def reject(self):
-        """
-        Stop the wizard on cancel button, close button or ESC key.
-        """
-        log.debug('Wizard cancelled by user.')
-        if self.with_progress_page and self.currentPage() == self.progress_page:
-            Registry().execute('openlp_stop_wizard')
-        self.done(QtWidgets.QDialog.Rejected)
-
-    def on_current_id_changed(self, page_id):
-        """
-        Perform necessary functions depending on which wizard page is active.
-        :param page_id: current page number
-        """
-        if self.with_progress_page and self.page(page_id) == self.progress_page:
-            self.pre_wizard()
-            self.perform_wizard()
-            self.post_wizard()
-        else:
-            self.custom_page_changed(page_id)
-
-    def custom_page_changed(self, page_id):
-        """
-        Called when changing to a page other than the progress page
-        :param page_id: current page number
-        """
-        pass
-
-    def on_error_copy_to_button_clicked(self):
-        """
-        Called when the ``error_copy_to_button`` has been clicked.
-        """
-        pass
-
-    def on_error_save_to_button_clicked(self):
-        """
-        Called when the ``error_save_to_button`` has been clicked.
-        """
-        pass
-
-    def increment_progress_bar(self, status_text, increment=1):
-        """
-        Update the wizard progress page.
-
-        :param status_text: Current status information to display.
-        :param increment: The value to increment the progress bar by.
-        """
-        log.debug('IncrementBar %s', status_text)
-        self.progress_label.setText(status_text)
-        if increment > 0:
-            self.progress_bar.setValue(self.progress_bar.value() + increment)
-        self.application.process_events()
-
-    def pre_wizard(self):
-        """
-        Prepare the UI for the import.
-        """
-        self.finish_button.setVisible(False)
-        self.progress_bar.setMinimum(0)
-        self.progress_bar.setMaximum(1188)
-        self.progress_bar.setValue(0)
-
-    def post_wizard(self):
-        """
-        Clean up the UI after the import has finished.
-        """
-        self.progress_bar.setValue(self.progress_bar.maximum())
-        self.finish_button.setVisible(True)
-        self.cancel_button.setVisible(False)
-        self.application.process_events()
-
-    def get_file_name(self, title, editbox, setting_name, filters=''):
-        """
-        Opens a QFileDialog and saves the filename to the given editbox.
-
-        :param title: The title of the dialog (unicode).
-        :param editbox:  An editbox (QLineEdit).
-        :param setting_name: The place where to save the last opened directory.
-        :param filters: The file extension filters. It should contain the file description
-            as well as the file extension. For example::
-
-                'OpenLP 2 Databases (*.sqlite)'
-        """
-        if filters:
-            filters += ';;'
-        filters += '%s (*)' % UiStrings().AllFiles
-        filename, filter_used = QtWidgets.QFileDialog.getOpenFileName(
-            self, title, os.path.dirname(Settings().value(self.plugin.settings_section + '/' + setting_name)),
-            filters)
-        if filename:
-            editbox.setText(filename)
-        Settings().setValue(self.plugin.settings_section + '/' + setting_name, filename)
-
-    def get_folder(self, title, editbox, setting_name):
-        """
-        Opens a QFileDialog and saves the selected folder to the given editbox.
-
-        :param title: The title of the dialog (unicode).
-        :param editbox: An editbox (QLineEdit).
-        :param setting_name: The place where to save the last opened directory.
-        """
-        folder = QtWidgets.QFileDialog.getExistingDirectory(
-            self, title, Settings().value(self.plugin.settings_section + '/' + setting_name),
-            QtWidgets.QFileDialog.ShowDirsOnly)
-        if folder:
-            editbox.setText(folder)
-        Settings().setValue(self.plugin.settings_section + '/' + setting_name, folder)

=== modified file 'openlp/plugins/alerts/lib/alertstab.py'
--- openlp/plugins/alerts/lib/alertstab.py	2015-12-31 22:46:06 +0000
+++ openlp/plugins/alerts/lib/alertstab.py	2016-04-22 19:42:45 +0000
@@ -23,8 +23,9 @@
 from PyQt5 import QtGui, QtWidgets
 
 from openlp.core.common import Settings, UiStrings, translate
-from openlp.core.lib import ColorButton, SettingsTab
+from openlp.core.lib import SettingsTab
 from openlp.core.lib.ui import create_valign_selection_widgets
+from openlp.core.ui.lib.colorbutton import ColorButton
 
 
 class AlertsTab(SettingsTab):

=== modified file 'openlp/plugins/bibles/forms/bibleimportform.py'
--- openlp/plugins/bibles/forms/bibleimportform.py	2016-04-05 17:30:20 +0000
+++ openlp/plugins/bibles/forms/bibleimportform.py	2016-04-22 19:42:45 +0000
@@ -31,7 +31,7 @@
 from openlp.core.common import AppLocation, Settings, UiStrings, translate, clean_filename
 from openlp.core.lib.db import delete_database
 from openlp.core.lib.ui import critical_error_message_box
-from openlp.core.ui.wizard import OpenLPWizard, WizardStrings
+from openlp.core.ui.lib.wizard import OpenLPWizard, WizardStrings
 from openlp.core.common.languagemanager import get_locale_key
 from openlp.plugins.bibles.lib.manager import BibleFormat
 from openlp.plugins.bibles.lib.db import BiblesResourcesDB, clean_filename

=== modified file 'openlp/plugins/bibles/forms/bibleupgradeform.py'
--- openlp/plugins/bibles/forms/bibleupgradeform.py	2016-04-05 17:10:51 +0000
+++ openlp/plugins/bibles/forms/bibleupgradeform.py	2016-04-22 19:42:45 +0000
@@ -32,7 +32,7 @@
 from openlp.core.common import Registry, AppLocation, UiStrings, Settings, check_directory_exists, translate, \
     delete_file
 from openlp.core.lib.ui import critical_error_message_box
-from openlp.core.ui.wizard import OpenLPWizard, WizardStrings
+from openlp.core.ui.lib.wizard import OpenLPWizard, WizardStrings
 from openlp.plugins.bibles.lib.db import BibleDB, BibleMeta, OldBibleDB, BiblesResourcesDB
 from openlp.plugins.bibles.lib.http import BSExtract, BGExtract, CWExtract
 

=== modified file 'openlp/plugins/images/lib/imagetab.py'
--- openlp/plugins/images/lib/imagetab.py	2015-12-31 22:46:06 +0000
+++ openlp/plugins/images/lib/imagetab.py	2016-04-22 19:42:45 +0000
@@ -23,7 +23,8 @@
 from PyQt5 import QtWidgets
 
 from openlp.core.common import Settings, UiStrings, translate
-from openlp.core.lib import ColorButton, SettingsTab
+from openlp.core.lib import SettingsTab
+from openlp.core.ui.lib.colorbutton import ColorButton
 
 
 class ImageTab(SettingsTab):

=== modified file 'openlp/plugins/images/lib/mediaitem.py'
--- openlp/plugins/images/lib/mediaitem.py	2016-04-13 17:40:05 +0000
+++ openlp/plugins/images/lib/mediaitem.py	2016-04-22 19:42:45 +0000
@@ -27,9 +27,10 @@
 
 from openlp.core.common import Registry, AppLocation, Settings, UiStrings, check_directory_exists, translate, \
     delete_file, get_images_filter
-from openlp.core.lib import ItemCapabilities, MediaManagerItem, ServiceItemContext, StringContent, TreeWidgetWithDnD,\
-    build_icon, check_item_selected, create_thumb, validate_thumb
+from openlp.core.lib import ItemCapabilities, MediaManagerItem, ServiceItemContext, StringContent, build_icon, \
+    check_item_selected, create_thumb, validate_thumb
 from openlp.core.lib.ui import create_widget_action, critical_error_message_box
+from openlp.core.ui.lib.treewidgetwithdnd import TreeWidgetWithDnD
 from openlp.core.common.languagemanager import get_locale_key
 from openlp.plugins.images.forms import AddGroupForm, ChooseGroupForm
 from openlp.plugins.images.lib.db import ImageFilenames, ImageGroups

=== modified file 'openlp/plugins/songs/forms/duplicatesongremovalform.py'
--- openlp/plugins/songs/forms/duplicatesongremovalform.py	2015-12-31 22:46:06 +0000
+++ openlp/plugins/songs/forms/duplicatesongremovalform.py	2016-04-22 19:42:45 +0000
@@ -30,7 +30,7 @@
 from PyQt5 import QtCore, QtWidgets
 
 from openlp.core.common import Registry, RegistryProperties, translate
-from openlp.core.ui.wizard import OpenLPWizard, WizardStrings
+from openlp.core.ui.lib.wizard import OpenLPWizard, WizardStrings
 from openlp.plugins.songs.lib import delete_song
 from openlp.plugins.songs.lib.db import Song, MediaFile
 from openlp.plugins.songs.forms.songreviewwidget import SongReviewWidget

=== modified file 'openlp/plugins/songs/forms/songexportform.py'
--- openlp/plugins/songs/forms/songexportform.py	2016-01-09 22:35:56 +0000
+++ openlp/plugins/songs/forms/songexportform.py	2016-04-22 19:42:45 +0000
@@ -30,7 +30,7 @@
 from openlp.core.common import Registry, UiStrings, translate
 from openlp.core.lib import create_separated_list, build_icon
 from openlp.core.lib.ui import critical_error_message_box
-from openlp.core.ui.wizard import OpenLPWizard, WizardStrings
+from openlp.core.ui.lib.wizard import OpenLPWizard, WizardStrings
 from openlp.plugins.songs.lib.db import Song
 from openlp.plugins.songs.lib.openlyricsexport import OpenLyricsExport
 

=== modified file 'openlp/plugins/songs/forms/songimportform.py'
--- openlp/plugins/songs/forms/songimportform.py	2015-12-31 22:46:06 +0000
+++ openlp/plugins/songs/forms/songimportform.py	2016-04-22 19:42:45 +0000
@@ -31,7 +31,7 @@
 from openlp.core.common import RegistryProperties, Settings, UiStrings, translate
 from openlp.core.lib import FileDialog
 from openlp.core.lib.ui import critical_error_message_box
-from openlp.core.ui.wizard import OpenLPWizard, WizardStrings
+from openlp.core.ui.lib.wizard import OpenLPWizard, WizardStrings
 from openlp.plugins.songs.lib.importer import SongFormat, SongFormatSelect
 
 log = logging.getLogger(__name__)

=== modified file 'openlp/plugins/songs/lib/importer.py'
--- openlp/plugins/songs/lib/importer.py	2016-03-31 05:39:13 +0000
+++ openlp/plugins/songs/lib/importer.py	2016-04-22 19:42:45 +0000
@@ -26,7 +26,7 @@
 import logging
 
 from openlp.core.common import translate, UiStrings, is_win
-from openlp.core.ui.wizard import WizardStrings
+from openlp.core.ui.lib.wizard import WizardStrings
 from .importers.opensong import OpenSongImport
 from .importers.easyslides import EasySlidesImport
 from .importers.openlp import OpenLPSongImport

=== modified file 'openlp/plugins/songs/lib/importers/foilpresenter.py'
--- openlp/plugins/songs/lib/importers/foilpresenter.py	2016-01-09 16:26:14 +0000
+++ openlp/plugins/songs/lib/importers/foilpresenter.py	2016-04-22 19:42:45 +0000
@@ -90,7 +90,7 @@
 from lxml import etree, objectify
 
 from openlp.core.lib import translate
-from openlp.core.ui.wizard import WizardStrings
+from openlp.core.ui.lib.wizard import WizardStrings
 from openlp.plugins.songs.lib import clean_song, VerseType
 from openlp.plugins.songs.lib.importers.songimport import SongImport
 from openlp.plugins.songs.lib.db import Author, Book, Song, Topic

=== modified file 'openlp/plugins/songs/lib/importers/openlp.py'
--- openlp/plugins/songs/lib/importers/openlp.py	2015-12-31 22:46:06 +0000
+++ openlp/plugins/songs/lib/importers/openlp.py	2016-04-22 19:42:45 +0000
@@ -31,7 +31,7 @@
 
 from openlp.core.common import translate
 from openlp.core.lib.db import BaseModel
-from openlp.core.ui.wizard import WizardStrings
+from openlp.core.ui.lib.wizard import WizardStrings
 from openlp.plugins.songs.lib import clean_song
 from openlp.plugins.songs.lib.db import Author, Book, Song, Topic, MediaFile
 from .songimport import SongImport

=== modified file 'openlp/plugins/songs/lib/importers/openlyrics.py'
--- openlp/plugins/songs/lib/importers/openlyrics.py	2015-12-31 22:46:06 +0000
+++ openlp/plugins/songs/lib/importers/openlyrics.py	2016-04-22 19:42:45 +0000
@@ -29,7 +29,7 @@
 
 from lxml import etree
 
-from openlp.core.ui.wizard import WizardStrings
+from openlp.core.ui.lib.wizard import WizardStrings
 from openlp.plugins.songs.lib.importers.songimport import SongImport
 from openlp.plugins.songs.lib.ui import SongStrings
 from openlp.plugins.songs.lib.openlyricsxml import OpenLyrics, OpenLyricsError

=== modified file 'openlp/plugins/songs/lib/importers/powerpraise.py'
--- openlp/plugins/songs/lib/importers/powerpraise.py	2015-12-31 22:46:06 +0000
+++ openlp/plugins/songs/lib/importers/powerpraise.py	2016-04-22 19:42:45 +0000
@@ -27,7 +27,7 @@
 import os
 from lxml import objectify
 
-from openlp.core.ui.wizard import WizardStrings
+from openlp.core.ui.lib.wizard import WizardStrings
 from .songimport import SongImport
 
 

=== modified file 'openlp/plugins/songs/lib/importers/presentationmanager.py'
--- openlp/plugins/songs/lib/importers/presentationmanager.py	2015-12-31 22:46:06 +0000
+++ openlp/plugins/songs/lib/importers/presentationmanager.py	2016-04-22 19:42:45 +0000
@@ -26,10 +26,11 @@
 
 import os
 import re
+
 import chardet
 from lxml import objectify, etree
 
-from openlp.core.ui.wizard import WizardStrings
+from openlp.core.ui.lib.wizard import WizardStrings
 from .songimport import SongImport
 
 

=== modified file 'openlp/plugins/songs/lib/importers/propresenter.py'
--- openlp/plugins/songs/lib/importers/propresenter.py	2016-03-31 05:39:13 +0000
+++ openlp/plugins/songs/lib/importers/propresenter.py	2016-04-22 19:42:45 +0000
@@ -29,7 +29,7 @@
 import logging
 from lxml import objectify
 
-from openlp.core.ui.wizard import WizardStrings
+from openlp.core.ui.lib.wizard import WizardStrings
 from openlp.plugins.songs.lib import strip_rtf
 from .songimport import SongImport
 

=== modified file 'openlp/plugins/songs/lib/importers/songimport.py'
--- openlp/plugins/songs/lib/importers/songimport.py	2016-03-19 20:28:32 +0000
+++ openlp/plugins/songs/lib/importers/songimport.py	2016-04-22 19:42:45 +0000
@@ -28,7 +28,7 @@
 from PyQt5 import QtCore
 
 from openlp.core.common import Registry, AppLocation, check_directory_exists, translate
-from openlp.core.ui.wizard import WizardStrings
+from openlp.core.ui.lib.wizard import WizardStrings
 from openlp.plugins.songs.lib import clean_song, VerseType
 from openlp.plugins.songs.lib.db import Song, Author, Topic, Book, MediaFile
 from openlp.plugins.songs.lib.ui import SongStrings

=== modified file 'openlp/plugins/songs/lib/importers/songshowplus.py'
--- openlp/plugins/songs/lib/importers/songshowplus.py	2015-12-31 22:46:06 +0000
+++ openlp/plugins/songs/lib/importers/songshowplus.py	2016-04-22 19:42:45 +0000
@@ -29,7 +29,7 @@
 import re
 import struct
 
-from openlp.core.ui.wizard import WizardStrings
+from openlp.core.ui.lib.wizard import WizardStrings
 from openlp.plugins.songs.lib import VerseType, retrieve_windows_encoding
 from openlp.plugins.songs.lib.importers.songimport import SongImport
 

=== modified file 'tests/functional/openlp_core_common/test_projector_utilities.py'
--- tests/functional/openlp_core_common/test_projector_utilities.py	2016-01-09 17:21:20 +0000
+++ tests/functional/openlp_core_common/test_projector_utilities.py	2016-04-22 19:42:45 +0000
@@ -23,13 +23,12 @@
 Package to test the openlp.core.ui.projector.networkutils package.
 """
 
-import os
-
 from unittest import TestCase
 
 from openlp.core.common import verify_ip_address, md5_hash, qmd5_hash
 
 from tests.resources.projector.data import TEST_PIN, TEST_SALT, TEST_HASH
+
 salt = TEST_SALT
 pin = TEST_PIN
 test_hash = TEST_HASH

=== removed file 'tests/functional/openlp_core_lib/test_color_button.py'
--- tests/functional/openlp_core_lib/test_color_button.py	2015-12-31 22:46:06 +0000
+++ tests/functional/openlp_core_lib/test_color_button.py	1970-01-01 00:00:00 +0000
@@ -1,199 +0,0 @@
-# -*- coding: utf-8 -*-
-# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
-
-###############################################################################
-# OpenLP - Open Source Lyrics Projection                                      #
-# --------------------------------------------------------------------------- #
-# Copyright (c) 2008-2016 OpenLP Developers                                   #
-# --------------------------------------------------------------------------- #
-# 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 module contains tests for the openlp.core.lib.filedialog module
-"""
-from unittest import TestCase
-
-from openlp.core.lib.colorbutton import ColorButton
-from tests.functional import MagicMock, call, patch
-
-
-class TestColorDialog(TestCase):
-    """
-    Test the :class:`~openlp.core.lib.colorbutton.ColorButton` class
-    """
-    def setUp(self):
-        self.change_color_patcher = patch('openlp.core.lib.colorbutton.ColorButton.change_color')
-        self.clicked_patcher = patch('openlp.core.lib.colorbutton.ColorButton.clicked')
-        self.color_changed_patcher = patch('openlp.core.lib.colorbutton.ColorButton.colorChanged')
-        self.qt_gui_patcher = patch('openlp.core.lib.colorbutton.QtWidgets')
-        self.translate_patcher = patch('openlp.core.lib.colorbutton.translate', **{'return_value': 'Tool Tip Text'})
-        self.addCleanup(self.change_color_patcher.stop)
-        self.addCleanup(self.clicked_patcher.stop)
-        self.addCleanup(self.color_changed_patcher.stop)
-        self.addCleanup(self.qt_gui_patcher.stop)
-        self.addCleanup(self.translate_patcher.stop)
-        self.mocked_change_color = self.change_color_patcher.start()
-        self.mocked_clicked = self.clicked_patcher.start()
-        self.mocked_color_changed = self.color_changed_patcher.start()
-        self.mocked_qt_widgets = self.qt_gui_patcher.start()
-        self.mocked_translate = self.translate_patcher.start()
-
-    def constructor_test(self):
-        """
-        Test that constructing a ColorButton object works correctly
-        """
-
-        # GIVEN: The ColorButton class, a mocked change_color, setToolTip methods and clicked signal
-        with patch('openlp.core.lib.colorbutton.ColorButton.setToolTip') as mocked_set_tool_tip:
-
-            # WHEN: The ColorButton object is instantiated
-            widget = ColorButton()
-
-            # THEN: The widget __init__ method should have the correct properties and methods called
-            self.assertEqual(widget.parent, None,
-                             'The parent should be the same as the one that the class was instianted with')
-            self.mocked_change_color.assert_called_once_with('#ffffff')
-            mocked_set_tool_tip.assert_called_once_with('Tool Tip Text')
-            self.mocked_clicked.connect.assert_called_once_with(widget.on_clicked)
-
-    def change_color_test(self):
-        """
-        Test that change_color sets the new color and the stylesheet
-        """
-        self.change_color_patcher.stop()
-
-        # GIVEN: An instance of the ColorButton object, and a mocked out setStyleSheet
-        with patch('openlp.core.lib.colorbutton.ColorButton.setStyleSheet') as mocked_set_style_sheet:
-            widget = ColorButton()
-
-            # WHEN: Changing the color
-            widget.change_color('#000000')
-
-            # THEN: The _color attribute should be set to #000000 and setStyleSheet should have been called twice
-            self.assertEqual(widget._color, '#000000', '_color should have been set to #000000')
-            mocked_set_style_sheet.assert_has_calls(
-                [call('background-color: #ffffff'), call('background-color: #000000')])
-
-        self.mocked_change_color = self.change_color_patcher.start()
-
-    def color_test(self):
-        """
-        Test that the color property method returns the set color
-        """
-
-        # GIVEN: An instance of ColorButton, with a set _color attribute
-        widget = ColorButton()
-        widget._color = '#000000'
-
-        # WHEN: Accesing the color property
-        value = widget.color
-
-        # THEN: The value set in _color should be returned
-        self.assertEqual(value, '#000000', 'The value returned should be equal to the one we set')
-
-    def color_test(self):
-        """
-        Test that the color property method returns the set color
-        """
-
-        # GIVEN: An instance of ColorButton, with a set _color attribute
-        widget = ColorButton()
-        widget._color = '#000000'
-
-        # WHEN: Accesing the color property
-        value = widget.color
-
-        # THEN: The value set in _color should be returned
-        self.assertEqual(value, '#000000', 'The value returned should be equal to the one we set')
-
-    def color_setter_test(self):
-        """
-        Test that the color property setter method sets the color
-        """
-
-        # GIVEN: An instance of ColorButton, with a mocked __init__
-        with patch('openlp.core.lib.colorbutton.ColorButton.__init__', **{'return_value': None}):
-            widget = ColorButton()
-
-            # WHEN: Setting the color property
-            widget.color = '#000000'
-
-            # THEN: Then change_color should have been called with the value we set
-            self.mocked_change_color.assert_called_once_with('#000000')
-
-    def on_clicked_invalid_color_test(self):
-        """
-        Test the on_click method when an invalid color has been supplied
-        """
-
-        # GIVEN: An instance of ColorButton, and a set _color attribute
-        widget = ColorButton()
-        self.mocked_change_color.reset_mock()
-        self.mocked_color_changed.reset_mock()
-        widget._color = '#000000'
-
-        # WHEN: The on_clicked method is called, and the color is invalid
-        self.mocked_qt_widgets.QColorDialog.getColor.return_value = MagicMock(**{'isValid.return_value': False})
-        widget.on_clicked()
-
-        # THEN: change_color should not have been called and the colorChanged signal should not have been emitted
-        self.assertEqual(
-            self.mocked_change_color.call_count, 0, 'change_color should not have been called with an invalid color')
-        self.assertEqual(
-            self.mocked_color_changed.emit.call_count, 0,
-            'colorChange signal should not have been emitted with an invalid color')
-
-    def on_clicked_same_color_test(self):
-        """
-        Test the on_click method when a new color has not been chosen
-        """
-
-        # GIVEN: An instance of ColorButton, and a set _color attribute
-        widget = ColorButton()
-        self.mocked_change_color.reset_mock()
-        self.mocked_color_changed.reset_mock()
-        widget._color = '#000000'
-
-        # WHEN: The on_clicked method is called, and the color is valid, but the same as the existing color
-        self.mocked_qt_widgets.QColorDialog.getColor.return_value = MagicMock(
-            **{'isValid.return_value': True, 'name.return_value': '#000000'})
-        widget.on_clicked()
-
-        # THEN: change_color should not have been called and the colorChanged signal should not have been emitted
-        self.assertEqual(
-            self.mocked_change_color.call_count, 0,
-            'change_color should not have been called when the color has not changed')
-        self.assertEqual(
-            self.mocked_color_changed.emit.call_count, 0,
-            'colorChange signal should not have been emitted when the color has not changed')
-
-    def on_clicked_new_color_test(self):
-        """
-        Test the on_click method when a new color has been chosen and is valid
-        """
-
-        # GIVEN: An instance of ColorButton, and a set _color attribute
-        widget = ColorButton()
-        self.mocked_change_color.reset_mock()
-        self.mocked_color_changed.reset_mock()
-        widget._color = '#000000'
-
-        # WHEN: The on_clicked method is called, and the color is valid, and different to the existing color
-        self.mocked_qt_widgets.QColorDialog.getColor.return_value = MagicMock(
-            **{'isValid.return_value': True, 'name.return_value': '#ffffff'})
-        widget.on_clicked()
-
-        # THEN: change_color should have been called and the colorChanged signal should have been emitted
-        self.mocked_change_color.assert_called_once_with('#ffffff')
-        self.mocked_color_changed.emit.assert_called_once_with('#ffffff')

=== removed file 'tests/functional/openlp_core_ui/test_listpreviewwidget.py'
--- tests/functional/openlp_core_ui/test_listpreviewwidget.py	2016-04-16 16:35:08 +0000
+++ tests/functional/openlp_core_ui/test_listpreviewwidget.py	1970-01-01 00:00:00 +0000
@@ -1,377 +0,0 @@
-# -*- coding: utf-8 -*-
-# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
-
-###############################################################################
-# OpenLP - Open Source Lyrics Projection                                      #
-# --------------------------------------------------------------------------- #
-# Copyright (c) 2008-2016 OpenLP Developers                                   #
-# --------------------------------------------------------------------------- #
-# 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                          #
-###############################################################################
-"""
-Package to test the openlp.core.ui.listpreviewwidget package.
-"""
-from unittest import TestCase
-
-from openlp.core.common import Settings
-from openlp.core.ui.listpreviewwidget import ListPreviewWidget
-from openlp.core.lib import ServiceItem
-
-from tests.functional import MagicMock, patch, call
-
-
-class TestListPreviewWidget(TestCase):
-
-    def setUp(self):
-        """
-        Mock out stuff for all the tests
-        """
-        # Mock self.parent().width()
-        self.parent_patcher = patch('openlp.core.ui.listpreviewwidget.ListPreviewWidget.parent')
-        self.mocked_parent = self.parent_patcher.start()
-        self.mocked_parent.width.return_value = 100
-        self.addCleanup(self.parent_patcher.stop)
-
-        # Mock Settings().value()
-        self.Settings_patcher = patch('openlp.core.ui.listpreviewwidget.Settings')
-        self.mocked_Settings = self.Settings_patcher.start()
-        self.mocked_Settings_obj = MagicMock()
-        self.mocked_Settings_obj.value.return_value = None
-        self.mocked_Settings.return_value = self.mocked_Settings_obj
-        self.addCleanup(self.Settings_patcher.stop)
-
-        # Mock self.viewport().width()
-        self.viewport_patcher = patch('openlp.core.ui.listpreviewwidget.ListPreviewWidget.viewport')
-        self.mocked_viewport = self.viewport_patcher.start()
-        self.mocked_viewport_obj = MagicMock()
-        self.mocked_viewport_obj.width.return_value = 200
-        self.mocked_viewport.return_value = self.mocked_viewport_obj
-        self.addCleanup(self.viewport_patcher.stop)
-
-    def new_list_preview_widget_test(self):
-        """
-        Test that creating an instance of ListPreviewWidget works
-        """
-        # GIVEN: A ListPreviewWidget class
-
-        # WHEN: An object is created
-        list_preview_widget = ListPreviewWidget(None, 1)
-
-        # THEN: The object is not None, and the _setup() method was called.
-        self.assertIsNotNone(list_preview_widget, 'The ListPreviewWidget object should not be None')
-        self.assertEquals(list_preview_widget.screen_ratio, 1, 'Should not be called')
-
-    @patch(u'openlp.core.ui.listpreviewwidget.ListPreviewWidget.resizeRowsToContents')
-    @patch(u'openlp.core.ui.listpreviewwidget.ListPreviewWidget.setRowHeight')
-    def replace_recalculate_layout_test_text(self, mocked_setRowHeight, mocked_resizeRowsToContents):
-        """
-        Test if "Max height for non-text slides..." enabled, txt slides unchanged in replace_service_item & __recalc...
-        """
-        # GIVEN: A setting to adjust "Max height for non-text slides in slide controller",
-        #        a text ServiceItem and a ListPreviewWidget.
-
-        # Mock Settings().value('advanced/slide max height')
-        self.mocked_Settings_obj.value.return_value = 100
-        # Mock self.viewport().width()
-        self.mocked_viewport_obj.width.return_value = 200
-        # Mock text service item
-        service_item = MagicMock()
-        service_item.is_text.return_value = True
-        service_item.get_frames.return_value = [{'title': None, 'text': None, 'verseTag': None},
-                                                {'title': None, 'text': None, 'verseTag': None}]
-        # init ListPreviewWidget and load service item
-        list_preview_widget = ListPreviewWidget(None, 1)
-        list_preview_widget.replace_service_item(service_item, 200, 0)
-        # Change viewport width before forcing a resize
-        self.mocked_viewport_obj.width.return_value = 400
-
-        # WHEN: __recalculate_layout() is called (via resizeEvent)
-        list_preview_widget.resizeEvent(None)
-
-        # THEN: setRowHeight() should not be called, while resizeRowsToContents() should be called twice
-        #       (once each in __recalculate_layout and replace_service_item)
-        self.assertEquals(mocked_resizeRowsToContents.call_count, 2, 'Should be called')
-        self.assertEquals(mocked_setRowHeight.call_count, 0, 'Should not be called')
-
-    @patch(u'openlp.core.ui.listpreviewwidget.ListPreviewWidget.resizeRowsToContents')
-    @patch(u'openlp.core.ui.listpreviewwidget.ListPreviewWidget.setRowHeight')
-    def replace_recalculate_layout_test_img(self, mocked_setRowHeight, mocked_resizeRowsToContents):
-        """
-        Test if "Max height for non-text slides..." disabled, img slides unchanged in replace_service_item & __recalc...
-        """
-        # GIVEN: A setting to adjust "Max height for non-text slides in slide controller",
-        #        an image ServiceItem and a ListPreviewWidget.
-
-        # Mock Settings().value('advanced/slide max height')
-        self.mocked_Settings_obj.value.return_value = 0
-        # Mock self.viewport().width()
-        self.mocked_viewport_obj.width.return_value = 200
-        # Mock image service item
-        service_item = MagicMock()
-        service_item.is_text.return_value = False
-        service_item.get_frames.return_value = [{'title': None, 'path': None, 'image': None},
-                                                {'title': None, 'path': None, 'image': None}]
-        # init ListPreviewWidget and load service item
-        list_preview_widget = ListPreviewWidget(None, 1)
-        list_preview_widget.replace_service_item(service_item, 200, 0)
-        # Change viewport width before forcing a resize
-        self.mocked_viewport_obj.width.return_value = 400
-
-        # WHEN: __recalculate_layout() is called (via resizeEvent)
-        list_preview_widget.resizeEvent(None)
-        self.mocked_Settings_obj.value.return_value = None
-        list_preview_widget.resizeEvent(None)
-
-        # THEN: resizeRowsToContents() should not be called, while setRowHeight() should be called
-        #       twice for each slide.
-        self.assertEquals(mocked_resizeRowsToContents.call_count, 0, 'Should not be called')
-        self.assertEquals(mocked_setRowHeight.call_count, 6, 'Should be called 3 times for each slide')
-        calls = [call(0, 200), call(1, 200), call(0, 400), call(1, 400), call(0, 400), call(1, 400)]
-        mocked_setRowHeight.assert_has_calls(calls)
-
-    @patch(u'openlp.core.ui.listpreviewwidget.ListPreviewWidget.resizeRowsToContents')
-    @patch(u'openlp.core.ui.listpreviewwidget.ListPreviewWidget.setRowHeight')
-    def replace_recalculate_layout_test_img_max(self, mocked_setRowHeight, mocked_resizeRowsToContents):
-        """
-        Test if "Max height for non-text slides..." enabled, img slides resized in replace_service_item & __recalc...
-        """
-        # GIVEN: A setting to adjust "Max height for non-text slides in slide controller",
-        #        an image ServiceItem and a ListPreviewWidget.
-
-        # Mock Settings().value('advanced/slide max height')
-        self.mocked_Settings_obj.value.return_value = 100
-        # Mock self.viewport().width()
-        self.mocked_viewport_obj.width.return_value = 200
-        # Mock image service item
-        service_item = MagicMock()
-        service_item.is_text.return_value = False
-        service_item.get_frames.return_value = [{'title': None, 'path': None, 'image': None},
-                                                {'title': None, 'path': None, 'image': None}]
-        # init ListPreviewWidget and load service item
-        list_preview_widget = ListPreviewWidget(None, 1)
-        list_preview_widget.replace_service_item(service_item, 200, 0)
-        # Change viewport width before forcing a resize
-        self.mocked_viewport_obj.width.return_value = 400
-
-        # WHEN: __recalculate_layout() is called (via resizeEvent)
-        list_preview_widget.resizeEvent(None)
-
-        # THEN: resizeRowsToContents() should not be called, while setRowHeight() should be called
-        #       twice for each slide.
-        self.assertEquals(mocked_resizeRowsToContents.call_count, 0, 'Should not be called')
-        self.assertEquals(mocked_setRowHeight.call_count, 4, 'Should be called twice for each slide')
-        calls = [call(0, 100), call(1, 100), call(0, 100), call(1, 100)]
-        mocked_setRowHeight.assert_has_calls(calls)
-
-    @patch(u'openlp.core.ui.listpreviewwidget.ListPreviewWidget.resizeRowsToContents')
-    @patch(u'openlp.core.ui.listpreviewwidget.ListPreviewWidget.setRowHeight')
-    @patch(u'openlp.core.ui.listpreviewwidget.ListPreviewWidget.cellWidget')
-    def row_resized_test_text(self, mocked_cellWidget, mocked_setRowHeight, mocked_resizeRowsToContents):
-        """
-        Test if "Max height for non-text slides..." enabled, text-based slides not affected in row_resized.
-        """
-        # GIVEN: A setting to adjust "Max height for non-text slides in slide controller",
-        #        a text ServiceItem and a ListPreviewWidget.
-
-        # Mock Settings().value('advanced/slide max height')
-        self.mocked_Settings_obj.value.return_value = 100
-        # Mock self.viewport().width()
-        self.mocked_viewport_obj.width.return_value = 200
-        # Mock text service item
-        service_item = MagicMock()
-        service_item.is_text.return_value = True
-        service_item.get_frames.return_value = [{'title': None, 'text': None, 'verseTag': None},
-                                                {'title': None, 'text': None, 'verseTag': None}]
-        # Mock self.cellWidget().children().setMaximumWidth()
-        mocked_cellWidget_child = MagicMock()
-        mocked_cellWidget_obj = MagicMock()
-        mocked_cellWidget_obj.children.return_value = [None, mocked_cellWidget_child]
-        mocked_cellWidget.return_value = mocked_cellWidget_obj
-        # init ListPreviewWidget and load service item
-        list_preview_widget = ListPreviewWidget(None, 1)
-        list_preview_widget.replace_service_item(service_item, 200, 0)
-
-        # WHEN: row_resized() is called
-        list_preview_widget.row_resized(0, 100, 150)
-
-        # THEN: self.cellWidget(row, 0).children()[1].setMaximumWidth() should not be called
-        self.assertEquals(mocked_cellWidget_child.setMaximumWidth.call_count, 0, 'Should not be called')
-
-    @patch(u'openlp.core.ui.listpreviewwidget.ListPreviewWidget.resizeRowsToContents')
-    @patch(u'openlp.core.ui.listpreviewwidget.ListPreviewWidget.setRowHeight')
-    @patch(u'openlp.core.ui.listpreviewwidget.ListPreviewWidget.cellWidget')
-    def row_resized_test_img(self, mocked_cellWidget, mocked_setRowHeight, mocked_resizeRowsToContents):
-        """
-        Test if "Max height for non-text slides..." disabled, image-based slides not affected in row_resized.
-        """
-        # GIVEN: A setting to adjust "Max height for non-text slides in slide controller",
-        #        an image ServiceItem and a ListPreviewWidget.
-
-        # Mock Settings().value('advanced/slide max height')
-        self.mocked_Settings_obj.value.return_value = 0
-        # Mock self.viewport().width()
-        self.mocked_viewport_obj.width.return_value = 200
-        # Mock image service item
-        service_item = MagicMock()
-        service_item.is_text.return_value = False
-        service_item.get_frames.return_value = [{'title': None, 'path': None, 'image': None},
-                                                {'title': None, 'path': None, 'image': None}]
-        # Mock self.cellWidget().children().setMaximumWidth()
-        mocked_cellWidget_child = MagicMock()
-        mocked_cellWidget_obj = MagicMock()
-        mocked_cellWidget_obj.children.return_value = [None, mocked_cellWidget_child]
-        mocked_cellWidget.return_value = mocked_cellWidget_obj
-        # init ListPreviewWidget and load service item
-        list_preview_widget = ListPreviewWidget(None, 1)
-        list_preview_widget.replace_service_item(service_item, 200, 0)
-
-        # WHEN: row_resized() is called
-        list_preview_widget.row_resized(0, 100, 150)
-        self.mocked_Settings_obj.value.return_value = None
-        list_preview_widget.row_resized(0, 100, 150)
-
-        # THEN: self.cellWidget(row, 0).children()[1].setMaximumWidth() should not be called
-        self.assertEquals(mocked_cellWidget_child.setMaximumWidth.call_count, 0, 'Should not be called')
-
-    @patch(u'openlp.core.ui.listpreviewwidget.ListPreviewWidget.resizeRowsToContents')
-    @patch(u'openlp.core.ui.listpreviewwidget.ListPreviewWidget.setRowHeight')
-    @patch(u'openlp.core.ui.listpreviewwidget.ListPreviewWidget.cellWidget')
-    def row_resized_test_img_max(self, mocked_cellWidget, mocked_setRowHeight, mocked_resizeRowsToContents):
-        """
-        Test if "Max height for non-text slides..." enabled, image-based slides are scaled in row_resized.
-        """
-        # GIVEN: A setting to adjust "Max height for non-text slides in slide controller",
-        #        an image ServiceItem and a ListPreviewWidget.
-
-        # Mock Settings().value('advanced/slide max height')
-        self.mocked_Settings_obj.value.return_value = 100
-        # Mock self.viewport().width()
-        self.mocked_viewport_obj.width.return_value = 200
-        # Mock image service item
-        service_item = MagicMock()
-        service_item.is_text.return_value = False
-        service_item.get_frames.return_value = [{'title': None, 'path': None, 'image': None},
-                                                {'title': None, 'path': None, 'image': None}]
-        # Mock self.cellWidget().children().setMaximumWidth()
-        mocked_cellWidget_child = MagicMock()
-        mocked_cellWidget_obj = MagicMock()
-        mocked_cellWidget_obj.children.return_value = [None, mocked_cellWidget_child]
-        mocked_cellWidget.return_value = mocked_cellWidget_obj
-        # init ListPreviewWidget and load service item
-        list_preview_widget = ListPreviewWidget(None, 1)
-        list_preview_widget.replace_service_item(service_item, 200, 0)
-
-        # WHEN: row_resized() is called
-        list_preview_widget.row_resized(0, 100, 150)
-
-        # THEN: self.cellWidget(row, 0).children()[1].setMaximumWidth() should be called
-        mocked_cellWidget_child.setMaximumWidth.assert_called_once_with(150)
-
-    @patch(u'openlp.core.ui.listpreviewwidget.ListPreviewWidget.selectRow')
-    @patch(u'openlp.core.ui.listpreviewwidget.ListPreviewWidget.scrollToItem')
-    @patch(u'openlp.core.ui.listpreviewwidget.ListPreviewWidget.item')
-    @patch(u'openlp.core.ui.listpreviewwidget.ListPreviewWidget.slide_count')
-    def autoscroll_test_setting_invalid(self, mocked_slide_count, mocked_item, mocked_scrollToItem, mocked_selectRow):
-        """
-        Test if 'advanced/autoscrolling' setting None or invalid, that no autoscrolling occurs on change_slide().
-        """
-        # GIVEN: A setting for autoscrolling and a ListPreviewWidget.
-        # Mock Settings().value('advanced/autoscrolling')
-        self.mocked_Settings_obj.value.return_value = None
-        # Mocked returns
-        mocked_slide_count.return_value = 1
-        mocked_item.return_value = None
-        # init ListPreviewWidget and load service item
-        list_preview_widget = ListPreviewWidget(None, 1)
-
-        # WHEN: change_slide() is called
-        list_preview_widget.change_slide(0)
-        self.mocked_Settings_obj.value.return_value = 1
-        list_preview_widget.change_slide(0)
-        self.mocked_Settings_obj.value.return_value = {'fail': 1}
-        list_preview_widget.change_slide(0)
-        self.mocked_Settings_obj.value.return_value = {'dist': 1, 'fail': 1}
-        list_preview_widget.change_slide(0)
-        self.mocked_Settings_obj.value.return_value = {'dist': 'fail', 'pos': 1}
-        list_preview_widget.change_slide(0)
-        self.mocked_Settings_obj.value.return_value = {'dist': 1, 'pos': 'fail'}
-        list_preview_widget.change_slide(0)
-
-        # THEN: no further functions should be called
-        self.assertEquals(mocked_slide_count.call_count, 0, 'Should not be called')
-        self.assertEquals(mocked_scrollToItem.call_count, 0, 'Should not be called')
-        self.assertEquals(mocked_selectRow.call_count, 0, 'Should not be called')
-        self.assertEquals(mocked_item.call_count, 0, 'Should not be called')
-
-    @patch(u'openlp.core.ui.listpreviewwidget.ListPreviewWidget.selectRow')
-    @patch(u'openlp.core.ui.listpreviewwidget.ListPreviewWidget.scrollToItem')
-    @patch(u'openlp.core.ui.listpreviewwidget.ListPreviewWidget.item')
-    @patch(u'openlp.core.ui.listpreviewwidget.ListPreviewWidget.slide_count')
-    def autoscroll_test_dist_bounds(self, mocked_slide_count, mocked_item, mocked_scrollToItem, mocked_selectRow):
-        """
-        Test if 'advanced/autoscrolling' setting asks to scroll beyond list bounds, that it does not beyond.
-        """
-        # GIVEN: A setting for autoscrolling and a ListPreviewWidget.
-        # Mock Settings().value('advanced/autoscrolling')
-        self.mocked_Settings_obj.value.return_value = {'dist': -1, 'pos': 1}
-        # Mocked returns
-        mocked_slide_count.return_value = 1
-        mocked_item.return_value = None
-        # init ListPreviewWidget and load service item
-        list_preview_widget = ListPreviewWidget(None, 1)
-
-        # WHEN: change_slide() is called
-        list_preview_widget.change_slide(0)
-        self.mocked_Settings_obj.value.return_value = {'dist': 1, 'pos': 1}
-        list_preview_widget.change_slide(0)
-
-        # THEN: no further functions should be called
-        self.assertEquals(mocked_slide_count.call_count, 3, 'Should be called')
-        self.assertEquals(mocked_scrollToItem.call_count, 2, 'Should be called')
-        self.assertEquals(mocked_selectRow.call_count, 2, 'Should be called')
-        self.assertEquals(mocked_item.call_count, 2, 'Should be called')
-        calls = [call(0, 0), call(0, 0)]
-        mocked_item.assert_has_calls(calls)
-
-    @patch(u'openlp.core.ui.listpreviewwidget.ListPreviewWidget.selectRow')
-    @patch(u'openlp.core.ui.listpreviewwidget.ListPreviewWidget.scrollToItem')
-    @patch(u'openlp.core.ui.listpreviewwidget.ListPreviewWidget.item')
-    @patch(u'openlp.core.ui.listpreviewwidget.ListPreviewWidget.slide_count')
-    def autoscroll_test_normal(self, mocked_slide_count, mocked_item, mocked_scrollToItem, mocked_selectRow):
-        """
-        Test if 'advanced/autoscrolling' setting valid, autoscrolling called as expected.
-        """
-        # GIVEN: A setting for autoscrolling and a ListPreviewWidget.
-        # Mock Settings().value('advanced/autoscrolling')
-        self.mocked_Settings_obj.value.return_value = {'dist': -1, 'pos': 1}
-        # Mocked returns
-        mocked_slide_count.return_value = 3
-        mocked_item.return_value = None
-        # init ListPreviewWidget and load service item
-        list_preview_widget = ListPreviewWidget(None, 1)
-
-        # WHEN: change_slide() is called
-        list_preview_widget.change_slide(1)
-        self.mocked_Settings_obj.value.return_value = {'dist': 0, 'pos': 1}
-        list_preview_widget.change_slide(1)
-        self.mocked_Settings_obj.value.return_value = {'dist': 1, 'pos': 1}
-        list_preview_widget.change_slide(1)
-
-        # THEN: no further functions should be called
-        self.assertEquals(mocked_slide_count.call_count, 3, 'Should be called')
-        self.assertEquals(mocked_scrollToItem.call_count, 3, 'Should be called')
-        self.assertEquals(mocked_selectRow.call_count, 3, 'Should be called')
-        self.assertEquals(mocked_item.call_count, 3, 'Should be called')
-        calls = [call(0, 0), call(1, 0), call(2, 0)]
-        mocked_item.assert_has_calls(calls)

=== added directory 'tests/functional/openlp_core_ui_lib'
=== added file 'tests/functional/openlp_core_ui_lib/__init__.py'
=== added file 'tests/functional/openlp_core_ui_lib/test_color_button.py'
--- tests/functional/openlp_core_ui_lib/test_color_button.py	1970-01-01 00:00:00 +0000
+++ tests/functional/openlp_core_ui_lib/test_color_button.py	2016-04-22 19:42:45 +0000
@@ -0,0 +1,199 @@
+# -*- coding: utf-8 -*-
+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
+
+###############################################################################
+# OpenLP - Open Source Lyrics Projection                                      #
+# --------------------------------------------------------------------------- #
+# Copyright (c) 2008-2016 OpenLP Developers                                   #
+# --------------------------------------------------------------------------- #
+# 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 module contains tests for the openlp.core.lib.filedialog module
+"""
+from unittest import TestCase
+
+from openlp.core.ui.lib.colorbutton import ColorButton
+from tests.functional import MagicMock, call, patch
+
+
+class TestColorDialog(TestCase):
+    """
+    Test the :class:`~openlp.core.lib.colorbutton.ColorButton` class
+    """
+    def setUp(self):
+        self.change_color_patcher = patch('openlp.core.ui.lib.colorbutton.ColorButton.change_color')
+        self.clicked_patcher = patch('openlp.core.ui.lib.colorbutton.ColorButton.clicked')
+        self.color_changed_patcher = patch('openlp.core.ui.lib.colorbutton.ColorButton.colorChanged')
+        self.qt_gui_patcher = patch('openlp.core.ui.lib.colorbutton.QtWidgets')
+        self.translate_patcher = patch('openlp.core.ui.lib.colorbutton.translate', **{'return_value': 'Tool Tip Text'})
+        self.addCleanup(self.change_color_patcher.stop)
+        self.addCleanup(self.clicked_patcher.stop)
+        self.addCleanup(self.color_changed_patcher.stop)
+        self.addCleanup(self.qt_gui_patcher.stop)
+        self.addCleanup(self.translate_patcher.stop)
+        self.mocked_change_color = self.change_color_patcher.start()
+        self.mocked_clicked = self.clicked_patcher.start()
+        self.mocked_color_changed = self.color_changed_patcher.start()
+        self.mocked_qt_widgets = self.qt_gui_patcher.start()
+        self.mocked_translate = self.translate_patcher.start()
+
+    def constructor_test(self):
+        """
+        Test that constructing a ColorButton object works correctly
+        """
+
+        # GIVEN: The ColorButton class, a mocked change_color, setToolTip methods and clicked signal
+        with patch('openlp.core.ui.lib.colorbutton.ColorButton.setToolTip') as mocked_set_tool_tip:
+
+            # WHEN: The ColorButton object is instantiated
+            widget = ColorButton()
+
+            # THEN: The widget __init__ method should have the correct properties and methods called
+            self.assertEqual(widget.parent, None,
+                             'The parent should be the same as the one that the class was instianted with')
+            self.mocked_change_color.assert_called_once_with('#ffffff')
+            mocked_set_tool_tip.assert_called_once_with('Tool Tip Text')
+            self.mocked_clicked.connect.assert_called_once_with(widget.on_clicked)
+
+    def change_color_test(self):
+        """
+        Test that change_color sets the new color and the stylesheet
+        """
+        self.change_color_patcher.stop()
+
+        # GIVEN: An instance of the ColorButton object, and a mocked out setStyleSheet
+        with patch('openlp.core.ui.lib.colorbutton.ColorButton.setStyleSheet') as mocked_set_style_sheet:
+            widget = ColorButton()
+
+            # WHEN: Changing the color
+            widget.change_color('#000000')
+
+            # THEN: The _color attribute should be set to #000000 and setStyleSheet should have been called twice
+            self.assertEqual(widget._color, '#000000', '_color should have been set to #000000')
+            mocked_set_style_sheet.assert_has_calls(
+                [call('background-color: #ffffff'), call('background-color: #000000')])
+
+        self.mocked_change_color = self.change_color_patcher.start()
+
+    def color_test(self):
+        """
+        Test that the color property method returns the set color
+        """
+
+        # GIVEN: An instance of ColorButton, with a set _color attribute
+        widget = ColorButton()
+        widget._color = '#000000'
+
+        # WHEN: Accesing the color property
+        value = widget.color
+
+        # THEN: The value set in _color should be returned
+        self.assertEqual(value, '#000000', 'The value returned should be equal to the one we set')
+
+    def color_test(self):
+        """
+        Test that the color property method returns the set color
+        """
+
+        # GIVEN: An instance of ColorButton, with a set _color attribute
+        widget = ColorButton()
+        widget._color = '#000000'
+
+        # WHEN: Accesing the color property
+        value = widget.color
+
+        # THEN: The value set in _color should be returned
+        self.assertEqual(value, '#000000', 'The value returned should be equal to the one we set')
+
+    def color_setter_test(self):
+        """
+        Test that the color property setter method sets the color
+        """
+
+        # GIVEN: An instance of ColorButton, with a mocked __init__
+        with patch('openlp.core.ui.lib.colorbutton.ColorButton.__init__', **{'return_value': None}):
+            widget = ColorButton()
+
+            # WHEN: Setting the color property
+            widget.color = '#000000'
+
+            # THEN: Then change_color should have been called with the value we set
+            self.mocked_change_color.assert_called_once_with('#000000')
+
+    def on_clicked_invalid_color_test(self):
+        """
+        Test the on_click method when an invalid color has been supplied
+        """
+
+        # GIVEN: An instance of ColorButton, and a set _color attribute
+        widget = ColorButton()
+        self.mocked_change_color.reset_mock()
+        self.mocked_color_changed.reset_mock()
+        widget._color = '#000000'
+
+        # WHEN: The on_clicked method is called, and the color is invalid
+        self.mocked_qt_widgets.QColorDialog.getColor.return_value = MagicMock(**{'isValid.return_value': False})
+        widget.on_clicked()
+
+        # THEN: change_color should not have been called and the colorChanged signal should not have been emitted
+        self.assertEqual(
+            self.mocked_change_color.call_count, 0, 'change_color should not have been called with an invalid color')
+        self.assertEqual(
+            self.mocked_color_changed.emit.call_count, 0,
+            'colorChange signal should not have been emitted with an invalid color')
+
+    def on_clicked_same_color_test(self):
+        """
+        Test the on_click method when a new color has not been chosen
+        """
+
+        # GIVEN: An instance of ColorButton, and a set _color attribute
+        widget = ColorButton()
+        self.mocked_change_color.reset_mock()
+        self.mocked_color_changed.reset_mock()
+        widget._color = '#000000'
+
+        # WHEN: The on_clicked method is called, and the color is valid, but the same as the existing color
+        self.mocked_qt_widgets.QColorDialog.getColor.return_value = MagicMock(
+            **{'isValid.return_value': True, 'name.return_value': '#000000'})
+        widget.on_clicked()
+
+        # THEN: change_color should not have been called and the colorChanged signal should not have been emitted
+        self.assertEqual(
+            self.mocked_change_color.call_count, 0,
+            'change_color should not have been called when the color has not changed')
+        self.assertEqual(
+            self.mocked_color_changed.emit.call_count, 0,
+            'colorChange signal should not have been emitted when the color has not changed')
+
+    def on_clicked_new_color_test(self):
+        """
+        Test the on_click method when a new color has been chosen and is valid
+        """
+
+        # GIVEN: An instance of ColorButton, and a set _color attribute
+        widget = ColorButton()
+        self.mocked_change_color.reset_mock()
+        self.mocked_color_changed.reset_mock()
+        widget._color = '#000000'
+
+        # WHEN: The on_clicked method is called, and the color is valid, and different to the existing color
+        self.mocked_qt_widgets.QColorDialog.getColor.return_value = MagicMock(
+            **{'isValid.return_value': True, 'name.return_value': '#ffffff'})
+        widget.on_clicked()
+
+        # THEN: change_color should have been called and the colorChanged signal should have been emitted
+        self.mocked_change_color.assert_called_once_with('#ffffff')
+        self.mocked_color_changed.emit.assert_called_once_with('#ffffff')

=== added file 'tests/functional/openlp_core_ui_lib/test_listpreviewwidget.py'
--- tests/functional/openlp_core_ui_lib/test_listpreviewwidget.py	1970-01-01 00:00:00 +0000
+++ tests/functional/openlp_core_ui_lib/test_listpreviewwidget.py	2016-04-22 19:42:45 +0000
@@ -0,0 +1,377 @@
+# -*- coding: utf-8 -*-
+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
+
+###############################################################################
+# OpenLP - Open Source Lyrics Projection                                      #
+# --------------------------------------------------------------------------- #
+# Copyright (c) 2008-2016 OpenLP Developers                                   #
+# --------------------------------------------------------------------------- #
+# 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                          #
+###############################################################################
+"""
+Package to test the openlp.core.ui.lib.listpreviewwidget package.
+"""
+from unittest import TestCase
+
+from openlp.core.common import Settings
+from openlp.core.ui.lib.listpreviewwidget import ListPreviewWidget
+from openlp.core.lib import ServiceItem
+
+from tests.functional import MagicMock, patch, call
+
+
+class TestListPreviewWidget(TestCase):
+
+    def setUp(self):
+        """
+        Mock out stuff for all the tests
+        """
+        # Mock self.parent().width()
+        self.parent_patcher = patch('openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.parent')
+        self.mocked_parent = self.parent_patcher.start()
+        self.mocked_parent.width.return_value = 100
+        self.addCleanup(self.parent_patcher.stop)
+
+        # Mock Settings().value()
+        self.Settings_patcher = patch('openlp.core.ui.lib.listpreviewwidget.Settings')
+        self.mocked_Settings = self.Settings_patcher.start()
+        self.mocked_Settings_obj = MagicMock()
+        self.mocked_Settings_obj.value.return_value = None
+        self.mocked_Settings.return_value = self.mocked_Settings_obj
+        self.addCleanup(self.Settings_patcher.stop)
+
+        # Mock self.viewport().width()
+        self.viewport_patcher = patch('openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.viewport')
+        self.mocked_viewport = self.viewport_patcher.start()
+        self.mocked_viewport_obj = MagicMock()
+        self.mocked_viewport_obj.width.return_value = 200
+        self.mocked_viewport.return_value = self.mocked_viewport_obj
+        self.addCleanup(self.viewport_patcher.stop)
+
+    def new_list_preview_widget_test(self):
+        """
+        Test that creating an instance of ListPreviewWidget works
+        """
+        # GIVEN: A ListPreviewWidget class
+
+        # WHEN: An object is created
+        list_preview_widget = ListPreviewWidget(None, 1)
+
+        # THEN: The object is not None, and the _setup() method was called.
+        self.assertIsNotNone(list_preview_widget, 'The ListPreviewWidget object should not be None')
+        self.assertEquals(list_preview_widget.screen_ratio, 1, 'Should not be called')
+
+    @patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.resizeRowsToContents')
+    @patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.setRowHeight')
+    def replace_recalculate_layout_test_text(self, mocked_setRowHeight, mocked_resizeRowsToContents):
+        """
+        Test if "Max height for non-text slides..." enabled, txt slides unchanged in replace_service_item & __recalc...
+        """
+        # GIVEN: A setting to adjust "Max height for non-text slides in slide controller",
+        #        a text ServiceItem and a ListPreviewWidget.
+
+        # Mock Settings().value('advanced/slide max height')
+        self.mocked_Settings_obj.value.return_value = 100
+        # Mock self.viewport().width()
+        self.mocked_viewport_obj.width.return_value = 200
+        # Mock text service item
+        service_item = MagicMock()
+        service_item.is_text.return_value = True
+        service_item.get_frames.return_value = [{'title': None, 'text': None, 'verseTag': None},
+                                                {'title': None, 'text': None, 'verseTag': None}]
+        # init ListPreviewWidget and load service item
+        list_preview_widget = ListPreviewWidget(None, 1)
+        list_preview_widget.replace_service_item(service_item, 200, 0)
+        # Change viewport width before forcing a resize
+        self.mocked_viewport_obj.width.return_value = 400
+
+        # WHEN: __recalculate_layout() is called (via resizeEvent)
+        list_preview_widget.resizeEvent(None)
+
+        # THEN: setRowHeight() should not be called, while resizeRowsToContents() should be called twice
+        #       (once each in __recalculate_layout and replace_service_item)
+        self.assertEquals(mocked_resizeRowsToContents.call_count, 2, 'Should be called')
+        self.assertEquals(mocked_setRowHeight.call_count, 0, 'Should not be called')
+
+    @patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.resizeRowsToContents')
+    @patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.setRowHeight')
+    def replace_recalculate_layout_test_img(self, mocked_setRowHeight, mocked_resizeRowsToContents):
+        """
+        Test if "Max height for non-text slides..." disabled, img slides unchanged in replace_service_item & __recalc...
+        """
+        # GIVEN: A setting to adjust "Max height for non-text slides in slide controller",
+        #        an image ServiceItem and a ListPreviewWidget.
+
+        # Mock Settings().value('advanced/slide max height')
+        self.mocked_Settings_obj.value.return_value = 0
+        # Mock self.viewport().width()
+        self.mocked_viewport_obj.width.return_value = 200
+        # Mock image service item
+        service_item = MagicMock()
+        service_item.is_text.return_value = False
+        service_item.get_frames.return_value = [{'title': None, 'path': None, 'image': None},
+                                                {'title': None, 'path': None, 'image': None}]
+        # init ListPreviewWidget and load service item
+        list_preview_widget = ListPreviewWidget(None, 1)
+        list_preview_widget.replace_service_item(service_item, 200, 0)
+        # Change viewport width before forcing a resize
+        self.mocked_viewport_obj.width.return_value = 400
+
+        # WHEN: __recalculate_layout() is called (via resizeEvent)
+        list_preview_widget.resizeEvent(None)
+        self.mocked_Settings_obj.value.return_value = None
+        list_preview_widget.resizeEvent(None)
+
+        # THEN: resizeRowsToContents() should not be called, while setRowHeight() should be called
+        #       twice for each slide.
+        self.assertEquals(mocked_resizeRowsToContents.call_count, 0, 'Should not be called')
+        self.assertEquals(mocked_setRowHeight.call_count, 6, 'Should be called 3 times for each slide')
+        calls = [call(0, 200), call(1, 200), call(0, 400), call(1, 400), call(0, 400), call(1, 400)]
+        mocked_setRowHeight.assert_has_calls(calls)
+
+    @patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.resizeRowsToContents')
+    @patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.setRowHeight')
+    def replace_recalculate_layout_test_img_max(self, mocked_setRowHeight, mocked_resizeRowsToContents):
+        """
+        Test if "Max height for non-text slides..." enabled, img slides resized in replace_service_item & __recalc...
+        """
+        # GIVEN: A setting to adjust "Max height for non-text slides in slide controller",
+        #        an image ServiceItem and a ListPreviewWidget.
+
+        # Mock Settings().value('advanced/slide max height')
+        self.mocked_Settings_obj.value.return_value = 100
+        # Mock self.viewport().width()
+        self.mocked_viewport_obj.width.return_value = 200
+        # Mock image service item
+        service_item = MagicMock()
+        service_item.is_text.return_value = False
+        service_item.get_frames.return_value = [{'title': None, 'path': None, 'image': None},
+                                                {'title': None, 'path': None, 'image': None}]
+        # init ListPreviewWidget and load service item
+        list_preview_widget = ListPreviewWidget(None, 1)
+        list_preview_widget.replace_service_item(service_item, 200, 0)
+        # Change viewport width before forcing a resize
+        self.mocked_viewport_obj.width.return_value = 400
+
+        # WHEN: __recalculate_layout() is called (via resizeEvent)
+        list_preview_widget.resizeEvent(None)
+
+        # THEN: resizeRowsToContents() should not be called, while setRowHeight() should be called
+        #       twice for each slide.
+        self.assertEquals(mocked_resizeRowsToContents.call_count, 0, 'Should not be called')
+        self.assertEquals(mocked_setRowHeight.call_count, 4, 'Should be called twice for each slide')
+        calls = [call(0, 100), call(1, 100), call(0, 100), call(1, 100)]
+        mocked_setRowHeight.assert_has_calls(calls)
+
+    @patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.resizeRowsToContents')
+    @patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.setRowHeight')
+    @patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.cellWidget')
+    def row_resized_test_text(self, mocked_cellWidget, mocked_setRowHeight, mocked_resizeRowsToContents):
+        """
+        Test if "Max height for non-text slides..." enabled, text-based slides not affected in row_resized.
+        """
+        # GIVEN: A setting to adjust "Max height for non-text slides in slide controller",
+        #        a text ServiceItem and a ListPreviewWidget.
+
+        # Mock Settings().value('advanced/slide max height')
+        self.mocked_Settings_obj.value.return_value = 100
+        # Mock self.viewport().width()
+        self.mocked_viewport_obj.width.return_value = 200
+        # Mock text service item
+        service_item = MagicMock()
+        service_item.is_text.return_value = True
+        service_item.get_frames.return_value = [{'title': None, 'text': None, 'verseTag': None},
+                                                {'title': None, 'text': None, 'verseTag': None}]
+        # Mock self.cellWidget().children().setMaximumWidth()
+        mocked_cellWidget_child = MagicMock()
+        mocked_cellWidget_obj = MagicMock()
+        mocked_cellWidget_obj.children.return_value = [None, mocked_cellWidget_child]
+        mocked_cellWidget.return_value = mocked_cellWidget_obj
+        # init ListPreviewWidget and load service item
+        list_preview_widget = ListPreviewWidget(None, 1)
+        list_preview_widget.replace_service_item(service_item, 200, 0)
+
+        # WHEN: row_resized() is called
+        list_preview_widget.row_resized(0, 100, 150)
+
+        # THEN: self.cellWidget(row, 0).children()[1].setMaximumWidth() should not be called
+        self.assertEquals(mocked_cellWidget_child.setMaximumWidth.call_count, 0, 'Should not be called')
+
+    @patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.resizeRowsToContents')
+    @patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.setRowHeight')
+    @patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.cellWidget')
+    def row_resized_test_img(self, mocked_cellWidget, mocked_setRowHeight, mocked_resizeRowsToContents):
+        """
+        Test if "Max height for non-text slides..." disabled, image-based slides not affected in row_resized.
+        """
+        # GIVEN: A setting to adjust "Max height for non-text slides in slide controller",
+        #        an image ServiceItem and a ListPreviewWidget.
+
+        # Mock Settings().value('advanced/slide max height')
+        self.mocked_Settings_obj.value.return_value = 0
+        # Mock self.viewport().width()
+        self.mocked_viewport_obj.width.return_value = 200
+        # Mock image service item
+        service_item = MagicMock()
+        service_item.is_text.return_value = False
+        service_item.get_frames.return_value = [{'title': None, 'path': None, 'image': None},
+                                                {'title': None, 'path': None, 'image': None}]
+        # Mock self.cellWidget().children().setMaximumWidth()
+        mocked_cellWidget_child = MagicMock()
+        mocked_cellWidget_obj = MagicMock()
+        mocked_cellWidget_obj.children.return_value = [None, mocked_cellWidget_child]
+        mocked_cellWidget.return_value = mocked_cellWidget_obj
+        # init ListPreviewWidget and load service item
+        list_preview_widget = ListPreviewWidget(None, 1)
+        list_preview_widget.replace_service_item(service_item, 200, 0)
+
+        # WHEN: row_resized() is called
+        list_preview_widget.row_resized(0, 100, 150)
+        self.mocked_Settings_obj.value.return_value = None
+        list_preview_widget.row_resized(0, 100, 150)
+
+        # THEN: self.cellWidget(row, 0).children()[1].setMaximumWidth() should not be called
+        self.assertEquals(mocked_cellWidget_child.setMaximumWidth.call_count, 0, 'Should not be called')
+
+    @patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.resizeRowsToContents')
+    @patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.setRowHeight')
+    @patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.cellWidget')
+    def row_resized_test_img_max(self, mocked_cellWidget, mocked_setRowHeight, mocked_resizeRowsToContents):
+        """
+        Test if "Max height for non-text slides..." enabled, image-based slides are scaled in row_resized.
+        """
+        # GIVEN: A setting to adjust "Max height for non-text slides in slide controller",
+        #        an image ServiceItem and a ListPreviewWidget.
+
+        # Mock Settings().value('advanced/slide max height')
+        self.mocked_Settings_obj.value.return_value = 100
+        # Mock self.viewport().width()
+        self.mocked_viewport_obj.width.return_value = 200
+        # Mock image service item
+        service_item = MagicMock()
+        service_item.is_text.return_value = False
+        service_item.get_frames.return_value = [{'title': None, 'path': None, 'image': None},
+                                                {'title': None, 'path': None, 'image': None}]
+        # Mock self.cellWidget().children().setMaximumWidth()
+        mocked_cellWidget_child = MagicMock()
+        mocked_cellWidget_obj = MagicMock()
+        mocked_cellWidget_obj.children.return_value = [None, mocked_cellWidget_child]
+        mocked_cellWidget.return_value = mocked_cellWidget_obj
+        # init ListPreviewWidget and load service item
+        list_preview_widget = ListPreviewWidget(None, 1)
+        list_preview_widget.replace_service_item(service_item, 200, 0)
+
+        # WHEN: row_resized() is called
+        list_preview_widget.row_resized(0, 100, 150)
+
+        # THEN: self.cellWidget(row, 0).children()[1].setMaximumWidth() should be called
+        mocked_cellWidget_child.setMaximumWidth.assert_called_once_with(150)
+
+    @patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.selectRow')
+    @patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.scrollToItem')
+    @patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.item')
+    @patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.slide_count')
+    def autoscroll_test_setting_invalid(self, mocked_slide_count, mocked_item, mocked_scrollToItem, mocked_selectRow):
+        """
+        Test if 'advanced/autoscrolling' setting None or invalid, that no autoscrolling occurs on change_slide().
+        """
+        # GIVEN: A setting for autoscrolling and a ListPreviewWidget.
+        # Mock Settings().value('advanced/autoscrolling')
+        self.mocked_Settings_obj.value.return_value = None
+        # Mocked returns
+        mocked_slide_count.return_value = 1
+        mocked_item.return_value = None
+        # init ListPreviewWidget and load service item
+        list_preview_widget = ListPreviewWidget(None, 1)
+
+        # WHEN: change_slide() is called
+        list_preview_widget.change_slide(0)
+        self.mocked_Settings_obj.value.return_value = 1
+        list_preview_widget.change_slide(0)
+        self.mocked_Settings_obj.value.return_value = {'fail': 1}
+        list_preview_widget.change_slide(0)
+        self.mocked_Settings_obj.value.return_value = {'dist': 1, 'fail': 1}
+        list_preview_widget.change_slide(0)
+        self.mocked_Settings_obj.value.return_value = {'dist': 'fail', 'pos': 1}
+        list_preview_widget.change_slide(0)
+        self.mocked_Settings_obj.value.return_value = {'dist': 1, 'pos': 'fail'}
+        list_preview_widget.change_slide(0)
+
+        # THEN: no further functions should be called
+        self.assertEquals(mocked_slide_count.call_count, 0, 'Should not be called')
+        self.assertEquals(mocked_scrollToItem.call_count, 0, 'Should not be called')
+        self.assertEquals(mocked_selectRow.call_count, 0, 'Should not be called')
+        self.assertEquals(mocked_item.call_count, 0, 'Should not be called')
+
+    @patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.selectRow')
+    @patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.scrollToItem')
+    @patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.item')
+    @patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.slide_count')
+    def autoscroll_test_dist_bounds(self, mocked_slide_count, mocked_item, mocked_scrollToItem, mocked_selectRow):
+        """
+        Test if 'advanced/autoscrolling' setting asks to scroll beyond list bounds, that it does not beyond.
+        """
+        # GIVEN: A setting for autoscrolling and a ListPreviewWidget.
+        # Mock Settings().value('advanced/autoscrolling')
+        self.mocked_Settings_obj.value.return_value = {'dist': -1, 'pos': 1}
+        # Mocked returns
+        mocked_slide_count.return_value = 1
+        mocked_item.return_value = None
+        # init ListPreviewWidget and load service item
+        list_preview_widget = ListPreviewWidget(None, 1)
+
+        # WHEN: change_slide() is called
+        list_preview_widget.change_slide(0)
+        self.mocked_Settings_obj.value.return_value = {'dist': 1, 'pos': 1}
+        list_preview_widget.change_slide(0)
+
+        # THEN: no further functions should be called
+        self.assertEquals(mocked_slide_count.call_count, 3, 'Should be called')
+        self.assertEquals(mocked_scrollToItem.call_count, 2, 'Should be called')
+        self.assertEquals(mocked_selectRow.call_count, 2, 'Should be called')
+        self.assertEquals(mocked_item.call_count, 2, 'Should be called')
+        calls = [call(0, 0), call(0, 0)]
+        mocked_item.assert_has_calls(calls)
+
+    @patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.selectRow')
+    @patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.scrollToItem')
+    @patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.item')
+    @patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.slide_count')
+    def autoscroll_test_normal(self, mocked_slide_count, mocked_item, mocked_scrollToItem, mocked_selectRow):
+        """
+        Test if 'advanced/autoscrolling' setting valid, autoscrolling called as expected.
+        """
+        # GIVEN: A setting for autoscrolling and a ListPreviewWidget.
+        # Mock Settings().value('advanced/autoscrolling')
+        self.mocked_Settings_obj.value.return_value = {'dist': -1, 'pos': 1}
+        # Mocked returns
+        mocked_slide_count.return_value = 3
+        mocked_item.return_value = None
+        # init ListPreviewWidget and load service item
+        list_preview_widget = ListPreviewWidget(None, 1)
+
+        # WHEN: change_slide() is called
+        list_preview_widget.change_slide(1)
+        self.mocked_Settings_obj.value.return_value = {'dist': 0, 'pos': 1}
+        list_preview_widget.change_slide(1)
+        self.mocked_Settings_obj.value.return_value = {'dist': 1, 'pos': 1}
+        list_preview_widget.change_slide(1)
+
+        # THEN: no further functions should be called
+        self.assertEquals(mocked_slide_count.call_count, 3, 'Should be called')
+        self.assertEquals(mocked_scrollToItem.call_count, 3, 'Should be called')
+        self.assertEquals(mocked_selectRow.call_count, 3, 'Should be called')
+        self.assertEquals(mocked_item.call_count, 3, 'Should be called')
+        calls = [call(0, 0), call(1, 0), call(2, 0)]
+        mocked_item.assert_has_calls(calls)

=== removed file 'tests/interfaces/openlp_core_ui/test_listpreviewwidget.py'
--- tests/interfaces/openlp_core_ui/test_listpreviewwidget.py	2015-12-31 22:46:06 +0000
+++ tests/interfaces/openlp_core_ui/test_listpreviewwidget.py	1970-01-01 00:00:00 +0000
@@ -1,106 +0,0 @@
-# -*- coding: utf-8 -*-
-# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
-
-###############################################################################
-# OpenLP - Open Source Lyrics Projection                                      #
-# --------------------------------------------------------------------------- #
-# Copyright (c) 2008-2016 OpenLP Developers                                   #
-# --------------------------------------------------------------------------- #
-# 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                          #
-###############################################################################
-"""
-    Package to test the openlp.core.ui.listpreviewwidget.
-"""
-
-from unittest import TestCase
-
-from PyQt5 import QtGui, QtWidgets
-
-from openlp.core.common import Registry
-from openlp.core.lib import ServiceItem
-from openlp.core.ui import listpreviewwidget
-from tests.interfaces import MagicMock, patch
-from tests.utils.osdinteraction import read_service_from_file
-from tests.helpers.testmixin import TestMixin
-
-
-class TestListPreviewWidget(TestCase, TestMixin):
-
-    def setUp(self):
-        """
-        Create the UI.
-        """
-        Registry.create()
-        self.setup_application()
-        self.main_window = QtWidgets.QMainWindow()
-        self.image = QtGui.QImage(1, 1, QtGui.QImage.Format_RGB32)
-        self.image_manager = MagicMock()
-        self.image_manager.get_image.return_value = self.image
-        Registry().register('image_manager', self.image_manager)
-        self.preview_widget = listpreviewwidget.ListPreviewWidget(self.main_window, 2)
-
-    def tearDown(self):
-        """
-        Delete all the C++ objects at the end so that we don't have a segfault.
-        """
-        del self.preview_widget
-        del self.main_window
-
-    def initial_slide_count_test(self):
-        """
-        Test the initial slide count .
-        """
-        # GIVEN: A new ListPreviewWidget instance.
-        # WHEN: No SlideItem has been added yet.
-        # THEN: The count of items should be zero.
-        self.assertEqual(self.preview_widget.slide_count(), 0, 'The slide list should be empty.')
-
-    def initial_slide_number_test(self):
-        """
-        Test the initial current slide number.
-        """
-        # GIVEN: A new ListPreviewWidget instance.
-        # WHEN: No SlideItem has been added yet.
-        # THEN: The number of the current item should be -1.
-        self.assertEqual(self.preview_widget.current_slide_number(), -1, 'The slide number should be -1.')
-
-    def replace_service_item_test(self):
-        """
-        Test item counts and current number with a service item.
-        """
-        # GIVEN: A ServiceItem with two frames.
-        service_item = ServiceItem(None)
-        service = read_service_from_file('serviceitem_image_3.osj')
-        with patch('os.path.exists'):
-            service_item.set_from_service(service[0])
-        # WHEN: Added to the preview widget.
-        self.preview_widget.replace_service_item(service_item, 1, 1)
-        # THEN: The slide count and number should fit.
-        self.assertEqual(self.preview_widget.slide_count(), 2, 'The slide count should be 2.')
-        self.assertEqual(self.preview_widget.current_slide_number(), 1, 'The current slide number should  be 1.')
-
-    def change_slide_test(self):
-        """
-        Test the change_slide method.
-        """
-        # GIVEN: A ServiceItem with two frames content.
-        service_item = ServiceItem(None)
-        service = read_service_from_file('serviceitem_image_3.osj')
-        with patch('os.path.exists'):
-            service_item.set_from_service(service[0])
-        # WHEN: Added to the preview widget and switched to the second frame.
-        self.preview_widget.replace_service_item(service_item, 1, 0)
-        self.preview_widget.change_slide(1)
-        # THEN: The current_slide_number should reflect the change.
-        self.assertEqual(self.preview_widget.current_slide_number(), 1, 'The current slide number should  be 1.')

=== added file 'tests/interfaces/openlp_core_ui_lib/test_listpreviewwidget.py'
--- tests/interfaces/openlp_core_ui_lib/test_listpreviewwidget.py	1970-01-01 00:00:00 +0000
+++ tests/interfaces/openlp_core_ui_lib/test_listpreviewwidget.py	2016-04-22 19:42:45 +0000
@@ -0,0 +1,106 @@
+# -*- coding: utf-8 -*-
+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
+
+###############################################################################
+# OpenLP - Open Source Lyrics Projection                                      #
+# --------------------------------------------------------------------------- #
+# Copyright (c) 2008-2016 OpenLP Developers                                   #
+# --------------------------------------------------------------------------- #
+# 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                          #
+###############################################################################
+"""
+    Package to test the openlp.core.ui.lib.listpreviewwidget.
+"""
+
+from unittest import TestCase
+
+from PyQt5 import QtGui, QtWidgets
+
+from openlp.core.common import Registry
+from openlp.core.lib import ServiceItem
+from openlp.core.ui.lib import ListWidgetWithDnD, ListPreviewWidget
+from tests.interfaces import MagicMock, patch
+from tests.utils.osdinteraction import read_service_from_file
+from tests.helpers.testmixin import TestMixin
+
+
+class TestListPreviewWidget(TestCase, TestMixin):
+
+    def setUp(self):
+        """
+        Create the UI.
+        """
+        Registry.create()
+        self.setup_application()
+        self.main_window = QtWidgets.QMainWindow()
+        self.image = QtGui.QImage(1, 1, QtGui.QImage.Format_RGB32)
+        self.image_manager = MagicMock()
+        self.image_manager.get_image.return_value = self.image
+        Registry().register('image_manager', self.image_manager)
+        self.preview_widget = ListPreviewWidget(self.main_window, 2)
+
+    def tearDown(self):
+        """
+        Delete all the C++ objects at the end so that we don't have a segfault.
+        """
+        del self.preview_widget
+        del self.main_window
+
+    def initial_slide_count_test(self):
+        """
+        Test the initial slide count .
+        """
+        # GIVEN: A new ListPreviewWidget instance.
+        # WHEN: No SlideItem has been added yet.
+        # THEN: The count of items should be zero.
+        self.assertEqual(self.preview_widget.slide_count(), 0, 'The slide list should be empty.')
+
+    def initial_slide_number_test(self):
+        """
+        Test the initial current slide number.
+        """
+        # GIVEN: A new ListPreviewWidget instance.
+        # WHEN: No SlideItem has been added yet.
+        # THEN: The number of the current item should be -1.
+        self.assertEqual(self.preview_widget.current_slide_number(), -1, 'The slide number should be -1.')
+
+    def replace_service_item_test(self):
+        """
+        Test item counts and current number with a service item.
+        """
+        # GIVEN: A ServiceItem with two frames.
+        service_item = ServiceItem(None)
+        service = read_service_from_file('serviceitem_image_3.osj')
+        with patch('os.path.exists'):
+            service_item.set_from_service(service[0])
+        # WHEN: Added to the preview widget.
+        self.preview_widget.replace_service_item(service_item, 1, 1)
+        # THEN: The slide count and number should fit.
+        self.assertEqual(self.preview_widget.slide_count(), 2, 'The slide count should be 2.')
+        self.assertEqual(self.preview_widget.current_slide_number(), 1, 'The current slide number should  be 1.')
+
+    def change_slide_test(self):
+        """
+        Test the change_slide method.
+        """
+        # GIVEN: A ServiceItem with two frames content.
+        service_item = ServiceItem(None)
+        service = read_service_from_file('serviceitem_image_3.osj')
+        with patch('os.path.exists'):
+            service_item.set_from_service(service[0])
+        # WHEN: Added to the preview widget and switched to the second frame.
+        self.preview_widget.replace_service_item(service_item, 1, 0)
+        self.preview_widget.change_slide(1)
+        # THEN: The current_slide_number should reflect the change.
+        self.assertEqual(self.preview_widget.current_slide_number(), 1, 'The current slide number should  be 1.')


Follow ups