← Back to team overview

openlp-core team mailing list archive

[Merge] lp:~phill-ridout/openlp/bible_media_item_refactors into lp:openlp

 

Phill has proposed merging lp:~phill-ridout/openlp/bible_media_item_refactors into lp:openlp.

Requested reviews:
  OpenLP Core (openlp-core)

For more details, see:
https://code.launchpad.net/~phill-ridout/openlp/bible_media_item_refactors/+merge/313506

Bible media item refactors.
Added the ability to sort the 'select' aka 'advanced' bible books combo box alphabetically. (note resources will need regen to show the button icon, I'll submit this in another merge proposal to keep this one a tiny bit cleaner.
Lots of Tests!
-- 
Your team OpenLP Core is requested to review the proposed merge of lp:~phill-ridout/openlp/bible_media_item_refactors into lp:openlp.
=== modified file 'openlp/core/common/settings.py'
--- openlp/core/common/settings.py	2016-11-29 15:44:32 +0000
+++ openlp/core/common/settings.py	2016-12-18 19:00:12 +0000
@@ -217,7 +217,9 @@
         ('advanced/default image', 'core/logo file', []),  # Default image renamed + moved to general after 2.4.
         ('shortcuts/escapeItem', 'shortcuts/desktopScreenEnable', []),  # Escape item was removed in 2.6.
         ('shortcuts/offlineHelpItem', 'shortcuts/userManualItem', []),  # Online and Offline help were combined in 2.6.
-        ('shortcuts/onlineHelpItem', 'shortcuts/userManualItem', [])  # Online and Offline help were combined in 2.6.
+        ('shortcuts/onlineHelpItem', 'shortcuts/userManualItem', []),  # Online and Offline help were combined in 2.6.
+        ('bibles/advanced bible', '', []),  # Common bible search widgets combined in 2.6
+        ('bibles/quick bible', 'bibles/primary bible', [])  # Common bible search widgets combined in 2.6
     ]
 
     @staticmethod

=== modified file 'openlp/core/lib/mediamanageritem.py'
--- openlp/core/lib/mediamanageritem.py	2016-11-15 22:44:11 +0000
+++ openlp/core/lib/mediamanageritem.py	2016-12-18 19:00:12 +0000
@@ -197,14 +197,9 @@
         """
         # Add the List widget
         self.list_view = ListWidgetWithDnD(self, self.plugin.name)
-        self.list_view.setSpacing(1)
-        self.list_view.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
-        self.list_view.setAlternatingRowColors(True)
         self.list_view.setObjectName('{name}ListView'.format(name=self.plugin.name))
         # Add to page_layout
         self.page_layout.addWidget(self.list_view)
-        # define and add the context menu
-        self.list_view.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
         if self.has_edit_icon:
             create_widget_action(self.list_view,
                                  text=self.plugin.get_string(StringContent.Edit)['title'],

=== modified file 'openlp/core/ui/lib/listwidgetwithdnd.py'
--- openlp/core/ui/lib/listwidgetwithdnd.py	2016-11-12 11:50:06 +0000
+++ openlp/core/ui/lib/listwidgetwithdnd.py	2016-12-18 19:00:12 +0000
@@ -40,6 +40,11 @@
         super().__init__(parent)
         self.mime_data_text = name
         self.no_results_text = UiStrings().NoResults
+        self.setSpacing(1)
+        self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
+        self.setAlternatingRowColors(True)
+        self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
+        self.locked = False
 
     def activateDnD(self):
         """
@@ -49,13 +54,15 @@
         self.setDragDropMode(QtWidgets.QAbstractItemView.DragDrop)
         Registry().register_function(('%s_dnd' % self.mime_data_text), self.parent().load_file)
 
-    def clear(self, search_while_typing=False):
+    def clear(self, search_while_typing=False, override_lock=False):
         """
         Re-implement clear, so that we can customise feedback when using 'Search as you type'
 
         :param search_while_typing: True if we want to display the customised message
         :return: None
         """
+        if self.locked and not override_lock:
+            return
         if search_while_typing:
             self.no_results_text = UiStrings().ShortResults
         else:

=== modified file 'openlp/plugins/bibles/bibleplugin.py'
--- openlp/plugins/bibles/bibleplugin.py	2016-08-14 10:00:27 +0000
+++ openlp/plugins/bibles/bibleplugin.py	2016-12-18 19:00:12 +0000
@@ -46,8 +46,7 @@
     'bibles/is verse number visible': True,
     'bibles/display new chapter': False,
     'bibles/second bibles': True,
-    'bibles/advanced bible': '',
-    'bibles/quick bible': '',
+    'bibles/primary bible': '',
     'bibles/proxy name': '',
     'bibles/proxy address': '',
     'bibles/proxy username': '',

=== modified file 'openlp/plugins/bibles/lib/__init__.py'
--- openlp/plugins/bibles/lib/__init__.py	2016-08-14 10:00:27 +0000
+++ openlp/plugins/bibles/lib/__init__.py	2016-12-18 19:00:12 +0000
@@ -230,7 +230,7 @@
     REFERENCE_MATCHES['range_separator'] = re.compile(REFERENCE_SEPARATORS['sep_l'], re.UNICODE)
     # full reference match: <book>(<range>(,(?!$)|(?=$)))+
     REFERENCE_MATCHES['full'] = \
-        re.compile('^\s*(?!\s)(?P<book>[\d]*[^\d]+)(?<!\s)\s*'
+        re.compile('^\s*(?!\s)(?P<book>[\d]*[^\d\.]+)\.*(?<!\s)\s*'
                    '(?P<ranges>(?:%(range_regex)s(?:%(sep_l)s(?!\s*$)|(?=\s*$)))+)\s*$'
                    % dict(list(REFERENCE_SEPARATORS.items()) + [('range_regex', range_regex)]), re.UNICODE)
 
@@ -326,7 +326,7 @@
 
     ``^\s*(?!\s)(?P<book>[\d]*[^\d]+)(?<!\s)\s*``
         The ``book`` group starts with the first non-whitespace character. There are optional leading digits followed by
-        non-digits. The group ends before the whitspace in front of the next digit.
+        non-digits. The group ends before the whitspace, or a full stop in front of the next digit.
 
     ``(?P<ranges>(?:%(range_regex)s(?:%(sep_l)s(?!\s*$)|(?=\s*$)))+)\s*$``
         The second group contains all ``ranges``. This can be multiple declarations of range_regex separated by a list

=== modified file 'openlp/plugins/bibles/lib/db.py'
--- openlp/plugins/bibles/lib/db.py	2016-09-05 18:02:47 +0000
+++ openlp/plugins/bibles/lib/db.py	2016-12-18 19:00:12 +0000
@@ -36,7 +36,7 @@
 from openlp.core.common import AppLocation, translate, clean_filename
 from openlp.core.lib.db import BaseModel, init_db, Manager
 from openlp.core.lib.ui import critical_error_message_box
-from openlp.plugins.bibles.lib import upgrade
+from openlp.plugins.bibles.lib import BibleStrings, LanguageSelection, upgrade
 
 log = logging.getLogger(__name__)
 
@@ -52,9 +52,15 @@
 
 class Book(BaseModel):
     """
-    Song model
+    Bible Book model
     """
-    pass
+    def get_name(self, language_selection=LanguageSelection.Bible):
+        if language_selection == LanguageSelection.Bible:
+            return self.name
+        elif language_selection == LanguageSelection.Application:
+            return BibleStrings().BookNames[BiblesResourcesDB.get_book_by_id(self.book_reference_id)['abbreviation']]
+        elif language_selection == LanguageSelection.English:
+            return BiblesResourcesDB.get_book_by_id(self.book_reference_id)['name']
 
 
 class Verse(BaseModel):
@@ -380,13 +386,12 @@
         """
         log.debug('BibleDB.verse_search("{text}")'.format(text=text))
         verses = self.session.query(Verse)
-        # TODO: Find out what this is doing before converting to format()
         if text.find(',') > -1:
-            keywords = ['%%%s%%' % keyword.strip() for keyword in text.split(',')]
+            keywords = ['%{keyword}%'.format(keyword=keyword.strip()) for keyword in text.split(',') if keyword.strip()]
             or_clause = [Verse.text.like(keyword) for keyword in keywords]
             verses = verses.filter(or_(*or_clause))
         else:
-            keywords = ['%%%s%%' % keyword.strip() for keyword in text.split(' ')]
+            keywords = ['%{keyword}%'.format(keyword=keyword.strip()) for keyword in text.split(' ') if keyword.strip()]
             for keyword in keywords:
                 verses = verses.filter(Verse.text.like(keyword))
         verses = verses.all()

=== modified file 'openlp/plugins/bibles/lib/manager.py'
--- openlp/plugins/bibles/lib/manager.py	2016-11-24 21:48:51 +0000
+++ openlp/plugins/bibles/lib/manager.py	2016-12-18 19:00:12 +0000
@@ -250,7 +250,13 @@
                   '"{book}", "{chapter}")'.format(bible=bible, book=book_ref_id, chapter=chapter))
         return self.db_cache[bible].get_verse_count(book_ref_id, chapter)
 
-    def get_verses(self, bible, verse_text, book_ref_id=False, show_error=True):
+    def parse_ref(self, bible, reference_text, book_ref_id=False):
+        if not bible:
+            return
+        language_selection = self.get_language_selection(bible)
+        return parse_reference(reference_text, self.db_cache[bible], language_selection, book_ref_id)
+
+    def get_verses(self, bible, ref_list, show_error=True):
         """
         Parses a scripture reference, fetches the verses from the Bible
         specified, and returns a list of ``Verse`` objects.
@@ -271,22 +277,9 @@
             For second bible this is necessary.
         :param show_error:
         """
-        # If no bibles are installed, message is given.
-        log.debug('BibleManager.get_verses("{bible}", "{verse}")'.format(bible=bible, verse=verse_text))
-        if not bible:
-            if show_error:
-                self.main_window.information_message(
-                    UiStrings().BibleNoBiblesTitle,
-                    UiStrings().BibleNoBibles)
-            return None
-        # Get the language for books.
-        language_selection = self.get_language_selection(bible)
-        ref_list = parse_reference(verse_text, self.db_cache[bible], language_selection, book_ref_id)
-        if ref_list:
-            return self.db_cache[bible].get_verses(ref_list, show_error)
-        # If nothing is found. Message is given if this is not combined search. (defined in mediaitem.py)
-        else:
-            return None
+        if not bible or not ref_list:
+            return None
+        return self.db_cache[bible].get_verses(ref_list, show_error)
 
     def get_language_selection(self, bible):
         """
@@ -308,7 +301,7 @@
             language_selection = LanguageSelection.Application
         return language_selection
 
-    def verse_search(self, bible, second_bible, text):
+    def verse_search(self, bible, text):
         """
         Does a verse search for the given bible and text.
 
@@ -325,20 +318,14 @@
             return None
         # Check if the bible or second_bible is a web bible.
         web_bible = self.db_cache[bible].get_object(BibleMeta, 'download_source')
-        second_web_bible = ''
-        if second_bible:
-            second_web_bible = self.db_cache[second_bible].get_object(BibleMeta, 'download_source')
-        if web_bible or second_web_bible:
+        if web_bible:
             # If either Bible is Web, cursor is reset to normal and message is given.
             self.application.set_normal_cursor()
             self.main_window.information_message(
                 translate('BiblesPlugin.BibleManager', 'Web Bible cannot be used in Text Search'),
                 translate('BiblesPlugin.BibleManager', 'Text Search is not available with Web Bibles.\n'
                                                        'Please use the Scripture Reference Search instead.\n\n'
-                                                       'This means that the currently used Bible\nor Second Bible '
-                                                       'is installed as Web Bible.\n\n'
-                                                       'If you were trying to perform a Reference search\nin Combined '
-                                                       'Search, your reference is invalid.')
+                                                       'This means that the currently selected Bible is a Web Bible.')
             )
             return None
         # Shorter than 3 char searches break OpenLP with very long search times, thus they are blocked.
@@ -380,6 +367,20 @@
         else:
             return None
 
+    def process_verse_range(self, book_ref_id, chapter_from, verse_from, chapter_to, verse_to):
+        verse_ranges = []
+        for chapter in range(chapter_from, chapter_to + 1):
+            if chapter == chapter_from:
+                start_verse = verse_from
+            else:
+                start_verse = 1
+            if chapter == chapter_to:
+                end_verse = verse_to
+            else:
+                end_verse = -1
+            verse_ranges.append((book_ref_id, chapter, start_verse, end_verse))
+        return verse_ranges
+
     def save_meta_data(self, bible, version, copyright, permissions, book_name_language=None):
         """
         Saves the bibles meta data.

=== modified file 'openlp/plugins/bibles/lib/mediaitem.py'
--- openlp/plugins/bibles/lib/mediaitem.py	2016-11-24 21:48:51 +0000
+++ openlp/plugins/bibles/lib/mediaitem.py	2016-12-18 19:00:12 +0000
@@ -21,28 +21,36 @@
 ###############################################################################
 
 import logging
+import re
 
 from PyQt5 import QtCore, QtWidgets
 
 from openlp.core.common import Registry, Settings, UiStrings, translate
-from openlp.core.lib import MediaManagerItem, ItemCapabilities, ServiceItemContext, create_separated_list
+from openlp.core.lib import MediaManagerItem, ItemCapabilities, ServiceItemContext
 from openlp.core.lib.searchedit import SearchEdit
 from openlp.core.lib.ui import set_case_insensitive_completer, create_horizontal_adjusting_combo_box, \
     critical_error_message_box, find_and_set_in_combo_box, build_icon
 from openlp.core.common.languagemanager import get_locale_key
 from openlp.plugins.bibles.forms.bibleimportform import BibleImportForm
 from openlp.plugins.bibles.forms.editbibleform import EditBibleForm
-from openlp.plugins.bibles.lib import LayoutStyle, DisplayStyle, VerseReferenceList, get_reference_separator, \
-    LanguageSelection, BibleStrings
-from openlp.plugins.bibles.lib.db import BiblesResourcesDB
-import re
+from openlp.plugins.bibles.lib import DisplayStyle, LayoutStyle, VerseReferenceList, \
+    get_reference_match, get_reference_separator
 
 log = logging.getLogger(__name__)
 
 
+VALID_TEXT_SEARCH = re.compile('\w\w\w')
+
+
+def get_reference_separators():
+    return {'verse': get_reference_separator('sep_v_display'),
+            'range': get_reference_separator('sep_r_display'),
+            'list': get_reference_separator('sep_l_display')}
+
+
 class BibleSearch(object):
     """
-    Enumeration class for the different search methods for the "quick search".
+    Enumeration class for the different search methods for the "Search" tab.
     """
     Reference = 1
     Text = 2
@@ -57,15 +65,31 @@
     bibles_add_to_service = QtCore.pyqtSignal(list)
     log.info('Bible Media Item loaded')
 
-    def __init__(self, parent, plugin):
+    def __init__(self, *args, **kwargs):
+        """
+        Constructor
+
+        :param args: Positional arguments to pass to the super method. (tuple)
+        :param kwargs: Keyword arguments to pass to the super method. (dict)
+        """
         self.clear_icon = build_icon(':/bibles/bibles_search_clear.png')
         self.lock_icon = build_icon(':/bibles/bibles_search_lock.png')
         self.unlock_icon = build_icon(':/bibles/bibles_search_unlock.png')
-        MediaManagerItem.__init__(self, parent, plugin)
+        self.sort_icon = build_icon(':/bibles/bibles_book_sort.png')
+        self.bible = None
+        self.second_bible = None
+        # TODO: Make more central and clean up after!
+        self.search_timer = QtCore.QTimer()
+        self.search_timer.setInterval(200)
+        self.search_timer.setSingleShot(True)
+        self.search_timer.timeout.connect(self.on_search_timer_timeout)
+        super().__init__(*args, **kwargs)
 
     def setup_item(self):
         """
         Do some additional setup.
+
+        :return: None
         """
         self.bibles_go_live.connect(self.go_live_remote)
         self.bibles_add_to_service.connect(self.add_to_service_remote)
@@ -73,251 +97,174 @@
         self.settings = self.plugin.settings_tab
         self.quick_preview_allowed = True
         self.has_search = True
-        self.search_results = {}
-        self.second_search_results = {}
+        self.search_results = []
+        self.second_search_results = []
         Registry().register_function('bibles_load_list', self.reload_bibles)
 
-    def __check_second_bible(self, bible, second_bible):
-        """
-        Check if the first item is a second bible item or not.
-        """
-        if not self.list_view.count():
-            self.display_results(bible, second_bible)
-            return
-        item_second_bible = self._decode_qt_object(self.list_view.item(0), 'second_bible')
-        if item_second_bible and second_bible or not item_second_bible and not second_bible:
-            self.display_results(bible, second_bible)
-        elif critical_error_message_box(
-            message=translate('BiblesPlugin.MediaItem',
-                              'You cannot combine single and dual Bible verse search results. '
-                              'Do you want to delete your search results and start a new search?'),
-                parent=self, question=True) == QtWidgets.QMessageBox.Yes:
-            self.list_view.clear()
-            self.display_results(bible, second_bible)
-
-    def _decode_qt_object(self, bitem, key):
-        reference = bitem.data(QtCore.Qt.UserRole)
-        obj = reference[str(key)]
-        return str(obj).strip()
-
     def required_icons(self):
         """
         Set which icons the media manager tab should show
+
+        :return: None
         """
-        MediaManagerItem.required_icons(self)
+        super().required_icons()
         self.has_import_icon = True
         self.has_new_icon = False
         self.has_edit_icon = True
         self.has_delete_icon = True
         self.add_to_service_item = False
 
-    def add_search_tab(self, prefix, name):
-        self.search_tab_bar.addTab(name)
-        tab = QtWidgets.QWidget()
-        tab.setObjectName(prefix + 'Tab')
-        tab.setSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum)
-        layout = QtWidgets.QGridLayout(tab)
-        layout.setObjectName(prefix + 'Layout')
-        setattr(self, prefix + 'Tab', tab)
-        setattr(self, prefix + 'Layout', layout)
-
-    def add_search_fields(self, prefix, name):
-        """
-        Creates and adds generic search tab.
-
-        :param prefix: The prefix of the tab, this is either ``quick`` or ``advanced``.
-        :param name: The translated string to display.
-        """
-        if prefix == 'quick':
-            idx = 2
-        else:
-            idx = 5
-        tab = getattr(self, prefix + 'Tab')
-        layout = getattr(self, prefix + 'Layout')
-        version_label = QtWidgets.QLabel(tab)
-        version_label.setObjectName(prefix + 'VersionLabel')
-        layout.addWidget(version_label, idx, 0, QtCore.Qt.AlignRight)
-        version_combo_box = create_horizontal_adjusting_combo_box(tab, prefix + 'VersionComboBox')
-        version_label.setBuddy(version_combo_box)
-        layout.addWidget(version_combo_box, idx, 1, 1, 2)
-        second_label = QtWidgets.QLabel(tab)
-        second_label.setObjectName(prefix + 'SecondLabel')
-        layout.addWidget(second_label, idx + 1, 0, QtCore.Qt.AlignRight)
-        second_combo_box = create_horizontal_adjusting_combo_box(tab, prefix + 'SecondComboBox')
-        version_label.setBuddy(second_combo_box)
-        layout.addWidget(second_combo_box, idx + 1, 1, 1, 2)
-        style_label = QtWidgets.QLabel(tab)
-        style_label.setObjectName(prefix + 'StyleLabel')
-        layout.addWidget(style_label, idx + 2, 0, QtCore.Qt.AlignRight)
-        style_combo_box = create_horizontal_adjusting_combo_box(tab, prefix + 'StyleComboBox')
-        style_combo_box.addItems(['', '', ''])
-        layout.addWidget(style_combo_box, idx + 2, 1, 1, 2)
-        search_button_layout = QtWidgets.QHBoxLayout()
-        search_button_layout.setObjectName(prefix + 'search_button_layout')
-        search_button_layout.addStretch()
-        # Note: If we use QPushButton instead of the QToolButton, the icon will be larger than the Lock icon.
-        clear_button = QtWidgets.QToolButton(tab)
-        clear_button.setIcon(self.clear_icon)
-        clear_button.setObjectName(prefix + 'ClearButton')
-        lock_button = QtWidgets.QToolButton(tab)
-        lock_button.setIcon(self.unlock_icon)
-        lock_button.setCheckable(True)
-        lock_button.setObjectName(prefix + 'LockButton')
-        search_button_layout.addWidget(clear_button)
-        search_button_layout.addWidget(lock_button)
-        search_button = QtWidgets.QPushButton(tab)
-        search_button.setObjectName(prefix + 'SearchButton')
-        search_button_layout.addWidget(search_button)
-        layout.addLayout(search_button_layout, idx + 3, 1, 1, 2)
-        self.page_layout.addWidget(tab)
-        tab.setVisible(False)
-        lock_button.toggled.connect(self.on_lock_button_toggled)
-        second_combo_box.currentIndexChanged.connect(self.on_second_bible_combobox_index_changed)
-        setattr(self, prefix + 'VersionLabel', version_label)
-        setattr(self, prefix + 'VersionComboBox', version_combo_box)
-        setattr(self, prefix + 'SecondLabel', second_label)
-        setattr(self, prefix + 'SecondComboBox', second_combo_box)
-        setattr(self, prefix + 'StyleLabel', style_label)
-        setattr(self, prefix + 'StyleComboBox', style_combo_box)
-        setattr(self, prefix + 'ClearButton', clear_button)
-        setattr(self, prefix + 'LockButton', lock_button)
-        setattr(self, prefix + 'SearchButtonLayout', search_button_layout)
-        setattr(self, prefix + 'SearchButton', search_button)
-
     def add_end_header_bar(self):
         self.search_tab_bar = QtWidgets.QTabBar(self)
         self.search_tab_bar.setExpanding(False)
-        self.search_tab_bar.setObjectName('search_tab_bar')
         self.page_layout.addWidget(self.search_tab_bar)
-        # Add the Quick Search tab.
-        self.add_search_tab('quick', translate('BiblesPlugin.MediaItem', 'Search'))
-        self.quick_search_label = QtWidgets.QLabel(self.quickTab)
-        self.quick_search_label.setObjectName('quick_search_label')
-        self.quickLayout.addWidget(self.quick_search_label, 0, 0, QtCore.Qt.AlignRight)
-        self.quick_search_edit = SearchEdit(self.quickTab, self.settings_section)
-        self.quick_search_edit.setSizePolicy(QtWidgets.QSizePolicy.Ignored, QtWidgets.QSizePolicy.Fixed)
-        self.quick_search_edit.setObjectName('quick_search_edit')
-        self.quick_search_label.setBuddy(self.quick_search_edit)
-        self.quickLayout.addWidget(self.quick_search_edit, 0, 1, 1, 2)
-        self.add_search_fields('quick', translate('BiblesPlugin.MediaItem', 'Search'))
-        self.quickTab.setVisible(True)
-        # Add the Advanced Search tab.
-        self.add_search_tab('advanced', translate('BiblesPlugin.MediaItem', 'Select'))
-        self.advanced_book_label = QtWidgets.QLabel(self.advancedTab)
-        self.advanced_book_label.setObjectName('advanced_book_label')
-        self.advancedLayout.addWidget(self.advanced_book_label, 0, 0, QtCore.Qt.AlignRight)
-        self.advanced_book_combo_box = create_horizontal_adjusting_combo_box(self.advancedTab,
-                                                                             'advanced_book_combo_box')
-        self.advanced_book_label.setBuddy(self.advanced_book_combo_box)
-        self.advancedLayout.addWidget(self.advanced_book_combo_box, 0, 1, 1, 2)
-        self.advanced_chapter_label = QtWidgets.QLabel(self.advancedTab)
-        self.advanced_chapter_label.setObjectName('advanced_chapter_label')
-        self.advancedLayout.addWidget(self.advanced_chapter_label, 1, 1, 1, 2)
-        self.advanced_verse_label = QtWidgets.QLabel(self.advancedTab)
-        self.advanced_verse_label.setObjectName('advanced_verse_label')
-        self.advancedLayout.addWidget(self.advanced_verse_label, 1, 2)
-        self.advanced_from_label = QtWidgets.QLabel(self.advancedTab)
-        self.advanced_from_label.setObjectName('advanced_from_label')
-        self.advancedLayout.addWidget(self.advanced_from_label, 3, 0, QtCore.Qt.AlignRight)
-        self.advanced_from_chapter = QtWidgets.QComboBox(self.advancedTab)
-        self.advanced_from_chapter.setObjectName('advanced_from_chapter')
-        self.advancedLayout.addWidget(self.advanced_from_chapter, 3, 1)
-        self.advanced_from_verse = QtWidgets.QComboBox(self.advancedTab)
-        self.advanced_from_verse.setObjectName('advanced_from_verse')
-        self.advancedLayout.addWidget(self.advanced_from_verse, 3, 2)
-        self.advanced_to_label = QtWidgets.QLabel(self.advancedTab)
-        self.advanced_to_label.setObjectName('advanced_to_label')
-        self.advancedLayout.addWidget(self.advanced_to_label, 4, 0, QtCore.Qt.AlignRight)
-        self.advanced_to_chapter = QtWidgets.QComboBox(self.advancedTab)
-        self.advanced_to_chapter.setObjectName('advanced_to_chapter')
-        self.advancedLayout.addWidget(self.advanced_to_chapter, 4, 1)
-        self.advanced_to_verse = QtWidgets.QComboBox(self.advancedTab)
-        self.advanced_to_verse.setObjectName('advanced_to_verse')
-        self.advancedLayout.addWidget(self.advanced_to_verse, 4, 2)
-        self.add_search_fields('advanced', UiStrings().Advanced)
+        # Add the Search tab.
+        self.search_tab = QtWidgets.QWidget()
+        self.search_tab.setSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum)
+        self.search_tab_bar.addTab(translate('BiblesPlugin.MediaItem', 'Find'))
+        self.search_layout = QtWidgets.QFormLayout(self.search_tab)
+        self.search_edit = SearchEdit(self.search_tab, self.settings_section)
+        self.search_layout.addRow(translate('BiblesPlugin.MediaItem', 'Find:'), self.search_edit)
+        self.search_tab.setVisible(True)
+        self.page_layout.addWidget(self.search_tab)
+        # Add the Select tab.
+        self.select_tab = QtWidgets.QWidget()
+        self.select_tab.setSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum)
+        self.search_tab_bar.addTab(translate('BiblesPlugin.MediaItem', 'Select'))
+        self.select_layout = QtWidgets.QFormLayout(self.select_tab)
+        self.book_layout = QtWidgets.QHBoxLayout(self.select_tab)
+        self.select_book_combo_box = create_horizontal_adjusting_combo_box(self.select_tab, 'select_book_combo_box')
+        self.book_layout.addWidget(self.select_book_combo_box)
+        self.book_order_button = QtWidgets.QToolButton()
+        self.book_order_button.setIcon(self.sort_icon)
+        self.book_order_button.setCheckable(True)
+        self.book_layout.addWidget(self.book_order_button)
+        self.select_layout.addRow(translate('BiblesPlugin.MediaItem', 'Book:'), self.book_layout)
+        self.verse_title_layout = QtWidgets.QHBoxLayout(self.select_tab)
+        self.chapter_label = QtWidgets.QLabel(self.select_tab)
+        self.verse_title_layout.addWidget(self.chapter_label)
+        self.verse_label = QtWidgets.QLabel(self.select_tab)
+        self.verse_title_layout.addWidget(self.verse_label)
+        self.select_layout.addRow('', self.verse_title_layout)
+        self.from_layout = QtWidgets.QHBoxLayout(self.select_tab)
+        self.from_chapter = QtWidgets.QComboBox(self.select_tab)
+        self.from_layout.addWidget(self.from_chapter)
+        self.from_verse = QtWidgets.QComboBox(self.select_tab)
+        self.from_layout.addWidget(self.from_verse)
+        self.select_layout.addRow(translate('BiblesPlugin.MediaItem', 'From:'), self.from_layout)
+        self.to_layout = QtWidgets.QHBoxLayout(self.select_tab)
+        self.to_chapter = QtWidgets.QComboBox(self.select_tab)
+        self.to_layout.addWidget(self.to_chapter)
+        self.to_verse = QtWidgets.QComboBox(self.select_tab)
+        self.to_layout.addWidget(self.to_verse)
+        self.select_layout.addRow(translate('BiblesPlugin.MediaItem', 'To:'), self.to_layout)
+        self.select_tab.setVisible(False)
+        self.page_layout.addWidget(self.select_tab)
+        # General Search Opions
+        self.options_widget = QtWidgets.QGroupBox(translate('BiblesPlugin.MediaItem', 'Options'), self)
+        self.general_bible_layout = QtWidgets.QFormLayout(self.options_widget)
+        self.version_combo_box = create_horizontal_adjusting_combo_box(self, 'version_combo_box')
+        self.general_bible_layout.addRow('{version}:'.format(version=UiStrings().Version), self.version_combo_box)
+        self.second_combo_box = create_horizontal_adjusting_combo_box(self, 'second_combo_box')
+        self.general_bible_layout.addRow(translate('BiblesPlugin.MediaItem', 'Second:'), self.second_combo_box)
+        self.style_combo_box = create_horizontal_adjusting_combo_box(self, 'style_combo_box')
+        self.style_combo_box.addItems(['', '', ''])
+        self.general_bible_layout.addRow(UiStrings().LayoutStyle, self.style_combo_box)
+        self.search_button_layout = QtWidgets.QHBoxLayout(self.options_widget)
+        self.search_button_layout.addStretch()
+        # Note: If we use QPushButton instead of the QToolButton, the icon will be larger than the Lock icon.
+        self.clear_button = QtWidgets.QToolButton(self)
+        self.clear_button.setIcon(self.clear_icon)
+        self.lock_button = QtWidgets.QToolButton(self)
+        self.lock_button.setIcon(self.unlock_icon)
+        self.lock_button.setCheckable(True)
+        self.search_button_layout.addWidget(self.clear_button)
+        self.search_button_layout.addWidget(self.lock_button)
+        self.search_button = QtWidgets.QPushButton(self)
+        self.search_button_layout.addWidget(self.search_button)
+        self.general_bible_layout.addRow(self.search_button_layout)
+        self.page_layout.addWidget(self.options_widget)
+
+    def setupUi(self):
+        super().setupUi()
+
+        sort_model = QtCore.QSortFilterProxyModel(self.select_book_combo_box)
+        model = self.select_book_combo_box.model()
+        # Reparent the combo box model to the sort proxy, otherwise it will be deleted when we change the comobox's
+        # model
+        model.setParent(sort_model)
+        sort_model.setSourceModel(model)
+        self.select_book_combo_box.setModel(sort_model)
+
+        # Signals & Slots
         # Combo Boxes
-        self.quickVersionComboBox.activated.connect(self.update_auto_completer)
-        self.quickSecondComboBox.activated.connect(self.update_auto_completer)
-        self.advancedVersionComboBox.activated.connect(self.on_advanced_version_combo_box)
-        self.advancedSecondComboBox.activated.connect(self.on_advanced_second_combo_box)
-        self.advanced_book_combo_box.activated.connect(self.on_advanced_book_combo_box)
-        self.advanced_from_chapter.activated.connect(self.on_advanced_from_chapter)
-        self.advanced_from_verse.activated.connect(self.on_advanced_from_verse)
-        self.advanced_to_chapter.activated.connect(self.on_advanced_to_chapter)
-        self.quick_search_edit.searchTypeChanged.connect(self.update_auto_completer)
-        self.quickVersionComboBox.activated.connect(self.update_auto_completer)
-        self.quickStyleComboBox.activated.connect(self.on_quick_style_combo_box_changed)
-        self.advancedStyleComboBox.activated.connect(self.on_advanced_style_combo_box_changed)
+        self.select_book_combo_box.activated.connect(self.on_advanced_book_combo_box)
+        self.from_chapter.activated.connect(self.on_from_chapter_activated)
+        self.from_verse.activated.connect(self.on_from_verse)
+        self.to_chapter.activated.connect(self.on_to_chapter)
+        self.version_combo_box.currentIndexChanged.connect(self.on_version_combo_box_index_changed)
+        self.version_combo_box.currentIndexChanged.connect(self.update_auto_completer)
+        self.second_combo_box.currentIndexChanged.connect(self.on_second_combo_box_index_changed)
+        self.second_combo_box.currentIndexChanged.connect(self.update_auto_completer)
+        self.style_combo_box.currentIndexChanged.connect(self.on_style_combo_box_index_changed)
+        self.search_edit.searchTypeChanged.connect(self.update_auto_completer)
         # Buttons
-        self.advancedClearButton.clicked.connect(self.on_advanced_clear_button_clicked)
-        self.quickClearButton.clicked.connect(self.on_clear_button_clicked)
-        self.advancedSearchButton.clicked.connect(self.on_advanced_search_button)
-        self.quickSearchButton.clicked.connect(self.on_quick_search_button)
+        self.book_order_button.toggled.connect(self.on_book_order_button_toggled)
+        self.clear_button.clicked.connect(self.on_clear_button_clicked)
+        self.lock_button.toggled.connect(self.on_lock_button_toggled)
+        self.search_button.clicked.connect(self.on_search_button_clicked)
         # Other stuff
-        self.quick_search_edit.returnPressed.connect(self.on_quick_search_button)
+        self.search_edit.returnPressed.connect(self.on_search_button_clicked)
         self.search_tab_bar.currentChanged.connect(self.on_search_tab_bar_current_changed)
-        self.quick_search_edit.textChanged.connect(self.on_search_text_edit_changed)
+        self.search_edit.textChanged.connect(self.on_search_edit_text_changed)
+
+    def retranslateUi(self):
+        log.debug('retranslateUi')
+        self.chapter_label.setText(translate('BiblesPlugin.MediaItem', 'Chapter:'))
+        self.verse_label.setText(translate('BiblesPlugin.MediaItem', 'Verse:'))
+        self.style_combo_box.setItemText(LayoutStyle.VersePerSlide, UiStrings().VersePerSlide)
+        self.style_combo_box.setItemText(LayoutStyle.VersePerLine, UiStrings().VersePerLine)
+        self.style_combo_box.setItemText(LayoutStyle.Continuous, UiStrings().Continuous)
+        self.clear_button.setToolTip(translate('BiblesPlugin.MediaItem', 'Clear the search results.'))
+        self.lock_button.setToolTip(
+            translate('BiblesPlugin.MediaItem', 'Toggle to keep or clear the previous results.'))
+        self.search_button.setText(UiStrings().Search)
 
     def on_focus(self):
-        if self.quickTab.isVisible():
-            self.quick_search_edit.setFocus()
-            self.quick_search_edit.selectAll()
+        """
+        Set focus on the appropriate widget when BibleMediaItem receives focus
+
+        Reimplements MediaManagerItem.on_focus()
+
+        :return: None
+        """
+        if self.search_tab.isVisible():
+            self.search_edit.setFocus()
+            self.search_edit.selectAll()
         else:
-            self.advanced_book_combo_box.setFocus()
+            self.select_book_combo_box.setFocus()
 
     def config_update(self):
+        """
+        Change the visible widgets when the config changes
+
+        :return: None
+        """
         log.debug('config_update')
-        if Settings().value(self.settings_section + '/second bibles'):
-            self.quickSecondLabel.setVisible(True)
-            self.quickSecondComboBox.setVisible(True)
-            self.advancedSecondLabel.setVisible(True)
-            self.advancedSecondComboBox.setVisible(True)
-            self.quickSecondLabel.setVisible(True)
-            self.quickSecondComboBox.setVisible(True)
-        else:
-            self.quickSecondLabel.setVisible(False)
-            self.quickSecondComboBox.setVisible(False)
-            self.advancedSecondLabel.setVisible(False)
-            self.advancedSecondComboBox.setVisible(False)
-            self.quickSecondLabel.setVisible(False)
-            self.quickSecondComboBox.setVisible(False)
-        self.quickStyleComboBox.setCurrentIndex(self.settings.layout_style)
-        self.advancedStyleComboBox.setCurrentIndex(self.settings.layout_style)
-
-    def retranslateUi(self):
-        log.debug('retranslateUi')
-        self.quick_search_label.setText(translate('BiblesPlugin.MediaItem', 'Find:'))
-        self.quickVersionLabel.setText('{version}:'.format(version=UiStrings().Version))
-        self.quickSecondLabel.setText(translate('BiblesPlugin.MediaItem', 'Second:'))
-        self.quickStyleLabel.setText(UiStrings().LayoutStyle)
-        self.quickStyleComboBox.setItemText(LayoutStyle.VersePerSlide, UiStrings().VersePerSlide)
-        self.quickStyleComboBox.setItemText(LayoutStyle.VersePerLine, UiStrings().VersePerLine)
-        self.quickStyleComboBox.setItemText(LayoutStyle.Continuous, UiStrings().Continuous)
-        self.quickClearButton.setToolTip(translate('BiblesPlugin.MediaItem', 'Clear the search results.'))
-        self.quickLockButton.setToolTip(translate('BiblesPlugin.MediaItem',
-                                                  'Toggle to keep or clear the previous results.'))
-        self.quickSearchButton.setText(UiStrings().Search)
-        self.advanced_book_label.setText(translate('BiblesPlugin.MediaItem', 'Book:'))
-        self.advanced_chapter_label.setText(translate('BiblesPlugin.MediaItem', 'Chapter:'))
-        self.advanced_verse_label.setText(translate('BiblesPlugin.MediaItem', 'Verse:'))
-        self.advanced_from_label.setText(translate('BiblesPlugin.MediaItem', 'From:'))
-        self.advanced_to_label.setText(translate('BiblesPlugin.MediaItem', 'To:'))
-        self.advancedVersionLabel.setText('{version}:'.format(version=UiStrings().Version))
-        self.advancedSecondLabel.setText(translate('BiblesPlugin.MediaItem', 'Second:'))
-        self.advancedStyleLabel.setText(UiStrings().LayoutStyle)
-        self.advancedStyleComboBox.setItemText(LayoutStyle.VersePerSlide, UiStrings().VersePerSlide)
-        self.advancedStyleComboBox.setItemText(LayoutStyle.VersePerLine, UiStrings().VersePerLine)
-        self.advancedStyleComboBox.setItemText(LayoutStyle.Continuous, UiStrings().Continuous)
-        self.advancedClearButton.setToolTip(translate('BiblesPlugin.MediaItem', 'Clear the search results.'))
-        self.advancedLockButton.setToolTip(translate('BiblesPlugin.MediaItem',
-                                                     'Toggle to keep or clear the previous results.'))
-        self.advancedSearchButton.setText(UiStrings().Search)
+        visible = Settings().value('{settings_section}/second bibles'.format(settings_section=self.settings_section))
+        self.general_bible_layout.labelForField(self.second_combo_box).setVisible(visible)
+        self.second_combo_box.setVisible(visible)
 
     def initialise(self):
+        """
+        Called to complete initialisation that could not be completed in the constructor.
+
+        :return: None
+        """
         log.debug('bible manager initialise')
         self.plugin.manager.media = self
-        self.load_bibles()
-        self.quick_search_edit.set_search_types([
+        self.populate_bible_combo_boxes()
+        self.search_edit.set_search_types([
             (BibleSearch.Combined, ':/bibles/bibles_search_combined.png',
                 translate('BiblesPlugin.MediaItem', 'Text or Reference'),
                 translate('BiblesPlugin.MediaItem', 'Text or Reference...')),
@@ -328,164 +275,107 @@
                 translate('BiblesPlugin.MediaItem', 'Text Search'),
                 translate('BiblesPlugin.MediaItem', 'Search Text...'))
         ])
-        if Settings().value(self.settings_section + '/reset to combined quick search'):
-            self.quick_search_edit.set_current_search_type(BibleSearch.Combined)
+        if Settings().value(
+                '{settings_section}/reset to combined quick search'.format(settings_section=self.settings_section)):
+            self.search_edit.set_current_search_type(BibleSearch.Combined)
         self.config_update()
         log.debug('bible manager initialise complete')
 
-    def load_bibles(self):
+    def populate_bible_combo_boxes(self):
+        """
+        Populate the bible combo boxes with the list of bibles that have been loaded
+
+        :return: None
+        """
         log.debug('Loading Bibles')
-        self.quickVersionComboBox.clear()
-        self.quickSecondComboBox.clear()
-        self.advancedVersionComboBox.clear()
-        self.advancedSecondComboBox.clear()
-        self.quickSecondComboBox.addItem('')
-        self.advancedSecondComboBox.addItem('')
+        self.version_combo_box.clear()
+        self.second_combo_box.clear()
+        self.second_combo_box.addItem('', None)
         # Get all bibles and sort the list.
-        bibles = list(self.plugin.manager.get_bibles().keys())
-        bibles = [_f for _f in bibles if _f]
-        bibles.sort(key=get_locale_key)
-        # Load the bibles into the combo boxes.
-        self.quickVersionComboBox.addItems(bibles)
-        self.quickSecondComboBox.addItems(bibles)
-        self.advancedVersionComboBox.addItems(bibles)
-        self.advancedSecondComboBox.addItems(bibles)
+        bibles = self.plugin.manager.get_bibles()
+        bibles = [(_f, bibles[_f]) for _f in bibles if _f]
+        bibles.sort(key=lambda k: get_locale_key(k[0]))
+        for bible in bibles:
+            self.version_combo_box.addItem(bible[0], bible[1])
+            self.second_combo_box.addItem(bible[0], bible[1])
         # set the default value
-        bible = Settings().value(self.settings_section + '/advanced bible')
-        if bible in bibles:
-            find_and_set_in_combo_box(self.advancedVersionComboBox, bible)
-            self.initialise_advanced_bible(str(bible))
-        elif bibles:
-            self.initialise_advanced_bible(bibles[0])
-        bible = Settings().value(self.settings_section + '/quick bible')
-        find_and_set_in_combo_box(self.quickVersionComboBox, bible)
-
-    def reload_bibles(self, process=False):
+        bible = Settings().value('{settings_section}/primary bible'.format(settings_section=self.settings_section))
+        find_and_set_in_combo_box(self.version_combo_box, bible)
+
+    def reload_bibles(self):
+        """
+        Reload the bibles and update the combo boxes
+
+        :return: None
+        """
         log.debug('Reloading Bibles')
         self.plugin.manager.reload_bibles()
-        self.load_bibles()
-        # If called from first time wizard re-run, process any new bibles.
-        if process:
-            self.plugin.app_startup()
-        self.update_auto_completer()
-
-    def initialise_advanced_bible(self, bible, last_book_id=None):
+        self.populate_bible_combo_boxes()
+
+    def get_common_books(self, first_bible, second_bible=None):
+        """
+        Return a list of common books between two bibles.
+
+        :param first_bible: The first bible (BibleDB)
+        :param second_bible: The second bible. (Optional, BibleDB
+        :return: A list of common books between the two bibles. Or if only one bible is supplied a list of that bibles
+                books (list of Book objects)
+        """
+        if not second_bible:
+            return first_bible.get_books()
+        book_data = []
+        for book in first_bible.get_books():
+            for second_book in second_bible.get_books():
+                if book.book_reference_id == second_book.book_reference_id:
+                    book_data.append(book)
+        return book_data
+
+    def initialise_advanced_bible(self, last_book=None):
         """
         This initialises the given bible, which means that its book names and their chapter numbers is added to the
-        combo boxes on the 'Advanced Search' Tab. This is not of any importance of the 'Quick Search' Tab.
+        combo boxes on the 'Select' Tab. This is not of any importance of the 'Search' Tab.
 
-        :param bible: The bible to initialise (unicode).
         :param last_book_id: The "book reference id" of the book which is chosen at the moment. (int)
+        :return: None
         """
-        log.debug('initialise_advanced_bible {bible}, {ref}'.format(bible=bible, ref=last_book_id))
-        book_data = self.plugin.manager.get_books(bible)
-        second_bible = self.advancedSecondComboBox.currentText()
-        if second_bible != '':
-            second_book_data = self.plugin.manager.get_books(second_bible)
-            book_data_temp = []
-            for book in book_data:
-                for second_book in second_book_data:
-                    if book['book_reference_id'] == second_book['book_reference_id']:
-                        book_data_temp.append(book)
-            book_data = book_data_temp
-        self.advanced_book_combo_box.clear()
-        first = True
-        initialise_chapter_verse = False
-        language_selection = self.plugin.manager.get_language_selection(bible)
-        book_names = BibleStrings().BookNames
+        log.debug('initialise_advanced_bible {bible}, {ref}'.format(bible=self.bible, ref=last_book))
+        self.select_book_combo_box.clear()
+        if self.bible is None:
+            return
+        book_data = self.get_common_books(self.bible, self.second_bible)
+        language_selection = self.plugin.manager.get_language_selection(self.bible.name)
+        self.select_book_combo_box.model().setDynamicSortFilter(False)
         for book in book_data:
-            row = self.advanced_book_combo_box.count()
-            if language_selection == LanguageSelection.Bible:
-                self.advanced_book_combo_box.addItem(book['name'])
-            elif language_selection == LanguageSelection.Application:
-                data = BiblesResourcesDB.get_book_by_id(book['book_reference_id'])
-                self.advanced_book_combo_box.addItem(book_names[data['abbreviation']])
-            elif language_selection == LanguageSelection.English:
-                data = BiblesResourcesDB.get_book_by_id(book['book_reference_id'])
-                self.advanced_book_combo_box.addItem(data['name'])
-            self.advanced_book_combo_box.setItemData(row, book['book_reference_id'])
-            if first:
-                first = False
-                first_book = book
-                initialise_chapter_verse = True
-            if last_book_id and last_book_id == int(book['book_reference_id']):
-                index = self.advanced_book_combo_box.findData(book['book_reference_id'])
-                if index == -1:
-                    # Not Found.
-                    index = 0
-                self.advanced_book_combo_box.setCurrentIndex(index)
-                initialise_chapter_verse = False
-        if initialise_chapter_verse:
-            self.initialise_chapter_verse(bible, first_book['name'], first_book['book_reference_id'])
-
-    def initialise_chapter_verse(self, bible, book, book_ref_id):
-        log.debug('initialise_chapter_verse {bible}, {book}, {ref}'.format(bible=bible, book=book, ref=book_ref_id))
-        book = self.plugin.manager.get_book_by_id(bible, book_ref_id)
-        self.chapter_count = self.plugin.manager.get_chapter_count(bible, book)
-        verse_count = self.plugin.manager.get_verse_count_by_book_ref_id(bible, book_ref_id, 1)
-        if verse_count == 0:
-            self.advancedSearchButton.setEnabled(False)
-            log.warning('Not enough chapters in %s', book_ref_id)
-            critical_error_message_box(message=translate('BiblesPlugin.MediaItem', 'Bible not fully loaded.'))
-        else:
-            self.advancedSearchButton.setEnabled(True)
-            self.adjust_combo_box(1, self.chapter_count, self.advanced_from_chapter)
-            self.adjust_combo_box(1, self.chapter_count, self.advanced_to_chapter)
-            self.adjust_combo_box(1, verse_count, self.advanced_from_verse)
-            self.adjust_combo_box(1, verse_count, self.advanced_to_verse)
+            self.select_book_combo_box.addItem(book.get_name(language_selection), book.book_reference_id)
+        self.select_book_combo_box.model().setDynamicSortFilter(True)
+        if last_book:
+            index = self.select_book_combo_box.findData(last_book)
+            self.select_book_combo_box.setCurrentIndex(index if index != -1 else 0)
+        self.on_advanced_book_combo_box()
 
     def update_auto_completer(self):
         """
         This updates the bible book completion list for the search field. The completion depends on the bible. It is
         only updated when we are doing reference or combined search, in text search the completion list is removed.
+
+        :return: None
         """
-        log.debug('update_auto_completer')
-        # Save the current bible to the configuration.
-        Settings().setValue('{section}/quick bible'.format(section=self.settings_section),
-                            self.quickVersionComboBox.currentText())
         books = []
         # We have to do a 'Reference Search' (Or as part of Combined Search).
-        if self.quick_search_edit.current_search_type() is not BibleSearch.Text:
-            bibles = self.plugin.manager.get_bibles()
-            bible = self.quickVersionComboBox.currentText()
-            if bible:
-                book_data = bibles[bible].get_books()
-                second_bible = self.quickSecondComboBox.currentText()
-                if second_bible != '':
-                    second_book_data = bibles[second_bible].get_books()
-                    book_data_temp = []
-                    for book in book_data:
-                        for second_book in second_book_data:
-                            if book.book_reference_id == second_book.book_reference_id:
-                                book_data_temp.append(book)
-                    book_data = book_data_temp
-                language_selection = self.plugin.manager.get_language_selection(bible)
-                if language_selection == LanguageSelection.Bible:
-                    books = [book.name + ' ' for book in book_data]
-                elif language_selection == LanguageSelection.Application:
-                    book_names = BibleStrings().BookNames
-                    for book in book_data:
-                        data = BiblesResourcesDB.get_book_by_id(book.book_reference_id)
-                        books.append(str(book_names[data['abbreviation']]) + ' ')
-                elif language_selection == LanguageSelection.English:
-                    for book in book_data:
-                        data = BiblesResourcesDB.get_book_by_id(book.book_reference_id)
-                        books.append(data['name'] + ' ')
+        if self.search_edit.current_search_type() is not BibleSearch.Text:
+            if self.bible:
+                book_data = self.get_common_books(self.bible, self.second_bible)
+                language_selection = self.plugin.manager.get_language_selection(self.bible.name)
+                books = [book.get_name(language_selection) for book in book_data]
                 books.sort(key=get_locale_key)
-        set_case_insensitive_completer(books, self.quick_search_edit)
-
-    def on_second_bible_combobox_index_changed(self, selection):
-        """
-        Activate the style combobox only when no second bible is selected
-        """
-        if selection == 0:
-            self.quickStyleComboBox.setEnabled(True)
-            self.advancedStyleComboBox.setEnabled(True)
-        else:
-            self.quickStyleComboBox.setEnabled(False)
-            self.advancedStyleComboBox.setEnabled(False)
+        set_case_insensitive_completer(books, self.search_edit)
 
     def on_import_click(self):
+        """
+        Create, if not already, the `BibleImportForm` and execute it
+
+        :return: None
+        """
         if not hasattr(self, 'import_wizard'):
             self.import_wizard = BibleImportForm(self, self.plugin.manager, self.plugin)
         # If the import was not cancelled then reload.
@@ -493,141 +383,199 @@
             self.reload_bibles()
 
     def on_edit_click(self):
-        if self.quickTab.isVisible():
-            bible = self.quickVersionComboBox.currentText()
-        elif self.advancedTab.isVisible():
-            bible = self.advancedVersionComboBox.currentText()
-        if bible:
+        """
+        Load the EditBibleForm and reload the bibles if the user accepts it
+
+        :return: None
+        """
+        if self.bible:
             self.edit_bible_form = EditBibleForm(self, self.main_window, self.plugin.manager)
-            self.edit_bible_form.load_bible(bible)
+            self.edit_bible_form.load_bible(self.bible.name)
             if self.edit_bible_form.exec():
                 self.reload_bibles()
 
     def on_delete_click(self):
         """
-        When the delete button is pressed
+        Confirm that the user wants to delete the main bible
+
+        :return: None
         """
-        bible = None
-        if self.quickTab.isVisible():
-            bible = self.quickVersionComboBox.currentText()
-        elif self.advancedTab.isVisible():
-            bible = self.advancedVersionComboBox.currentText()
-        if bible:
+        if self.bible:
             if QtWidgets.QMessageBox.question(
-                    self, UiStrings().ConfirmDelete,
-                    translate('BiblesPlugin.MediaItem',
-                              'Are you sure you want to completely delete "{bible}" Bible '
-                              'from OpenLP?\n\nYou will need to re-import this Bible to use it '
-                              'again.').format(bible=bible),
-                    QtWidgets.QMessageBox.StandardButtons(QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No),
-                    QtWidgets.QMessageBox.Yes) == QtWidgets.QMessageBox.No:
+                self, UiStrings().ConfirmDelete,
+                translate('BiblesPlugin.MediaItem',
+                          'Are you sure you want to completely delete "{bible}" Bible from OpenLP?\n\n'
+                          'You will need to re-import this Bible to use it again.').format(bible=self.bible.name),
+                    defaultButton=QtWidgets.QMessageBox.No) == QtWidgets.QMessageBox.No:
                 return
-            self.plugin.manager.delete_bible(bible)
+            self.plugin.manager.delete_bible(self.bible.name)
             self.reload_bibles()
 
     def on_search_tab_bar_current_changed(self, index):
-        if index == 0:
-            self.advancedTab.setVisible(False)
-            self.quickTab.setVisible(True)
-            self.quick_search_edit.setFocus()
+        """
+        Show the selected tab and set focus to it
+
+        :param index: The tab selected (int)
+        :return: None
+        """
+        search_tab = index == 0
+        self.search_tab.setVisible(search_tab)
+        self.select_tab.setVisible(not search_tab)
+        self.on_focus()
+
+    def on_book_order_button_toggled(self, checked):
+        """
+        Change the sort order of the book names
+
+        :param checked: Indicates if the button is checked or not (Bool)
+        :return: None
+        """
+        if checked:
+            self.select_book_combo_box.model().sort(0)
         else:
-            self.quickTab.setVisible(False)
-            self.advancedTab.setVisible(True)
-            self.advanced_book_combo_box.setFocus()
+            # -1 Removes the sorting, and returns the items to the order they were added in
+            self.select_book_combo_box.model().sort(-1)
 
     def on_clear_button_clicked(self):
-        # Clear the list, then set the "No search Results" message, then clear the text field and give it focus.
-        self.list_view.clear()
-        self.quick_search_edit.clear()
-        self.quick_search_edit.setFocus()
+        """
+        Clear the list_view and the search_edit
 
-    def on_advanced_clear_button_clicked(self):
-        # The same as the on_clear_button_clicked, but gives focus to Book name field in "Select" (advanced).
+        :return: None
+        """
         self.list_view.clear()
-        self.advanced_book_combo_box.setFocus()
+        self.search_edit.clear()
+        self.on_focus()
 
     def on_lock_button_toggled(self, checked):
         """
         Toggle the lock button, if Search tab is used, set focus to search field.
-        :param checked: The state of the toggle button. bool
+
+        :param checked: The state of the toggle button. (bool)
         :return: None
         """
+        self.list_view.locked = checked
         if checked:
             self.sender().setIcon(self.lock_icon)
         else:
             self.sender().setIcon(self.unlock_icon)
-        if self.quickTab.isVisible():
-            self.quick_search_edit.setFocus()
-
-    def on_quick_style_combo_box_changed(self):
-        self.settings.layout_style = self.quickStyleComboBox.currentIndex()
-        self.advancedStyleComboBox.setCurrentIndex(self.settings.layout_style)
-        self.settings.layout_style_combo_box.setCurrentIndex(self.settings.layout_style)
-        Settings().setValue(self.settings_section + '/verse layout style', self.settings.layout_style)
-
-    def on_advanced_style_combo_box_changed(self):
-        self.settings.layout_style = self.advancedStyleComboBox.currentIndex()
-        self.quickStyleComboBox.setCurrentIndex(self.settings.layout_style)
-        self.settings.layout_style_combo_box.setCurrentIndex(self.settings.layout_style)
-        Settings().setValue(self.settings_section + '/verse layout style', self.settings.layout_style)
-
-    def on_advanced_version_combo_box(self):
-        Settings().setValue(self.settings_section + '/advanced bible', self.advancedVersionComboBox.currentText())
-        self.initialise_advanced_bible(
-            self.advancedVersionComboBox.currentText(),
-            self.advanced_book_combo_box.itemData(int(self.advanced_book_combo_box.currentIndex())))
-
-    def on_advanced_second_combo_box(self):
-        self.initialise_advanced_bible(
-            self.advancedVersionComboBox.currentText(),
-            self.advanced_book_combo_box.itemData(int(self.advanced_book_combo_box.currentIndex())))
+
+    def on_style_combo_box_index_changed(self, index):
+        """
+        Change the layout style and save the setting
+
+        :param index: The index of the current item in the combobox (int)
+        :return: None
+        """
+        # TODO: Change layout_style to a property
+        self.settings.layout_style = index
+        self.settings.layout_style_combo_box.setCurrentIndex(index)
+        Settings().setValue('{section}/verse layout style'.format(section=self.settings_section), index)
+
+    def on_version_combo_box_index_changed(self):
+        """
+        Update the main bible and save it to settings
+
+        :return: None
+        """
+        self.bible = self.version_combo_box.currentData()
+        if self.bible is not None:
+            Settings().setValue('{section}/primary bible'.format(section=self.settings_section), self.bible.name)
+        self.initialise_advanced_bible(self.select_book_combo_box.currentData())
+
+    def on_second_combo_box_index_changed(self, selection):
+        """
+        Update the second bible. If changing from single to dual bible modes as if the user wants to clear the search
+        results, if not revert to the previously selected bible
+
+        :return: None
+        """
+        new_selection = self.second_combo_box.currentData()
+        if self.list_view.count():
+            # Exclusive or (^) the new and previous selections to detect if the user has switched between single and
+            # dual bible mode
+            if (new_selection is None) ^ (self.second_bible is None):
+                if critical_error_message_box(
+                    message=translate('BiblesPlugin.MediaItem',
+                                      'OpenLP cannot combine single and dual Bible verse search results. '
+                                      'Do you want to clear your search results and start a new search?'),
+                        parent=self, question=True) == QtWidgets.QMessageBox.Yes:
+                    self.list_view.clear(override_lock=True)
+                else:
+                    self.second_combo_box.setCurrentIndex(self.second_combo_box.findData(self.second_bible))
+                    return
+        self.second_bible = new_selection
+        if new_selection is None:
+            self.style_combo_box.setEnabled(True)
+        else:
+            self.style_combo_box.setEnabled(False)
+            self.initialise_advanced_bible(self.select_book_combo_box.currentData())
 
     def on_advanced_book_combo_box(self):
-        item = int(self.advanced_book_combo_box.currentIndex())
-        self.initialise_chapter_verse(
-            self.advancedVersionComboBox.currentText(),
-            self.advanced_book_combo_box.currentText(),
-            self.advanced_book_combo_box.itemData(item))
-
-    def on_advanced_from_verse(self):
-        chapter_from = int(self.advanced_from_chapter.currentText())
-        chapter_to = int(self.advanced_to_chapter.currentText())
+        """
+        Update the verse selection boxes
+
+        :return: None
+        """
+        book_ref_id = self.select_book_combo_box.currentData()
+        book = self.plugin.manager.get_book_by_id(self.bible.name, book_ref_id)
+        self.chapter_count = self.plugin.manager.get_chapter_count(self.bible.name, book)
+        verse_count = self.plugin.manager.get_verse_count_by_book_ref_id(self.bible.name, book_ref_id, 1)
+        if verse_count == 0:
+            self.search_button.setEnabled(False)
+            log.warning('Not enough chapters in %s', book_ref_id)
+            critical_error_message_box(message=translate('BiblesPlugin.MediaItem', 'Bible not fully loaded.'))
+        else:
+            self.search_button.setEnabled(True)
+            self.adjust_combo_box(1, self.chapter_count, self.from_chapter)
+            self.adjust_combo_box(1, self.chapter_count, self.to_chapter)
+            self.adjust_combo_box(1, verse_count, self.from_verse)
+            self.adjust_combo_box(1, verse_count, self.to_verse)
+
+    def on_from_chapter_activated(self):
+        """
+        Update the verse selection boxes
+
+        :return: None
+        """
+        book_ref_id = self.select_book_combo_box.currentData()
+        chapter_from = self.from_chapter.currentData()
+        chapter_to = self.to_chapter.currentData()
+        verse_count = self.plugin.manager.get_verse_count_by_book_ref_id(self.bible.name, book_ref_id, chapter_from)
+        self.adjust_combo_box(1, verse_count, self.from_verse)
+        if chapter_from >= chapter_to:
+            self.adjust_combo_box(1, verse_count, self.to_verse, chapter_from == chapter_to)
+        self.adjust_combo_box(chapter_from, self.chapter_count, self.to_chapter, chapter_from < chapter_to)
+
+    def on_from_verse(self):
+        """
+        Update the verse selection boxes
+
+        :return: None
+        """
+        chapter_from = self.from_chapter.currentData()
+        chapter_to = self.to_chapter.currentData()
         if chapter_from == chapter_to:
-            bible = self.advancedVersionComboBox.currentText()
-            book_ref_id = self.advanced_book_combo_box.itemData(int(self.advanced_book_combo_box.currentIndex()))
-            verse_from = int(self.advanced_from_verse.currentText())
-            verse_count = self.plugin.manager.get_verse_count_by_book_ref_id(bible, book_ref_id, chapter_to)
-            self.adjust_combo_box(verse_from, verse_count, self.advanced_to_verse, True)
-
-    def on_advanced_to_chapter(self):
-        bible = self.advancedVersionComboBox.currentText()
-        book_ref_id = self.advanced_book_combo_box.itemData(int(self.advanced_book_combo_box.currentIndex()))
-        chapter_from = int(self.advanced_from_chapter.currentText())
-        chapter_to = int(self.advanced_to_chapter.currentText())
-        verse_from = int(self.advanced_from_verse.currentText())
-        verse_to = int(self.advanced_to_verse.currentText())
-        verse_count = self.plugin.manager.get_verse_count_by_book_ref_id(bible, book_ref_id, chapter_to)
+            book_ref_id = self.select_book_combo_box.currentData()
+            verse_from = self.from_verse.currentData()
+            verse_count = self.plugin.manager.get_verse_count_by_book_ref_id(self.bible.name, book_ref_id, chapter_to)
+            self.adjust_combo_box(verse_from, verse_count, self.to_verse, True)
+
+    def on_to_chapter(self):
+        """
+        Update the verse selection boxes
+
+        :return: None
+        """
+        book_ref_id = self.select_book_combo_box.currentData()
+        chapter_from = self.from_chapter.currentData()
+        chapter_to = self.to_chapter.currentData()
+        verse_from = self.from_verse.currentData()
+        verse_to = self.to_verse.currentData()
+        verse_count = self.plugin.manager.get_verse_count_by_book_ref_id(self.bible.name, book_ref_id, chapter_to)
         if chapter_from == chapter_to and verse_from > verse_to:
-            self.adjust_combo_box(verse_from, verse_count, self.advanced_to_verse)
-        else:
-            self.adjust_combo_box(1, verse_count, self.advanced_to_verse)
-
-    def on_advanced_from_chapter(self):
-        bible = self.advancedVersionComboBox.currentText()
-        book_ref_id = self.advanced_book_combo_box.itemData(
-            int(self.advanced_book_combo_box.currentIndex()))
-        chapter_from = int(self.advanced_from_chapter.currentText())
-        chapter_to = int(self.advanced_to_chapter.currentText())
-        verse_count = self.plugin.manager.get_verse_count_by_book_ref_id(bible, book_ref_id, chapter_from)
-        self.adjust_combo_box(1, verse_count, self.advanced_from_verse)
-        if chapter_from > chapter_to:
-            self.adjust_combo_box(1, verse_count, self.advanced_to_verse)
-            self.adjust_combo_box(chapter_from, self.chapter_count, self.advanced_to_chapter)
-        elif chapter_from == chapter_to:
-            self.adjust_combo_box(chapter_from, self.chapter_count, self.advanced_to_chapter)
-            self.adjust_combo_box(1, verse_count, self.advanced_to_verse, True)
-        else:
-            self.adjust_combo_box(chapter_from, self.chapter_count, self.advanced_to_chapter, True)
+            self.adjust_combo_box(verse_from, verse_count, self.to_verse)
+        else:
+            self.adjust_combo_box(1, verse_count, self.to_verse)
 
     def adjust_combo_box(self, range_from, range_to, combo, restore=False):
         """
@@ -640,380 +588,195 @@
         """
         log.debug('adjust_combo_box {box}, {start}, {end}'.format(box=combo, start=range_from, end=range_to))
         if restore:
-            old_text = combo.currentText()
+            old_selection = combo.currentData()
         combo.clear()
-        combo.addItems(list(map(str, list(range(range_from, range_to + 1)))))
-        if restore and combo.findText(old_text) != -1:
-            combo.setCurrentIndex(combo.findText(old_text))
-
-    def on_advanced_search_button(self):
-        """
-        Does an advanced search and saves the search results.
-        """
-        log.debug('Advanced Search Button clicked')
-        self.advancedSearchButton.setEnabled(False)
+        for item in range(range_from, range_to + 1):
+            combo.addItem(str(item), item)
+        if restore:
+            index = combo.findData(old_selection)
+            combo.setCurrentIndex(index if index != -1 else 0)
+
+    def on_search_button_clicked(self):
+        """
+        Call the correct search function depending on which tab the user is using
+
+        :return: None
+        """
+        if not self.bible:
+            self.main_window.information_message(UiStrings().BibleNoBiblesTitle, UiStrings().BibleNoBibles)
+            return
+        self.search_button.setEnabled(False)
+        self.application.set_busy_cursor()
         self.application.process_events()
-        bible = self.advancedVersionComboBox.currentText()
-        second_bible = self.advancedSecondComboBox.currentText()
-        book = self.advanced_book_combo_box.currentText()
-        book_ref_id = self.advanced_book_combo_box.itemData(int(self.advanced_book_combo_box.currentIndex()))
-        chapter_from = self.advanced_from_chapter.currentText()
-        chapter_to = self.advanced_to_chapter.currentText()
-        verse_from = self.advanced_from_verse.currentText()
-        verse_to = self.advanced_to_verse.currentText()
-        verse_separator = get_reference_separator('sep_v_display')
-        range_separator = get_reference_separator('sep_r_display')
-        verse_range = chapter_from + verse_separator + verse_from + range_separator + chapter_to + \
-            verse_separator + verse_to
-        verse_text = '{book} {verse}'.format(book=book, verse=verse_range)
-        self.application.set_busy_cursor()
-        self.search_results = self.plugin.manager.get_verses(bible, verse_text, book_ref_id)
-        if second_bible:
-            self.second_search_results = self.plugin.manager.get_verses(second_bible, verse_text, book_ref_id)
-        if not self.advancedLockButton.isChecked():
-            self.list_view.clear()
-        if self.list_view.count() != 0:
-            self.__check_second_bible(bible, second_bible)
-        elif self.search_results:
-            self.display_results(bible, second_bible)
-        self.advancedSearchButton.setEnabled(True)
+        if self.search_tab.isVisible():
+            self.text_search()
+        elif self.select_tab.isVisible():
+            self.select_search()
+        self.search_button.setEnabled(True)
         self.application.set_normal_cursor()
 
-    def on_quick_reference_search(self):
+    def select_search(self):
+        """
+        Preform a search using the passage selected on the `Select` tab
+
+        :return: None
+        """
+        verse_range = self.plugin.manager.process_verse_range(
+            self.select_book_combo_box.currentData(), self.from_chapter.currentData(), self.from_verse.currentData(),
+            self.to_chapter.currentData(), self.to_verse.currentData())
+        self.search_results = self.plugin.manager.get_verses(self.bible.name, verse_range, False)
+        if self.second_bible:
+            self.second_search_results = self.plugin.manager.get_verses(self.second_bible.name, verse_range, False)
+        self.display_results()
+
+    def text_reference_search(self, search_text):
         """
         We are doing a 'Reference Search'.
-        This search is called on def on_quick_search_button by Quick Reference and Combined Searches.
-        """
-        # Set Bibles to use the text input from Quick search field.
-        bible = self.quickVersionComboBox.currentText()
-        second_bible = self.quickSecondComboBox.currentText()
-        """
-        Get input from field and replace 'A-Z + . ' with ''
-        This will check if field has any '.' after A-Z and removes them. Eg. Gen. 1 = Ge 1 = Genesis 1
-        If Book name has '.' after number. eg. 1. Genesis, the search fails without the dot, and vice versa.
-        A better solution would be to make '.' optional in the search results. Current solution was easier to code.
-        """
-        text = self.quick_search_edit.text()
-        text = re.sub('\D[.]\s', ' ', text)
-        # This is triggered on reference search, use the search from manager.py
-        if self.quick_search_edit.current_search_type() != BibleSearch.Text:
-            self.search_results = self.plugin.manager.get_verses(bible, text)
-        if second_bible and self.search_results:
-            self.second_search_results = \
-                self.plugin.manager.get_verses(second_bible, text, self.search_results[0].book.book_reference_id)
-
-    def on_quick_text_search(self):
+        This search is called on def text_search by Reference and Combined Searches.
+
+        :return: None
+        """
+        verse_refs = self.plugin.manager.parse_ref(self.bible.name, search_text)
+        self.search_results = self.plugin.manager.get_verses(self.bible.name, verse_refs, True)
+        if self.second_bible and self.search_results:
+            self.search_results = self.plugin.manager.get_verses(self.second_bible.name, verse_refs, True)
+        self.display_results()
+
+    def on_text_search(self, text, search_while_type=False):
         """
         We are doing a 'Text Search'.
-        This search is called on def on_quick_search_button by Quick Text and Combined Searches.
+        This search is called on def text_search by 'Search' Text and Combined Searches.
         """
-        # Set Bibles to use the text input from Quick search field.
-        bible = self.quickVersionComboBox.currentText()
-        second_bible = self.quickSecondComboBox.currentText()
-        text = self.quick_search_edit.text()
-        # If Text search ends with "," OpenLP will crash, prevent this from happening by removing all ","s.
-        text = re.sub('[,]', '', text)
-        self.application.set_busy_cursor()
-        # Get Bibles list
-        bibles = self.plugin.manager.get_bibles()
-        # Add results to "search_results"
-        self.search_results = self.plugin.manager.verse_search(bible, second_bible, text)
-        if second_bible and self.search_results:
-            # new_search_results is needed to make sure 2nd bible contains all verses. (And counting them on error)
-            text = []
-            new_search_results = []
-            count = 0
-            passage_not_found = False
-            # Search second bible for results of search_results to make sure everythigns there.
-            # Count all the unfound passages.
+        self.search_results = self.plugin.manager.verse_search(self.bible.name, text)
+        if self.second_bible and self.search_results:
+            filtered_search_results = []
+            not_found_count = 0
             for verse in self.search_results:
-                db_book = bibles[second_bible].get_book_by_book_ref_id(verse.book.book_reference_id)
-                if not db_book:
-                    log.debug('Passage "{name} {chapter:d}:{verse:d}" not found in '
-                              'Second Bible'.format(name=verse.book.name, chapter=verse.chapter, verse=verse.verse))
-                    passage_not_found = True
-                    count += 1
-                    continue
-                new_search_results.append(verse)
-                text.append((verse.book.book_reference_id, verse.chapter, verse.verse, verse.verse))
-            if passage_not_found:
-                # This is for the 2nd Bible.
+                second_verse = self.second_bible.get_verses(
+                    [(verse.book.book_reference_id, verse.chapter, verse.verse, verse.verse)], False)
+                if second_verse:
+                    filtered_search_results.append(verse)
+                    self.second_search_results += second_verse
+                else:
+                    log.debug('Verse "{name} {chapter:d}:{verse:d}" not found in Second Bible "{bible_name}"'.format(
+                        name=verse.book.name, chapter=verse.chapter,
+                        verse=verse.verse, bible_name=self.second_bible.name))
+                    not_found_count += 1
+            self.search_results = filtered_search_results
+            if not_found_count != 0 and not search_while_type:
                 self.main_window.information_message(
-                    translate('BiblesPlugin.MediaItem', 'Information'),
-                    translate('BiblesPlugin.MediaItem', 'The second Bible does not contain all the verses '
-                                                        'that are in the main Bible.\nOnly verses found in both Bibles'
-                                                        ' will be shown.\n\n{count:d} verses have not been included '
-                                                        'in the results.').format(count=count))
-            # Join the searches so only verses that are found on both Bibles are shown.
-            self.search_results = new_search_results
-            self.second_search_results = bibles[second_bible].get_verses(text)
-
-    def on_quick_text_search_while_typing(self):
-        """
-        We are doing a 'Text Search' while typing
-        Call the verse_search_while_typing from manager.py
-        It does not show web bible errors while typing.
-        (It would result in the error popping every time a char is entered or removed)
-        """
-        # Set Bibles to use the text input from Quick search field.
-        bible = self.quickVersionComboBox.currentText()
-        second_bible = self.quickSecondComboBox.currentText()
-        text = self.quick_search_edit.text()
-        # If Text search ends with "," OpenLP will crash, prevent this from happening by removing all ","s.
-        text = re.sub('[,]', '', text)
-        self.application.set_busy_cursor()
-        # Get Bibles list
-        bibles = self.plugin.manager.get_bibles()
-        # Add results to "search_results"
-        self.search_results = self.plugin.manager.verse_search_while_typing(bible, second_bible, text)
-        if second_bible and self.search_results:
-            # new_search_results is needed to make sure 2nd bible contains all verses. (And counting them on error)
-            text = []
-            new_search_results = []
-            count = 0
-            passage_not_found = False
-            # Search second bible for results of search_results to make sure everythigns there.
-            # Count all the unfound passages. Even thou no error is shown, this needs to be done or
-            # the code breaks later on.
-            for verse in self.search_results:
-                db_book = bibles[second_bible].get_book_by_book_ref_id(verse.book.book_reference_id)
-                if not db_book:
-                    log.debug('Passage ("{versebookname}","{versechapter}","{verseverse}") not found in Second Bible'
-                              .format(versebookname=verse.book.name, versechapter='verse.chapter',
-                                      verseverse=verse.verse))
-                    count += 1
-                    continue
-                new_search_results.append(verse)
-                text.append((verse.book.book_reference_id, verse.chapter, verse.verse, verse.verse))
-            # Join the searches so only verses that are found on both Bibles are shown.
-            self.search_results = new_search_results
-            self.second_search_results = bibles[second_bible].get_verses(text)
-
-    def on_quick_search_button(self):
-        """
-        This triggers the proper Quick search based on which search type is used.
+                    translate('BiblesPlugin.MediaItem', 'Verses not found'),
+                    translate('BiblesPlugin.MediaItem',
+                              'The second Bible "{second_name}" does not contain all the verses that are in the main '
+                              'Bible "{name}".\nOnly verses found in both Bibles will be shown.\n\n'
+                              '{count:d} verses have not been included in the results.'
+                              ).format(second_name=self.second_bible.name, name=self.bible.name, count=not_found_count))
+        self.display_results()
+
+    def text_search(self, search_while_type=False):
+        """
+        This triggers the proper 'Search' search based on which search type is used.
         "Eg. "Reference Search", "Text Search" or "Combined search".
         """
-        log.debug('Quick Search Button clicked')
-        self.quickSearchButton.setEnabled(False)
-        self.application.process_events()
-        bible = self.quickVersionComboBox.currentText()
-        second_bible = self.quickSecondComboBox.currentText()
-        text = self.quick_search_edit.text()
-        if self.quick_search_edit.current_search_type() == BibleSearch.Reference:
-            # We are doing a 'Reference Search'. (Get script from def on_quick_reference_search)
-            self.on_quick_reference_search()
-            # Get reference separators from settings.
-            if not self.search_results:
-                reference_separators = {
-                    'verse': get_reference_separator('sep_v_display'),
-                    'range': get_reference_separator('sep_r_display'),
-                    'list': get_reference_separator('sep_l_display')}
+        log.debug('text_search called')
+        text = self.search_edit.text()
+        if text == '':
+            self.list_view.clear()
+            return
+        self.list_view.clear(search_while_typing=search_while_type)
+        if self.search_edit.current_search_type() == BibleSearch.Reference:
+            if get_reference_match('full').match(text):
+                # Valid reference found. Do reference search.
+                self.text_reference_search(text)
+            elif not search_while_type:
                 self.main_window.information_message(
                     translate('BiblesPlugin.BibleManager', 'Scripture Reference Error'),
-                    translate('BiblesPlugin.BibleManager', '<strong>OpenLP couldn’t find anything '
-                                                           'with your search.<br><br>'
-                                                           'Please make sure that your reference follows '
-                                                           'one of these patterns:</strong><br><br>%s'
-                              % UiStrings().BibleScriptureError % reference_separators))
-        elif self.quick_search_edit.current_search_type() == BibleSearch.Text:
-            # We are doing a 'Text Search'. (Get script from def on_quick_text_search)
-            self.on_quick_text_search()
-            if not self.search_results and len(text) - text.count(' ') < 3 and bible:
-                self.main_window.information_message(
-                    UiStrings().BibleShortSearchTitle,
-                    UiStrings().BibleShortSearch)
-        elif self.quick_search_edit.current_search_type() == BibleSearch.Combined:
-            # We are doing a 'Combined search'. Starting with reference search.
-            # Perform only if text contains any numbers
-            if (char.isdigit() for char in text):
-                self.on_quick_reference_search()
-            """
-            If results are found, search will be finalized.
-            This check needs to be here in order to avoid duplicate errors.
-            If keyword is shorter than 3 (not including spaces), message is given. It's actually possible to find
-            verses with less than 3 chars (Eg. G1 = Genesis 1) thus this error is not shown if any results are found.
-            if no Bibles are installed, this message is not shown - "No bibles" message is shown instead.
-            """
-            if not self.search_results and len(text) - text.count(' ') < 3 and bible:
-                self.main_window.information_message(
-                    UiStrings().BibleShortSearchTitle,
-                    UiStrings().BibleShortSearch)
-            if not self.search_results and len(text) - text.count(' ') > 2 and bible:
-                # Text search starts here if no reference was found and keyword is longer than 2.
-                #  > 2 check is required in order to avoid duplicate error messages for short keywords.
-                self.on_quick_text_search()
-                if not self.search_results and not \
-                        Settings().value(self.settings_section + '/hide combined quick error'):
-                        self.application.set_normal_cursor()
-                        # Reference separators need to be defined both, in here and on reference search,
-                        # error won't work if they are left out from one.
-                        reference_separators = {
-                            'verse': get_reference_separator('sep_v_display'),
-                            'range': get_reference_separator('sep_r_display'),
-                            'list': get_reference_separator('sep_l_display')}
-                        self.main_window.information_message(translate('BiblesPlugin.BibleManager', 'Nothing found'),
-                                                             translate('BiblesPlugin.BibleManager',
-                                                                       '<strong>OpenLP couldn’t find anything with your'
-                                                                       ' search.</strong><br><br>If you tried to search'
-                                                                       ' with Scripture Reference, please make<br> sure'
-                                                                       ' that your reference follows one of these'
-                                                                       ' patterns: <br><br>%s'
-                                                                       % UiStrings().BibleScriptureError %
-                                                                       reference_separators))
-        # Finalizing the search
-        # List is cleared if not locked, results are listed, button is set available, cursor is set to normal.
-        if not self.quickLockButton.isChecked():
-            self.list_view.clear()
-        if self.list_view.count() != 0 and self.search_results:
-            self.__check_second_bible(bible, second_bible)
-        elif self.search_results:
-            self.display_results(bible, second_bible)
-        self.quickSearchButton.setEnabled(True)
-        self.application.set_normal_cursor()
-
-    def on_quick_search_while_typing(self):
-        """
-        This function is called when "Search as you type" is enabled for Bibles.
-        It is basically the same thing as "on_quick_search_search" but all the error messages are removed.
-        This also has increased min len for text search for performance reasons.
-        For commented version, please visit def on_quick_search_button.
-        """
-        bible = self.quickVersionComboBox.currentText()
-        second_bible = self.quickSecondComboBox.currentText()
-        text = self.quick_search_edit.text()
-        if self.quick_search_edit.current_search_type() == BibleSearch.Combined:
-            # If text has no numbers, auto search limit is min 8 characters for performance reasons.
-            # If you change this value, also change it in biblestab.py (Count) in enabling search while typing.
-            if (char.isdigit() for char in text) and len(text) > 2:
-                self.on_quick_reference_search()
-            if not self.search_results and len(text) > 7:
-                self.on_quick_text_search_while_typing()
-        elif self.quick_search_edit.current_search_type() == BibleSearch.Reference:
-            self.on_quick_reference_search()
-        elif self.quick_search_edit.current_search_type() == BibleSearch.Text:
-            if len(text) > 7:
-                self.on_quick_text_search_while_typing()
-        if not self.quickLockButton.isChecked():
-            self.list_view.clear()
-        if self.list_view.count() != 0 and self.search_results:
-            self.__check_second_bible(bible, second_bible)
-        elif self.search_results:
-            self.display_results(bible, second_bible)
-        self.application.set_normal_cursor()
-
-    def on_search_text_edit_changed(self):
-        """
-        If search automatically while typing is enabled, perform the search and list results when conditions are met.
+                    translate('BiblesPlugin.BibleManager',
+                              '<strong>The reference you typed is invalid!<br><br>'
+                              'Please make sure that your reference follows one of these patterns:</strong><br><br>%s')
+                    % UiStrings().BibleScriptureError % get_reference_separators())
+        elif self.search_edit.current_search_type() == BibleSearch.Combined and get_reference_match('full').match(text):
+                # Valid reference found. Do reference search.
+                self.text_reference_search(text)
+        else:
+            # It can only be a 'Combined' search without a valid reference, or a 'Text' search
+            if search_while_type:
+                if len(text) > 8 and VALID_TEXT_SEARCH.search(text):
+                    self.on_text_search(text, True)
+            elif VALID_TEXT_SEARCH.search(text):
+                self.on_text_search(text)
+                self.display_results()
+
+    def on_search_edit_text_changed(self):
+        """
+        If 'search_as_you_type' is enabled, start a timer when the search_edit emits a textChanged signal. This is to
+        prevent overloading the system by submitting too many search requests in a short space of time.
+
+        :return: None
         """
         if Settings().value('bibles/is search while typing enabled'):
-            text = self.quick_search_edit.text()
-            """
-            Use Regex for finding space + number in reference search and space + 2 characters in text search.
-            Also search for two characters (Searches require at least two sets of two characters)
-            These are used to prevent bad search queries from starting. (Long/crashing queries)
-            """
-            space_and_digit_reference = re.compile(' \d')
-            two_chars_text = re.compile('\S\S')
-            space_and_two_chars_text = re.compile(' \S\S')
-            # Turn this into a format that may be used in if statement.
-            count_space_digit_reference = space_and_digit_reference.findall(text)
-            count_two_chars_text = two_chars_text.findall(text)
-            count_spaces_two_chars_text = space_and_two_chars_text.findall(text)
-            """
-            The Limit is required for setting the proper "No items found" message.
-            "Limit" is also hard coded to on_quick_search_while_typing, it must be there to avoid bad search
-            performance. Limit 8 = Text search, 3 = Reference search.
-            """
-            limit = 8
-            if self.quick_search_edit.current_search_type() == BibleSearch.Combined:
-                if len(count_space_digit_reference) != 0:
-                    limit = 3
-            elif self.quick_search_edit.current_search_type() == BibleSearch.Reference:
-                limit = 3
-            """
-            If text is empty, clear the list.
-            else: Start by checking if the search is suitable for "Search while typing"
-            """
-            if len(text) == 0:
-                if not self.quickLockButton.isChecked():
-                    self.list_view.clear()
-            else:
-                if limit == 3 and (len(text) < limit or len(count_space_digit_reference) == 0):
-                    if not self.quickLockButton.isChecked():
-                        self.list_view.clear()
-                elif limit == 8 and (len(text) < limit or len(count_two_chars_text) < 2):
-                    if not self.quickLockButton.isChecked():
-                        self.list_view.clear(search_while_typing=True)
-                else:
-                    """
-                    Start search if no chars are entered or deleted for 0.2 s
-                    If no Timer is set, Text search will break the search by sending repeative search Quaries on
-                    all chars. Use the self.on_quick_search_while_typing, this does not contain any error messages.
-                    """
-                    self.search_timer = ()
-                    if self.search_timer:
-                        self.search_timer.stop()
-                        self.search_timer.deleteLater()
-                    self.search_timer = QtCore.QTimer()
-                    self.search_timer.timeout.connect(self.on_quick_search_while_typing)
-                    self.search_timer.setSingleShot(True)
-                    self.search_timer.start(200)
-
-    def display_results(self, bible, second_bible=''):
-        """
-        Displays the search results in the media manager. All data needed for further action is saved for/in each row.
-        """
-        items = self.build_display_results(bible, second_bible, self.search_results)
-        for bible_verse in items:
-            self.list_view.addItem(bible_verse)
+            if not self.search_timer.isActive():
+                self.search_timer.start()
+
+    def on_search_timer_timeout(self):
+        """
+        Perform a search when the search timer timeouts. The search timer is used for 'search_as_you_type' so that we
+        don't overload the system buy submitting too many search requests in a short space of time.
+
+        :return: None
+        """
+        self.text_search(True)
+
+    def display_results(self):
+        """
+        Add the search results to the media manager list.
+
+        :return: None
+        """
+        self.list_view.clear()
+        items = self.build_display_results(self.bible, self.second_bible, self.search_results)
+        for item in items:
+            self.list_view.addItem(item)
         self.list_view.selectAll()
-        self.search_results = {}
-        self.second_search_results = {}
+        self.search_results = []
+        self.second_search_results = []
 
     def build_display_results(self, bible, second_bible, search_results):
         """
         Displays the search results in the media manager. All data needed for further action is saved for/in each row.
         """
-        verse_separator = get_reference_separator('sep_v_display')
-        version = self.plugin.manager.get_meta_data(bible, 'name').value
-        copyright = self.plugin.manager.get_meta_data(bible, 'copyright').value
-        permissions = self.plugin.manager.get_meta_data(bible, 'permissions').value
+        verse_separator = get_reference_separators()['verse']
+        version = self.plugin.manager.get_meta_data(self.bible.name, 'name').value
+        copyright = self.plugin.manager.get_meta_data(self.bible.name, 'copyright').value
+        permissions = self.plugin.manager.get_meta_data(self.bible.name, 'permissions').value
+        second_name = ''
         second_version = ''
         second_copyright = ''
         second_permissions = ''
         if second_bible:
-            second_version = self.plugin.manager.get_meta_data(second_bible, 'name').value
-            second_copyright = self.plugin.manager.get_meta_data(second_bible, 'copyright').value
-            second_permissions = self.plugin.manager.get_meta_data(second_bible, 'permissions').value
+            second_name = second_bible.name
+            second_version = self.plugin.manager.get_meta_data(self.second_bible.name, 'name').value
+            second_copyright = self.plugin.manager.get_meta_data(self.second_bible.name, 'copyright').value
+            second_permissions = self.plugin.manager.get_meta_data(self.second_bible.name, 'permissions').value
         items = []
-        language_selection = self.plugin.manager.get_language_selection(bible)
+        language_selection = self.plugin.manager.get_language_selection(self.bible.name)
         for count, verse in enumerate(search_results):
-            book = None
-            if language_selection == LanguageSelection.Bible:
-                book = verse.book.name
-            elif language_selection == LanguageSelection.Application:
-                book_names = BibleStrings().BookNames
-                data = BiblesResourcesDB.get_book_by_id(verse.book.book_reference_id)
-                book = str(book_names[data['abbreviation']])
-            elif language_selection == LanguageSelection.English:
-                data = BiblesResourcesDB.get_book_by_id(verse.book.book_reference_id)
-                book = data['name']
             data = {
-                'book': book,
+                'book': verse.book.get_name(language_selection),
                 'chapter': verse.chapter,
                 'verse': verse.verse,
-                'bible': bible,
+                'bible': self.bible.name,
                 'version': version,
                 'copyright': copyright,
                 'permissions': permissions,
                 'text': verse.text,
-                'second_bible': second_bible,
+                'second_bible': second_name,
                 'second_version': second_version,
                 'second_copyright': second_copyright,
                 'second_permissions': second_permissions,
                 'second_text': ''
             }
+
             if second_bible:
                 try:
                     data['second_text'] = self.second_search_results[count].text
@@ -1023,20 +786,10 @@
                 except TypeError:
                     log.exception('The second_search_results does not have this book.')
                     break
-                bible_text = ('{book} {chapter:d}{sep}{verse:d} '
-                              '({version1}, {version2})').format(book=book,
-                                                                 chapter=verse.chapter,
-                                                                 sep=verse_separator,
-                                                                 verse=verse.verse,
-                                                                 version1=version,
-                                                                 version2=second_version)
+                bible_text = '{book} {chapter:d}{sep}{verse:d} ({version}, {second_version})'
             else:
-                bible_text = '{book} {chapter:d}{sep}{verse:d} ({version})'.format(book=book,
-                                                                                   chapter=verse.chapter,
-                                                                                   sep=verse_separator,
-                                                                                   verse=verse.verse,
-                                                                                   version=version)
-            bible_verse = QtWidgets.QListWidgetItem(bible_text)
+                bible_text = '{book} {chapter:d}{sep}{verse:d} ({version})'
+            bible_verse = QtWidgets.QListWidgetItem(bible_text.format(sep=verse_separator, **data))
             bible_verse.setData(QtCore.Qt.UserRole, data)
             items.append(bible_verse)
         return items
@@ -1060,63 +813,42 @@
         if not items:
             return False
         bible_text = ''
-        old_item = None
         old_chapter = -1
-        raw_slides = []
-        raw_title = []
+        raw_slides = ['']
         verses = VerseReferenceList()
         for bitem in items:
-            book = self._decode_qt_object(bitem, 'book')
-            chapter = int(self._decode_qt_object(bitem, 'chapter'))
-            verse = int(self._decode_qt_object(bitem, 'verse'))
-            bible = self._decode_qt_object(bitem, 'bible')
-            version = self._decode_qt_object(bitem, 'version')
-            copyright = self._decode_qt_object(bitem, 'copyright')
-            permissions = self._decode_qt_object(bitem, 'permissions')
-            text = self._decode_qt_object(bitem, 'text')
-            second_bible = self._decode_qt_object(bitem, 'second_bible')
-            second_version = self._decode_qt_object(bitem, 'second_version')
-            second_copyright = self._decode_qt_object(bitem, 'second_copyright')
-            second_permissions = self._decode_qt_object(bitem, 'second_permissions')
-            second_text = self._decode_qt_object(bitem, 'second_text')
-            verses.add(book, chapter, verse, version, copyright, permissions)
-            verse_text = self.format_verse(old_chapter, chapter, verse)
-            if second_bible:
-                bible_text = '{verse}{text1}\n\n{verse}&nbsp;{text2}'.format(verse=verse_text,
-                                                                             text1=text,
-                                                                             text2=second_text)
+            data = bitem.data(QtCore.Qt.UserRole)
+            verses.add(
+                data['book'], data['chapter'], data['verse'], data['version'], data['copyright'], data['permissions'])
+            verse_text = self.format_verse(old_chapter, data['chapter'], data['verse'])
+            # We only support 'Verse Per Slide' when using a scond bible
+            if data['second_bible']:
+                bible_text = '{data[verse]}{data[text]}\n\n{data[verse]}{data[second_text]}'.format(data=data)
                 raw_slides.append(bible_text.rstrip())
                 bible_text = ''
             # If we are 'Verse Per Slide' then create a new slide.
             elif self.settings.layout_style == LayoutStyle.VersePerSlide:
-                bible_text = '{verse}{text}'.format(verse=verse_text, text=text)
+                bible_text = '{data[verse]}{data[text]}'.format(data=data)
                 raw_slides.append(bible_text.rstrip())
                 bible_text = ''
             # If we are 'Verse Per Line' then force a new line.
             elif self.settings.layout_style == LayoutStyle.VersePerLine:
-                bible_text = '{bible}{verse}{text}\n'.format(bible=bible_text, verse=verse_text, text=text)
+                bible_text = '{bible} {verse}{data[text]}\n'.format(bible=bible_text, verse=verse_text, data=data)
             # We have to be 'Continuous'.
             else:
-                bible_text = '{bible} {verse}{text}\n'.format(bible=bible_text, verse=verse_text, text=text)
+                bible_text = '{bible} {verse}{data[text]}'.format(bible=bible_text, verse=verse_text, data=data)
             bible_text = bible_text.strip(' ')
-            if not old_item:
-                start_item = bitem
-            elif self.check_title(bitem, old_item):
-                raw_title.append(self.format_title(start_item, old_item))
-                start_item = bitem
-            old_item = bitem
-            old_chapter = chapter
+            old_chapter = data['chapter']
         # Add footer
         service_item.raw_footer.append(verses.format_verses())
-        if second_bible:
-            verses.add_version(second_version, second_copyright, second_permissions)
+        if data['second_bible']:
+            verses.add_version(data['second_version'], data['second_copyright'], data['second_permissions'])
         service_item.raw_footer.append(verses.format_versions())
-        raw_title.append(self.format_title(start_item, bitem))
         # If there are no more items we check whether we have to add bible_text.
         if bible_text:
             raw_slides.append(bible_text.lstrip())
         # Service Item: Capabilities
-        if self.settings.layout_style == LayoutStyle.Continuous and not second_bible:
+        if self.settings.layout_style == LayoutStyle.Continuous and not data['second_bible']:
             # Split the line but do not replace line breaks in renderer.
             service_item.add_capability(ItemCapabilities.NoLineBreaks)
         service_item.add_capability(ItemCapabilities.CanPreview)
@@ -1126,77 +858,12 @@
         # Service Item: Title
         service_item.title = '{verse} {version}'.format(verse=verses.format_verses(), version=verses.format_versions())
         # Service Item: Theme
-        if not self.settings.bible_theme:
-            service_item.theme = None
-        else:
+        if self.settings.bible_theme:
             service_item.theme = self.settings.bible_theme
         for slide in raw_slides:
             service_item.add_from_text(slide)
         return True
 
-    def format_title(self, start_bitem, old_bitem):
-        """
-        This method is called, when we have to change the title, because we are at the end of a verse range. E. g. if we
-        want to add Genesis 1:1-6 as well as Daniel 2:14.
-
-        :param start_bitem: The first item of a range.
-        :param old_bitem: The last item of a range.
-        """
-        verse_separator = get_reference_separator('sep_v_display')
-        range_separator = get_reference_separator('sep_r_display')
-        old_chapter = self._decode_qt_object(old_bitem, 'chapter')
-        old_verse = self._decode_qt_object(old_bitem, 'verse')
-        start_book = self._decode_qt_object(start_bitem, 'book')
-        start_chapter = self._decode_qt_object(start_bitem, 'chapter')
-        start_verse = self._decode_qt_object(start_bitem, 'verse')
-        start_bible = self._decode_qt_object(start_bitem, 'bible')
-        start_second_bible = self._decode_qt_object(start_bitem, 'second_bible')
-        if start_second_bible:
-            bibles = '{bible1}, {bible2}'.format(bible1=start_bible, bible2=start_second_bible)
-        else:
-            bibles = start_bible
-        if start_chapter == old_chapter:
-            if start_verse == old_verse:
-                verse_range = start_chapter + verse_separator + start_verse
-            else:
-                verse_range = start_chapter + verse_separator + start_verse + range_separator + old_verse
-        else:
-            verse_range = start_chapter + verse_separator + start_verse + \
-                range_separator + old_chapter + verse_separator + old_verse
-        return '{book} {verse} ({bible})'.format(book=start_book, verse=verse_range, bible=bibles)
-
-    def check_title(self, bitem, old_bitem):
-        """
-        This method checks if we are at the end of an verse range. If that is the case, we return True, otherwise False.
-        E. g. if we added Genesis 1:1-6, but the next verse is Daniel 2:14, we return True.
-
-        :param bitem: The item we are dealing with at the moment.
-        :param old_bitem: The item we were previously dealing with.
-        """
-        # Get all the necessary meta data.
-        book = self._decode_qt_object(bitem, 'book')
-        chapter = int(self._decode_qt_object(bitem, 'chapter'))
-        verse = int(self._decode_qt_object(bitem, 'verse'))
-        bible = self._decode_qt_object(bitem, 'bible')
-        second_bible = self._decode_qt_object(bitem, 'second_bible')
-        old_book = self._decode_qt_object(old_bitem, 'book')
-        old_chapter = int(self._decode_qt_object(old_bitem, 'chapter'))
-        old_verse = int(self._decode_qt_object(old_bitem, 'verse'))
-        old_bible = self._decode_qt_object(old_bitem, 'bible')
-        old_second_bible = self._decode_qt_object(old_bitem, 'second_bible')
-        if old_bible != bible or old_second_bible != second_bible or old_book != book:
-            # The bible, second bible or book has changed.
-            return True
-        elif old_verse + 1 != verse and old_chapter == chapter:
-            # We are still in the same chapter, but a verse has been skipped.
-            return True
-        elif old_chapter + 1 == chapter and (verse != 1 or old_verse !=
-                                             self.plugin.manager.get_verse_count(old_bible, old_book, old_chapter)):
-            # We are in the following chapter, but the last verse was not the last verse of the chapter or the current
-            # verse is not the first one of the chapter.
-            return True
-        return False
-
     def format_verse(self, old_chapter, chapter, verse):
         """
         Formats and returns the text, each verse starts with, for the given chapter and verse. The text is either
@@ -1207,28 +874,29 @@
         :param old_chapter: The previous verse's chapter number (int).
         :param chapter: The chapter number (int).
         :param verse: The verse number (int).
+        :return: An empty or formatted string
         """
-        verse_separator = get_reference_separator('sep_v_display')
         if not self.settings.is_verse_number_visible:
             return ''
+        verse_separator = get_reference_separators()['verse']
         if not self.settings.show_new_chapters or old_chapter != chapter:
-            verse_text = str(chapter) + verse_separator + str(verse)
+            verse_text = '{chapter}{sep}{verse}'.format(chapter=chapter, sep=verse_separator, verse=verse)
         else:
-            verse_text = str(verse)
-        if self.settings.display_style == DisplayStyle.Round:
-            return '{{su}}({verse}){{/su}}&nbsp;'.format(verse=verse_text)
-        if self.settings.display_style == DisplayStyle.Curly:
-            return '{{su}}{{{verse}}}{{/su}}&nbsp;'.format(verse=verse_text)
-        if self.settings.display_style == DisplayStyle.Square:
-            return '{{su}}[{verse}]{{/su}}&nbsp;'.format(verse=verse_text)
-        return '{{su}}{verse}{{/su}}&nbsp;'.format(verse=verse_text)
+            verse_text = verse
+        bracket = {
+            DisplayStyle.NoBrackets: ('', ''),
+            DisplayStyle.Round: ('(', ')'),
+            DisplayStyle.Curly: ('{', '}'),
+            DisplayStyle.Square: ('[', ']')
+        }[self.settings.display_style]
+        return '{{su}}{bracket[0]}{verse_text}{bracket[1]}{{/su}}&nbsp;'.format(verse_text=verse_text, bracket=bracket)
 
     def search(self, string, showError):
         """
         Search for some Bible verses (by reference).
         """
-        bible = self.quickVersionComboBox.currentText()
-        search_results = self.plugin.manager.get_verses(bible, string, False, showError)
+        reference = self.plugin.manager.parse_ref(self.bible.name, string)
+        search_results = self.plugin.manager.get_verses(self.bible.name, reference, showError)
         if search_results:
             verse_text = ' '.join([verse.text for verse in search_results])
             return [[string, verse_text]]
@@ -1238,7 +906,6 @@
         """
         Create a media item from an item id.
         """
-        bible = self.quickVersionComboBox.currentText()
-        search_results = self.plugin.manager.get_verses(bible, item_id, False)
-        items = self.build_display_results(bible, '', search_results)
-        return items
+        reference = self.plugin.manager.parse_ref(self.bible.name, item_id)
+        search_results = self.plugin.manager.get_verses(self.bible.name, reference, False)
+        return self.build_display_results(self.bible, None, search_results)

=== modified file 'openlp/plugins/songs/forms/songselectform.py' (properties changed: +x to -x)
=== modified file 'resources/images/openlp-2.qrc'
--- resources/images/openlp-2.qrc	2016-10-27 20:37:33 +0000
+++ resources/images/openlp-2.qrc	2016-12-18 19:00:12 +0000
@@ -29,6 +29,7 @@
     <file>image_new_group.png</file>
   </qresource>
   <qresource prefix="bibles">
+    <file>bibles_book_sort.png</file>
     <file>bibles_search_combined.png</file>
     <file>bibles_search_text.png</file>
     <file>bibles_search_reference.png</file>

=== modified file 'tests/functional/openlp_core_ui_lib/test_listwidgetwithdnd.py'
--- tests/functional/openlp_core_ui_lib/test_listwidgetwithdnd.py	2016-11-12 11:19:52 +0000
+++ tests/functional/openlp_core_ui_lib/test_listwidgetwithdnd.py	2016-12-18 19:00:12 +0000
@@ -33,6 +33,37 @@
     """
     Test the :class:`~openlp.core.lib.listwidgetwithdnd.ListWidgetWithDnD` class
     """
+    def test_clear_locked(self):
+        """
+        Test the clear method the list is 'locked'
+        """
+        with patch('openlp.core.ui.lib.listwidgetwithdnd.QtWidgets.QListWidget.clear') as mocked_clear_super_method:
+            # GIVEN: An instance of ListWidgetWithDnD
+            widget = ListWidgetWithDnD()
+
+            # WHEN: The list is 'locked' and clear has been called
+            widget.locked = True
+            widget.clear()
+
+            # THEN: The super method should not have been called (i.e. The list not cleared)
+            self.assertFalse(mocked_clear_super_method.called)
+
+    def test_clear_overide_locked(self):
+        """
+        Test the clear method the list is 'locked', but clear is called with 'override_lock' set to True
+        """
+        with patch('openlp.core.ui.lib.listwidgetwithdnd.QtWidgets.QListWidget.clear') as mocked_clear_super_method:
+            # GIVEN: An instance of ListWidgetWithDnD
+            widget = ListWidgetWithDnD()
+
+            # WHEN: The list is 'locked' and clear has been called with override_lock se to True
+            widget.locked = True
+            widget.clear(override_lock=True)
+
+            # THEN: The super method should have been called (i.e. The list is cleared regardless whether it is locked
+            #       or not)
+            mocked_clear_super_method.assert_called_once_with()
+
     def test_clear(self):
         """
         Test the clear method when called without any arguments.

=== modified file 'tests/functional/openlp_plugins/bibles/test_lib.py'
--- tests/functional/openlp_plugins/bibles/test_lib.py	2016-06-17 01:08:21 +0000
+++ tests/functional/openlp_plugins/bibles/test_lib.py	2016-12-18 19:00:12 +0000
@@ -25,11 +25,12 @@
 from unittest import TestCase
 
 from openlp.plugins.bibles import lib
-from openlp.plugins.bibles.lib import SearchResults
-from tests.functional import patch
-
-
-class TestLib(TestCase):
+from openlp.plugins.bibles.lib import SearchResults, get_reference_match
+from tests.functional import MagicMock, patch
+from tests.helpers.testmixin import TestMixin
+
+
+class TestLib(TestCase, TestMixin):
     """
     Test the functions in the :mod:`lib` module.
     """
@@ -60,6 +61,142 @@
             self.assertEqual(separators[key], value)
         mocked_update_reference_separators.assert_called_once_with()
 
+    def test_reference_matched_full(self):
+        """
+        Test that the 'full' regex parses bible verse references correctly.
+        """
+        # GIVEN: Some test data which contains different references to parse, with the expected results.
+        with patch('openlp.plugins.bibles.lib.Settings', return_value=MagicMock(**{'value.return_value': ''})):
+            # The following test data tests with 222 variants when using the default 'separators'
+            test_data = [
+                # Input reference, book name, chapter + verse reference
+                ('Psalm 23', 'Psalm', '23'),
+                ('Psalm. 23', 'Psalm', '23'),
+                ('Psalm 23{to}24', 'Psalm', '23-24'),
+                ('Psalm 23{verse}1{to}2', 'Psalm', '23:1-2'),
+                ('Psalm 23{verse}1{to}{end}', 'Psalm', '23:1-end'),
+                ('Psalm 23{verse}1{to}2{_and}5{to}6', 'Psalm', '23:1-2,5-6'),
+                ('Psalm 23{verse}1{to}2{_and}5{to}{end}', 'Psalm', '23:1-2,5-end'),
+                ('Psalm 23{verse}1{to}2{_and}24{verse}1{to}3', 'Psalm', '23:1-2,24:1-3'),
+                ('Psalm 23{verse}1{to}{end}{_and}24{verse}1{to}{end}', 'Psalm', '23:1-end,24:1-end'),
+                ('Psalm 23{verse}1{to}24{verse}1', 'Psalm', '23:1-24:1'),
+                ('Psalm 23{_and}24', 'Psalm', '23,24'),
+                ('1 John 23', '1 John', '23'),
+                ('1 John. 23', '1 John', '23'),
+                ('1 John 23{to}24', '1 John', '23-24'),
+                ('1 John 23{verse}1{to}2', '1 John', '23:1-2'),
+                ('1 John 23{verse}1{to}{end}', '1 John', '23:1-end'),
+                ('1 John 23{verse}1{to}2{_and}5{to}6', '1 John', '23:1-2,5-6'),
+                ('1 John 23{verse}1{to}2{_and}5{to}{end}', '1 John', '23:1-2,5-end'),
+                ('1 John 23{verse}1{to}2{_and}24{verse}1{to}3', '1 John', '23:1-2,24:1-3'),
+                ('1 John 23{verse}1{to}{end}{_and}24{verse}1{to}{end}', '1 John', '23:1-end,24:1-end'),
+                ('1 John 23{verse}1{to}24{verse}1', '1 John', '23:1-24:1'),
+                ('1 John 23{_and}24', '1 John', '23,24')]
+
+            full_reference_match = get_reference_match('full')
+            for reference_text, book_result, ranges_result in test_data:
+                to_separators = ['-', ' - ', 'to', ' to '] if '{to}' in reference_text else ['']
+                verse_separators = [':', ' : ', 'v', ' v ', 'V', ' V ', 'verse', ' verse ', 'verses', ' verses '] \
+                    if '{verse}' in reference_text else ['']
+                and_separators = [',', ' , ', 'and', ' and '] if '{_and}' in reference_text else ['']
+                end_separators = ['end', ' end '] if '{end}' in reference_text else ['']
+
+                for to in to_separators:
+                    for verse in verse_separators:
+                        for _and in and_separators:
+                            for end in end_separators:
+                                reference_text = reference_text.format(to=to, verse=verse, _and=_and, end=end)
+
+                                # WHEN: Attempting to parse the input string
+                                match = full_reference_match.match(reference_text)
+
+                                # THEN: A match should be returned, and the book and reference should match the
+                                #       expected result
+                                self.assertIsNotNone(match, '{text} should provide a match'.format(text=reference_text))
+                                self.assertEqual(book_result, match.group('book'),
+                                                 '{text} does not provide the expected result for the book group.'
+                                                 .format(text=reference_text))
+                                self.assertEqual(ranges_result, match.group('ranges'),
+                                                 '{text} does not provide the expected result for the ranges group.'
+                                                 .format(text=reference_text))
+
+    def test_reference_matched_range(self):
+        """
+        Test that the 'range' regex parses bible verse references correctly.
+        Note: This test takes in to account that the regex does not work quite as expected!
+        see https://bugs.launchpad.net/openlp/+bug/1638620
+        """
+        # GIVEN: Some test data which contains different references to parse, with the expected results.
+        with patch('openlp.plugins.bibles.lib.Settings', return_value=MagicMock(**{'value.return_value': ''})):
+            # The following test data tests with 45 variants when using the default 'separators'
+            test_data = [
+                ('23', None, '23', None, None, None),
+                ('23{to}24', None, '23', '-24', None, '24'),
+                ('23{verse}1{to}2', '23', '1', '-2', None, '2'),
+                ('23{verse}1{to}{end}', '23', '1', '-end', None, None),
+                ('23{verse}1{to}24{verse}1', '23', '1', '-24:1', '24', '1')]
+            full_reference_match = get_reference_match('range')
+            for reference_text, from_chapter, from_verse, range_to, to_chapter, to_verse in test_data:
+                to_separators = ['-', ' - ', 'to', ' to '] if '{to}' in reference_text else ['']
+                verse_separators = [':', ' : ', 'v', ' v ', 'V', ' V ', 'verse', ' verse ', 'verses', ' verses '] \
+                    if '{verse}' in reference_text else ['']
+                and_separators = [',', ' , ', 'and', ' and '] if '{_and}' in reference_text else ['']
+                end_separators = ['end', ' end '] if '{end}' in reference_text else ['']
+
+                for to in to_separators:
+                    for verse in verse_separators:
+                        for _and in and_separators:
+                            for end in end_separators:
+                                reference_text = reference_text.format(to=to, verse=verse, _and=_and, end=end)
+
+                                # WHEN: Attempting to parse the input string
+                                match = full_reference_match.match(reference_text)
+
+                                # THEN: A match should be returned, and the to/from chapter/verses should match as
+                                #       expected
+                                self.assertIsNotNone(match, '{text} should provide a match'.format(text=reference_text))
+                                self.assertEqual(match.group('from_chapter'), from_chapter)
+                                self.assertEqual(match.group('from_verse'), from_verse)
+                                self.assertEqual(match.group('range_to'), range_to)
+                                self.assertEqual(match.group('to_chapter'), to_chapter)
+                                self.assertEqual(match.group('to_verse'), to_verse)
+
+    def test_reference_matched_range_separator(self):
+        # GIVEN: Some test data which contains different references to parse, with the expected results.
+        with patch('openlp.plugins.bibles.lib.Settings', return_value=MagicMock(**{'value.return_value': ''})):
+            # The following test data tests with 111 variants when using the default 'separators'
+            # The regex for handling ranges is a bit screwy, see https://bugs.launchpad.net/openlp/+bug/1638620
+            test_data = [
+                ('23', ['23']),
+                ('23{to}24', ['23-24']),
+                ('23{verse}1{to}2', ['23:1-2']),
+                ('23{verse}1{to}{end}', ['23:1-end']),
+                ('23{verse}1{to}2{_and}5{to}6', ['23:1-2', '5-6']),
+                ('23{verse}1{to}2{_and}5{to}{end}', ['23:1-2', '5-end']),
+                ('23{verse}1{to}2{_and}24{verse}1{to}3', ['23:1-2', '24:1-3']),
+                ('23{verse}1{to}{end}{_and}24{verse}1{to}{end}', ['23:1-end', '24:1-end']),
+                ('23{verse}1{to}24{verse}1', ['23:1-24:1']),
+                ('23,24', ['23', '24'])]
+            full_reference_match = get_reference_match('range_separator')
+            for reference_text, ranges in test_data:
+                to_separators = ['-', ' - ', 'to', ' to '] if '{to}' in reference_text else ['']
+                verse_separators = [':', ' : ', 'v', ' v ', 'V', ' V ', 'verse', ' verse ', 'verses', ' verses '] \
+                    if '{verse}' in reference_text else ['']
+                and_separators = [',', ' , ', 'and', ' and '] if '{_and}' in reference_text else ['']
+                end_separators = ['end', ' end '] if '{end}' in reference_text else ['']
+
+                for to in to_separators:
+                    for verse in verse_separators:
+                        for _and in and_separators:
+                            for end in end_separators:
+                                reference_text = reference_text.format(to=to, verse=verse, _and=_and, end=end)
+
+                                # WHEN: Attempting to parse the input string
+                                references = full_reference_match.split(reference_text)
+
+                                # THEN: The list of references should be as the expected results
+                                self.assertEqual(references, ranges)
+
     def test_search_results_creation(self):
         """
         Test the creation and construction of the SearchResults class

=== modified file 'tests/functional/openlp_plugins/bibles/test_mediaitem.py'
--- tests/functional/openlp_plugins/bibles/test_mediaitem.py	2016-11-10 07:09:04 +0000
+++ tests/functional/openlp_plugins/bibles/test_mediaitem.py	2016-12-18 19:00:12 +0000
@@ -23,10 +23,85 @@
 This module contains tests for the lib submodule of the Presentations plugin.
 """
 from unittest import TestCase
+from unittest.mock import MagicMock, call, patch
+
+from PyQt5 import QtCore, QtWidgets
+
+from tests.helpers.testmixin import TestMixin
+
 from openlp.core.common import Registry
-from openlp.plugins.bibles.lib.mediaitem import BibleMediaItem
-from tests.functional import MagicMock, patch
-from tests.helpers.testmixin import TestMixin
+from openlp.core.lib import MediaManagerItem
+from openlp.plugins.bibles.lib.mediaitem import BibleMediaItem, BibleSearch, get_reference_separators, VALID_TEXT_SEARCH
+
+
+class TestBibleMediaItemModulefunctions(TestCase):
+    """
+    Test the module functions in :mod:`openlp.plugins.bibles.lib.mediaitem`
+    """
+
+    def test_valid_text_search(self):
+        """
+        Test the compiled VALID_TEXT_SEARCH regex expression
+        """
+        # GIVEN: Some test data and some expected results
+        test_data = [('a a a', None), ('a ab a', None), ('a abc a', ((2, 5),)), ('aa 123 aa', ((3, 6),))]
+        for data, expected_result in test_data:
+
+            # WHEN: Calling search on the compiled regex expression
+            result = VALID_TEXT_SEARCH.search(data)
+
+            # THEN: The expected result should be returned
+            if expected_result is None:
+                self.assertIsNone(result, expected_result)
+            else:
+                self.assertEqual(result.regs, expected_result)
+
+    def test_get_reference_separators(self):
+        """
+        Test the module function get_reference_separators
+        """
+        # GIVEN: A mocked get_reference_separator from the :mod:`openlp.plugins.bibles.lib` module
+        with patch('openlp.plugins.bibles.lib.mediaitem.get_reference_separator') as mocked_get_reference_separator:
+
+            # WHEN: Calling get_reference_separators
+            result = get_reference_separators()
+
+            # THEN: The result should contain the 'verse', 'range', 'list' keys and get_reference_separator should have
+            #       been called with the expected values.
+            self.assertTrue(all(key in result for key in ('verse', 'range', 'list')))
+            mocked_get_reference_separator.assert_has_calls(
+                [call('sep_v_display'), call('sep_r_display'), call('sep_l_display')])
+
+    def test_bible_search_enum(self):
+        """
+        Test that the :class:`BibleSearch` class contains the expected enumerations
+        """
+        # GIVEN: The BibleSearch class
+        # WHEN: Testing its attributes
+        # THEN: The BibleSearch class should have the following enumrations
+        self.assertTrue(hasattr(BibleSearch, 'Combined'))
+        self.assertTrue(hasattr(BibleSearch, 'Reference'))
+        self.assertTrue(hasattr(BibleSearch, 'Text'))
+
+    def test_bible_media_item_subclass(self):
+        """
+        Test that the :class:`BibleMediaItem` class is a subclass of the :class:`MediaManagerItem` class
+        """
+        # GIVEN: The :class:`BibleMediaItem`
+        # WHEN: Checking if it is a subclass of MediaManagerItem
+        # THEN: BibleMediaItem should be a subclass of MediaManagerItem
+        self.assertTrue(issubclass(BibleMediaItem, MediaManagerItem))
+
+    def test_bible_media_item_signals(self):
+        """
+        Test that the :class:`BibleMediaItem` class has the expected signals
+        """
+        # GIVEN: The :class:`BibleMediaItem`
+        # THEN:  The :class:`BibleMediaItem` should contain the following pyqtSignal's
+        self.assertTrue(hasattr(BibleMediaItem, 'bibles_go_live'))
+        self.assertTrue(hasattr(BibleMediaItem, 'bibles_add_to_service'))
+        self.assertTrue(isinstance(BibleMediaItem.bibles_go_live, QtCore.pyqtSignal))
+        self.assertTrue(isinstance(BibleMediaItem.bibles_add_to_service, QtCore.pyqtSignal))
 
 
 class TestMediaItem(TestCase, TestMixin):
@@ -38,189 +113,1315 @@
         """
         Set up the components need for all tests.
         """
-        with patch('openlp.plugins.bibles.lib.mediaitem.MediaManagerItem._setup'),\
-                patch('openlp.plugins.bibles.lib.mediaitem.BibleMediaItem.setup_item'):
-            self.media_item = BibleMediaItem(None, MagicMock())
-        self.setup_application()
+        log_patcher = patch('openlp.plugins.bibles.lib.mediaitem.log')
+        self.addCleanup(log_patcher.stop)
+        self.mocked_log = log_patcher.start()
+
+        qtimer_patcher = patch('openlp.plugins.bibles.lib.mediaitem.QtCore.QTimer')
+        self.addCleanup(qtimer_patcher.stop)
+        self.mocked_qtimer = qtimer_patcher.start()
+
+        self.mocked_settings_instance = MagicMock()
+        self.mocked_settings_instance.value.side_effect = lambda key: self.setting_values[key]
+        settings_patcher = patch(
+            'openlp.plugins.bibles.lib.mediaitem.Settings', return_value=self.mocked_settings_instance)
+        self.addCleanup(settings_patcher.stop)
+        self.mocked_settings = settings_patcher.start()
+
+        Registry.create()
+
+        # self.setup_application()
+        self.mocked_application = MagicMock()
+        Registry().register('application', self.mocked_application)
         self.mocked_main_window = MagicMock()
-        Registry.create()
         Registry().register('main_window', self.mocked_main_window)
 
-    def test_display_results_no_results(self):
-        """
-        Test the display_results method when called with a single bible, returning no results
-        """
-
-        # GIVEN: A mocked build_display_results which returns an empty list
-        with patch('openlp.plugins.bibles.lib.BibleMediaItem.build_display_results', **{'return_value': []}) \
-                as mocked_build_display_results:
-            mocked_list_view = MagicMock()
-            self.media_item.search_results = 'results'
-            self.media_item.list_view = mocked_list_view
-
-            # WHEN: Calling display_results with a single bible version
-            self.media_item.display_results('NIV')
-
-            # THEN: No items should be added to the list, and select all should have been called.
-            mocked_build_display_results.assert_called_once_with('NIV', '', 'results')
-            self.assertFalse(mocked_list_view.addItem.called)
-            mocked_list_view.selectAll.assert_called_once_with()
-            self.assertEqual(self.media_item.search_results, {})
-            self.assertEqual(self.media_item.second_search_results, {})
-
-    def test_display_results_two_bibles_no_results(self):
-        """
-        Test the display_results method when called with two bibles, returning no results
-        """
-
-        # GIVEN: A mocked build_display_results which returns an empty list
-        with patch('openlp.plugins.bibles.lib.BibleMediaItem.build_display_results', **{'return_value': []}) \
-                as mocked_build_display_results:
-            mocked_list_view = MagicMock()
-            self.media_item.search_results = 'results'
-            self.media_item.list_view = mocked_list_view
-
-            # WHEN: Calling display_results with two single bible versions
-            self.media_item.display_results('NIV', 'GNB')
-
-            # THEN: build_display_results should have been called with two bible versions.
-            #       No items should be added to the list, and select all should have been called.
-            mocked_build_display_results.assert_called_once_with('NIV', 'GNB', 'results')
-            self.assertFalse(mocked_list_view.addItem.called)
-            mocked_list_view.selectAll.assert_called_once_with()
-            self.assertEqual(self.media_item.search_results, {})
-            self.assertEqual(self.media_item.second_search_results, {})
-
-    def test_display_results_returns_lots_of_results(self):
-            """
-            Test the display_results method a large number of results (> 100) are returned
-            """
-
-            # GIVEN: A mocked build_display_results which returns a large list of results
-            long_list = list(range(100))
-            with patch('openlp.plugins.bibles.lib.BibleMediaItem.build_display_results', **{'return_value': long_list})\
-                    as mocked_build_display_results:
-                mocked_list_view = MagicMock()
-                self.media_item.search_results = 'results'
-                self.media_item.list_view = mocked_list_view
-
-                # WHEN: Calling display_results
-                self.media_item.display_results('NIV', 'GNB')
-
-                # THEN: addItem should have been called 100 times, and the lsit items should not be selected.
-                mocked_build_display_results.assert_called_once_with('NIV', 'GNB', 'results')
-                self.assertEqual(mocked_list_view.addItem.call_count, 100)
-                mocked_list_view.selectAll.assert_called_once_with()
-                self.assertEqual(self.media_item.search_results, {})
-                self.assertEqual(self.media_item.second_search_results, {})
+        self.mocked_plugin = MagicMock()
+        with patch('openlp.plugins.bibles.lib.mediaitem.build_icon'), \
+                patch('openlp.plugins.bibles.lib.mediaitem.MediaManagerItem._setup'), \
+                patch('openlp.plugins.bibles.lib.mediaitem.BibleMediaItem.setup_item'):
+            self.media_item = BibleMediaItem(None, self.mocked_plugin)
+
+        self.media_item.settings_section = 'bibles'
+
+        self.mocked_book_1 = MagicMock(**{'get_name.return_value': 'Book 1', 'book_reference_id': 1})
+        self.mocked_book_2 = MagicMock(**{'get_name.return_value': 'Book 2', 'book_reference_id': 2})
+        self.mocked_book_3 = MagicMock(**{'get_name.return_value': 'Book 3', 'book_reference_id': 3})
+        self.mocked_book_4 = MagicMock(**{'get_name.return_value': 'Book 4', 'book_reference_id': 4})
+
+        self.book_list_1 = [self.mocked_book_1, self.mocked_book_2, self.mocked_book_3]
+        self.book_list_2 = [self.mocked_book_2, self.mocked_book_3, self.mocked_book_4]
+        self.mocked_bible_1 = MagicMock(**{'get_books.return_value': self.book_list_1})
+        self.mocked_bible_1.name = 'Bible 1'
+        self.mocked_bible_2 = MagicMock(**{'get_books.return_value': self.book_list_2})
+        self.mocked_bible_2.name = 'Bible 2'
+
+    def test_media_item_instance(self):
+        """
+        When creating an instance of C test that it is also an instance of
+        :class:`MediaManagerItem`
+        """
+        # GIVEN: An instance of :class:`BibleMediaItem`
+        # WEHN: Checking its class
+        # THEN: It should be a subclass of :class:`MediaManagerItem`
+        self.assertTrue(isinstance(self.media_item, MediaManagerItem))
+
+    def test_steup_item(self):
+        """
+        Test the setup_item method
+        """
+        # Could have tested the connection of the custom signals, however they're class vairables, and I could not find
+        # a way to properly test them.
+
+        # GIVEN: A mocked Registry.register_function method and an instance of BibleMediaItem
+        with patch.object(Registry(), 'register_function') as mocked_register_function:
+
+            # WHEN: Calling setup_itme
+            self.media_item.setup_item()
+
+            # THEN: Registry.register_function method should have been called with the reload_bibles method
+            mocked_register_function.assert_called_once_with('bibles_load_list', self.media_item.reload_bibles)
 
     def test_required_icons(self):
         """
         Test that all the required icons are set properly.
         """
-        # GIVEN: Mocked icons that need to be called.
-        self.media_item.has_import_icon = MagicMock()
-        self.media_item.has_new_icon = MagicMock()
-        self.media_item.has_edit_icon = MagicMock()
-        self.media_item.has_delete_icon = MagicMock()
-        self.media_item.add_to_service_item = MagicMock()
-
-        # WHEN: self.media_item.required_icons is called
+        # GIVEN: An instance of :class:`MediaManagerItem`
+        # WHEN: required_icons is called
         self.media_item.required_icons()
 
-        # THEN: On windows it should return True, on other platforms False
+        # THEN: The correct icons should be set
         self.assertTrue(self.media_item.has_import_icon, 'Check that the icon is as True.')
         self.assertFalse(self.media_item.has_new_icon, 'Check that the icon is called as False.')
         self.assertTrue(self.media_item.has_edit_icon, 'Check that the icon is called as True.')
         self.assertTrue(self.media_item.has_delete_icon, 'Check that the icon is called as True.')
         self.assertFalse(self.media_item.add_to_service_item, 'Check that the icon is called as False')
 
-    def test_on_quick_search_button_general(self):
-        """
-        Test that general things, which should be called on all Quick searches are called.
-        """
-
-        # GIVEN: self.application as self.app, all the required functions
-        Registry.create()
-        Registry().register('application', self.app)
-        self.media_item.quickSearchButton = MagicMock()
-        self.app.process_events = MagicMock()
-        self.media_item.quickVersionComboBox = MagicMock()
-        self.media_item.quickVersionComboBox.currentText = MagicMock()
-        self.media_item.quickSecondComboBox = MagicMock()
-        self.media_item.quickSecondComboBox.currentText = MagicMock()
-        self.media_item.quick_search_edit = MagicMock()
-        self.media_item.quick_search_edit.text = MagicMock()
-        self.media_item.quickLockButton = MagicMock()
-        self.media_item.list_view = MagicMock()
-        self.media_item.search_results = MagicMock()
-        self.media_item.display_results = MagicMock()
-        self.app.set_normal_cursor = MagicMock()
-
-        # WHEN: on_quick_search_button is called
-        self.media_item.on_quick_search_button()
-
-        # THEN: Search should had been started and finalized properly
-        self.assertEqual(1, self.app.process_events.call_count, 'Normal cursor should had been called once')
-        self.assertEqual(1, self.media_item.quickVersionComboBox.currentText.call_count, 'Should had been called once')
-        self.assertEqual(1, self.media_item.quickSecondComboBox.currentText.call_count, 'Should had been called once')
-        self.assertEqual(1, self.media_item.quick_search_edit.text.call_count, 'Text edit Should had been called once')
-        self.assertEqual(1, self.media_item.quickLockButton.isChecked.call_count, 'Lock Should had been called once')
-        self.assertEqual(1, self.media_item.display_results.call_count, 'Display results Should had been called once')
-        self.assertEqual(2, self.media_item.quickSearchButton.setEnabled.call_count, 'Disable and Enable the button')
-        self.assertEqual(1, self.app.set_normal_cursor.call_count, 'Normal cursor should had been called once')
+    # TODO: Test add_end_header_bar
+    # TODO: Test setupUi
+
+    def test_on_focus_search_tab_visible(self):
+        """
+        Test the correct widget gets focus when the BibleMediaItem receives focus
+        """
+        # GIVEN: An instance of :class:`MediaManagerItem` and a mocked out search_tab  and search_edit
+        self.media_item.search_tab = MagicMock(**{'isVisible.return_value': True})
+        self.media_item.search_edit = MagicMock()
+
+        # WHEN: Calling on_focus
+        self.media_item.on_focus()
+
+        # THEN: setFocus and selectAll should have been called on search_edit
+        self.assertEqual(self.media_item.search_edit.mock_calls, [call.setFocus(), call.selectAll()])
+
+    def test_on_focus_search_tab_not_visible(self):
+        """
+        Test the correct widget gets focus when the BibleMediaItem receives focus
+        """
+        # GIVEN: An instance of :class:`MediaManagerItem` and a mocked out search_tab and select_book_combo_box
+        self.media_item.search_tab = MagicMock(**{'isVisible.return_value': False})
+        self.media_item.select_book_combo_box = MagicMock()
+
+        # WHEN: Calling on_focus
+        self.media_item.on_focus()
+
+        # THEN: setFocus should have been called on select_book_combo_box
+        self.assertTrue(self.media_item.select_book_combo_box.setFocus.called)
+
+    def test_config_update_show_second_bible(self):
+        """
+        Test the config update method
+        """
+        # GIVEN: An instance of :class:`MediaManagerItem` and mocked out settings class with known values
+        self.setting_values = {'bibles/second bibles': True}
+        self.media_item.general_bible_layout = MagicMock()
+        self.media_item.second_combo_box = MagicMock()
+
+        # WHEN: Calling config_update()
+        self.media_item.config_update()
+
+        # THEN: second_combo_box() should be set visible
+        self.media_item.second_combo_box.setVisible.assert_called_once_with(True)
+
+    def test_config_update_hide_second_bible(self):
+        """
+        Test the config update method
+        """
+        # GIVEN: An instance of :class:`MediaManagerItem` and mocked out settings class with known values
+        self.setting_values = {'bibles/second bibles': False}
+        self.media_item.general_bible_layout = MagicMock()
+        self.media_item.second_combo_box = MagicMock()
+
+        # WHEN: Calling config_update()
+        self.media_item.config_update()
+
+        # THEN: second_combo_box() should hidden
+        self.media_item.second_combo_box.setVisible.assert_called_once_with(False)
+
+    def test_initalise(self):
+        """
+        Test the initalise method
+        """
+        # GIVEN: An instance of :class:`MediaManagerItem` and mocked out settings class with known values
+        self.setting_values = {'bibles/reset to combined quick search': False}
+        with patch.object(self.media_item, 'populate_bible_combo_boxes'), \
+                patch.object(self.media_item, 'config_update'):
+            self.media_item.search_edit = MagicMock()
+
+            # WHEN: Calling initialise()
+            self.media_item.initialise()
+
+            # THEN: The search_edit search types should have been set.
+            self.assertTrue(self.media_item.search_edit.set_search_types.called)
+            self.assertFalse(self.media_item.search_edit.set_current_search_type.called)
+
+    def test_initalise_reset_search_type(self):
+        """
+        Test the initalise method
+        """
+        # GIVEN: An instance of :class:`MediaManagerItem` and mocked out settings class with known values
+        self.setting_values = {'bibles/reset to combined quick search': True}
+        with patch.object(self.media_item, 'populate_bible_combo_boxes'), \
+                patch.object(self.media_item, 'config_update'):
+            self.media_item.search_edit = MagicMock()
+
+            # WHEN: Calling initialise()
+            self.media_item.initialise()
+
+            # THEN: The search_edit search types should have been set and that the current search type should be set to
+            #       'Combined'
+            self.assertTrue(self.media_item.search_edit.set_search_types.called)
+            self.media_item.search_edit.set_current_search_type.assert_called_once_with(BibleSearch.Combined)
+
+    def test_populate_bible_combo_boxes(self):
+        """
+        Test populate_bible_combo_boxes method
+        """
+        # GIVEN: An instance of :class:`MediaManagerItem` and mocked out settings class with known values
+        bible_1 = MagicMock()
+        bible_2 = MagicMock()
+        bible_3 = MagicMock()
+        self.setting_values = {'bibles/primary bible': bible_2}
+        self.media_item.version_combo_box = MagicMock()
+        self.media_item.second_combo_box = MagicMock()
+        self.mocked_plugin.manager.get_bibles.return_value = \
+            {'Bible 2': bible_2, 'Bible 1': bible_1, 'Bible 3': bible_3}
+        with patch('openlp.plugins.bibles.lib.mediaitem.get_locale_key', side_effect=lambda x: x), \
+                patch('openlp.plugins.bibles.lib.mediaitem.find_and_set_in_combo_box'):
+
+            # WHEN: Calling populate_bible_combo_boxes
+            self.media_item.populate_bible_combo_boxes()
+
+            # THEN: The bible combo boxes should be filled with the bible names and data, in a sorted order.
+            self.media_item.version_combo_box.addItem.assert_has_calls(
+                [call('Bible 1', bible_1), call('Bible 2', bible_2), call('Bible 3', bible_3)])
+            self.media_item.second_combo_box.addItem.assert_has_calls(
+                [call('', None), call('Bible 1', bible_1), call('Bible 2', bible_2), call('Bible 3', bible_3)])
+
+    def test_reload_bibles(self):
+        """
+        Test reload_bibles
+        """
+        # GIVEN: An instance of :class:`MediaManagerItem` and mocked out settings class with known values
+        with patch.object(self.media_item, 'populate_bible_combo_boxes') as mocked_populate_bible_combo_boxes:
+            # WHEN: Calling reload_bibles()
+            self.media_item.reload_bibles()
+
+            # THEN: The manager reload_bibles method should have been called and the bible combo boxes updated
+            self.mocked_plugin.manager.reload_bibles.assert_called_once_with()
+            mocked_populate_bible_combo_boxes.assert_called_once_with()
+
+    def test_get_common_books_no_second_book(self):
+        """
+        Test get_common_books when called with out a second bible
+        """
+        # GIVEN: An instance of :class:`MediaManagerItem` and a mocked first bible
+        # WHEN: Calling get_common_books with only one bible
+        result = self.media_item.get_common_books(self.mocked_bible_1)
+
+        # THEN: The book of the bible should be returned
+        self.assertEqual(result, self.book_list_1)
+
+    def test_get_common_books_second_book(self):
+        """
+        Test get_common_books when called with a second bible
+        """
+        # GIVEN: An instance of :class:`MediaManagerItem` and two mocked bibles with differing books
+        # WHEN: Calling get_common_books with two bibles
+        result = self.media_item.get_common_books(self.mocked_bible_1, self.mocked_bible_2)
+
+        # THEN: Only the books contained in both bibles should be returned
+        self.assertEqual(result, [self.mocked_book_2, self.mocked_book_3])
+
+    def test_initialise_advanced_bible_no_bible(self):
+        """
+        Test initialise_advanced_bible when there is no main bible
+        """
+        # GIVEN: An instance of :class:`MediaManagerItem`
+        self.media_item.select_book_combo_box = MagicMock()
+        with patch.object(self.media_item, 'get_common_books') as mocked_get_common_books:
+
+            # WHEN: Calling initialise_advanced_bible() when there is no main bible
+            self.media_item.bible = None
+            result = self.media_item.initialise_advanced_bible()
+
+            # THEN: initialise_advanced_bible should return with put calling get_common_books
+            self.assertIsNone(result)
+            mocked_get_common_books.assert_not_called()
+
+    def test_initialise_advanced_bible_add_books_with_last_id_found(self):
+        """
+        Test initialise_advanced_bible when the last_id argument is supplied and it is found in the list
+        """
+        # GIVEN: An instance of :class:`MediaManagerItem` and a mocked_book_combo_box which simulates data being found
+        #        in the list
+        self.media_item.select_book_combo_box = MagicMock(**{'findData.return_value': 2})
+        with patch.object(self.media_item, 'get_common_books', return_value=self.book_list_1), \
+                patch.object(self.media_item, 'on_advanced_book_combo_box'):
+
+            # WHEN: Calling initialise_advanced_bible() with the last_id argument set
+            self.media_item.bible = MagicMock()
+            self.media_item.initialise_advanced_bible(10)
+
+            # THEN: The books should be added to the combo box, and the chosen book should be reselected
+            self.media_item.select_book_combo_box.addItem.assert_has_calls(
+                [call('Book 1', 1), call('Book 2', 2), call('Book 3', 3)])
+            self.media_item.select_book_combo_box.setCurrentIndex.assert_called_once_with(2)
+
+    def test_initialise_advanced_bible_add_books_with_last_id_not_found(self):
+        """
+        Test initialise_advanced_bible when the last_id argument is supplied and it is not found in the list
+        """
+        # GIVEN: An instance of :class:`MediaManagerItem` and a mocked_book_combo_box which simulates data not being
+        #          found in the list
+        self.media_item.select_book_combo_box = MagicMock(**{'findData.return_value': -1})
+        with patch.object(self.media_item, 'get_common_books', return_value=self.book_list_1), \
+                patch.object(self.media_item, 'on_advanced_book_combo_box'):
+
+            # WHEN: Calling initialise_advanced_bible() with the last_id argument set
+            self.media_item.bible = MagicMock()
+            self.media_item.initialise_advanced_bible(10)
+
+            # THEN: The books should be added to the combo box, and the first book should be selected
+            self.media_item.select_book_combo_box.addItem.assert_has_calls(
+                [call('Book 1', 1), call('Book 2', 2), call('Book 3', 3)])
+            self.media_item.select_book_combo_box.setCurrentIndex.assert_called_once_with(0)
+
+    def test_update_auto_completer_search_no_bible(self):
+        """
+        Test update_auto_completer when there is no main bible selected and the search_edit type is
+        'BibleSearch.Reference'
+        """
+        # GIVEN: An instance of :class:`MediaManagerItem` and a mocked search_edit
+        mocked_search_edit = MagicMock(**{'current_search_type.return_value': BibleSearch.Reference})
+        self.media_item.search_edit = mocked_search_edit
+        self.media_item.bible = None
+        with patch.object(self.media_item, 'get_common_books') as mocked_get_common_books, \
+                patch('openlp.plugins.bibles.lib.mediaitem.set_case_insensitive_completer') \
+                as mocked_set_case_insensitive_completer:
+
+            # WHEN: Calling update_auto_completer
+            self.media_item.update_auto_completer()
+
+            # THEN: get_common_books should not have been called. set_case_insensitive_completer should have been called
+            #       with an empty list
+            mocked_get_common_books.assert_not_called()
+            mocked_set_case_insensitive_completer.assert_called_once_with([], mocked_search_edit)
+
+    def test_update_auto_completer_search_reference_type(self):
+        """
+        Test update_auto_completer when a main bible is selected and the search_edit type is 'BibleSearch.Reference'
+        """
+        # GIVEN: An instance of :class:`MediaManagerItem` and a mocked search_edit
+        mocked_search_edit = MagicMock(**{'current_search_type.return_value': BibleSearch.Reference})
+        self.media_item.search_edit = mocked_search_edit
+        self.media_item.bible = MagicMock()
+        with patch.object(self.media_item, 'get_common_books', return_value=self.book_list_1), \
+                patch('openlp.plugins.bibles.lib.mediaitem.get_locale_key', side_effect=lambda x: x), \
+                patch('openlp.plugins.bibles.lib.mediaitem.set_case_insensitive_completer') \
+                as mocked_set_case_insensitive_completer:
+
+            # WHEN: Calling update_auto_completer
+            self.media_item.update_auto_completer()
+
+            # THEN: set_case_insensitive_completer should have been called with the names of the books in order
+            mocked_set_case_insensitive_completer.assert_called_once_with(
+                ['Book 1', 'Book 2', 'Book 3'], mocked_search_edit)
+
+    def test_update_auto_completer_search_combined_type(self):
+        """
+        Test update_auto_completer when a main bible is selected and the search_edit type is 'BibleSearch.Combined'
+        """
+        # GIVEN: An instance of :class:`MediaManagerItem` and a mocked search_edit
+        mocked_search_edit = MagicMock(**{'current_search_type.return_value': BibleSearch.Combined})
+        self.media_item.search_edit = mocked_search_edit
+        self.media_item.bible = MagicMock()
+        with patch.object(self.media_item, 'get_common_books', return_value=self.book_list_1), \
+                patch('openlp.plugins.bibles.lib.mediaitem.get_locale_key', side_effect=lambda x: x), \
+                patch('openlp.plugins.bibles.lib.mediaitem.set_case_insensitive_completer') \
+                as mocked_set_case_insensitive_completer:
+
+            # WHEN: Calling update_auto_completer
+            self.media_item.update_auto_completer()
+
+            # THEN: set_case_insensitive_completer should have been called with the names of the books in order
+            mocked_set_case_insensitive_completer.assert_called_once_with(
+                ['Book 1', 'Book 2', 'Book 3'], mocked_search_edit)
+
+    def test_on_import_click_no_import_wizzard_attr(self):
+        """
+        Test on_import_click when media_item does not have the `import_wizard` attribute. And the wizard was canceled.
+        """
+        # GIVEN: An instance of :class:`MediaManagerItem` and a mocked BibleImportForm
+        mocked_bible_import_form_instance = MagicMock(**{'exec.return_value': False})
+        with patch('openlp.plugins.bibles.lib.mediaitem.BibleImportForm',
+                   return_value=mocked_bible_import_form_instance) as mocked_bible_import_form, \
+                patch.object(self.media_item, 'reload_bibles') as mocked_reload_bibles:
+
+            # WHEN: Calling on_import_click
+            self.media_item.on_import_click()
+
+            # THEN: BibleImport wizard should have been instianted and reload_bibles should not have been called
+            self.assertTrue(mocked_bible_import_form.called)
+            self.assertFalse(mocked_reload_bibles.called)
+
+    def test_on_import_click_wizzard_not_canceled(self):
+        """
+        Test on_import_click when the media item has the import_wizzard attr set and wizard completes sucessfully.
+        """
+        # GIVEN: An instance of :class:`MediaManagerItem` and a mocked import_wizard
+        mocked_import_wizard = MagicMock(**{'exec.return_value': True})
+        self.media_item.import_wizard = mocked_import_wizard
+
+        with patch.object(self.media_item, 'reload_bibles') as mocked_reload_bibles:
+
+            # WHEN: Calling on_import_click
+            self.media_item.on_import_click()
+
+            # THEN: BibleImport wizard should have been instianted and reload_bibles should not have been called
+            self.assertFalse(mocked_import_wizard.called)
+            self.assertTrue(mocked_reload_bibles.called)
+
+    def test_on_edit_click_no_bible(self):
+        """
+        Test on_edit_click when there is no main bible selected
+        """
+        # GIVEN: An instance of :class:`MediaManagerItem`
+        with patch('openlp.plugins.bibles.lib.mediaitem.EditBibleForm') as mocked_edit_bible_form:
+
+            # WHEN: A main bible is not selected and on_edit_click is called
+            self.media_item.bible = None
+            self.media_item.on_edit_click()
+
+            # THEN: EditBibleForm should not have been instianted
+            self.assertFalse(mocked_edit_bible_form.called)
+
+    def test_on_edit_click_user_cancel_edit_form(self):
+        """
+        Test on_edit_click when the user cancels the EditBibleForm
+        """
+        # GIVEN: An instance of :class:`MediaManagerItem` and a mocked EditBibleForm which returns False when exec is
+        #        called
+        self.media_item.bible = MagicMock()
+        mocked_edit_bible_form_instance = MagicMock(**{'exec.return_value': False})
+        with patch('openlp.plugins.bibles.lib.mediaitem.EditBibleForm', return_value=mocked_edit_bible_form_instance) \
+                as mocked_edit_bible_form, \
+                patch.object(self.media_item, 'reload_bibles') as mocked_reload_bibles:
+
+            # WHEN: on_edit_click is called, and the user cancels the EditBibleForm
+            self.media_item.on_edit_click()
+
+            # THEN: EditBibleForm should have been been instianted but reload_bibles should not have been called
+            self.assertTrue(mocked_edit_bible_form.called)
+            self.assertFalse(mocked_reload_bibles.called)
+
+    def test_on_edit_click_user_accepts_edit_form(self):
+        """
+        Test on_edit_click when the user accepts the EditBibleForm
+        """
+        # GIVEN: An instance of :class:`MediaManagerItem` and a mocked EditBibleForm which returns True when exec is
+        #        called
+        self.media_item.bible = MagicMock()
+        mocked_edit_bible_form_instance = MagicMock(**{'exec.return_value': True})
+        with patch('openlp.plugins.bibles.lib.mediaitem.EditBibleForm',
+                   return_value=mocked_edit_bible_form_instance) \
+                as mocked_edit_bible_form, \
+                patch.object(self.media_item, 'reload_bibles') as mocked_reload_bibles:
+
+            # WHEN: on_edit_click is called, and the user accpets the EditBibleForm
+            self.media_item.on_edit_click()
+
+            # THEN: EditBibleForm should have been been instianted and reload_bibles should have been called
+            self.assertTrue(mocked_edit_bible_form.called)
+            self.assertTrue(mocked_reload_bibles.called)
+
+    def test_on_delete_click_no_bible(self):
+        """
+        Test on_delete_click when there is no main bible selected
+        """
+        # GIVEN: An instance of :class:`MediaManagerItem`
+        with patch('openlp.plugins.bibles.lib.mediaitem.QtWidgets.QMessageBox') as mocked_qmessage_box:
+
+            # WHEN: A main bible is not selected and on_delete_click is called
+            self.media_item.bible = None
+            self.media_item.on_delete_click()
+
+            # THEN: QMessageBox.question should not have been called
+            self.assertFalse(mocked_qmessage_box.question.called)
+
+    def test_on_delete_click_response_no(self):
+        """
+        Test on_delete_click when the user selects no from the message box
+        """
+        # GIVEN: An instance of :class:`MediaManagerItem` and a QMessageBox which reutrns QtWidgets.QMessageBox.No
+        self.media_item.bible = MagicMock()
+        with patch('openlp.plugins.bibles.lib.mediaitem.QtWidgets.QMessageBox.question',
+                   return_value=QtWidgets.QMessageBox.No) as mocked_qmessage_box:
+
+            # WHEN: on_delete_click is called
+            self.media_item.on_delete_click()
+
+            # THEN: QMessageBox.question should have been called, but the delete_bible should not have been called
+            self.assertTrue(mocked_qmessage_box.called)
+            self.assertFalse(self.mocked_plugin.manager.delete_bible.called)
+
+    def test_on_delete_click_response_yes(self):
+        """
+        Test on_delete_click when the user selects yes from the message box
+        """
+        # GIVEN: An instance of :class:`MediaManagerItem` and a QMessageBox which reutrns QtWidgets.QMessageBox.Yes
+        self.media_item.bible = MagicMock()
+        with patch('openlp.plugins.bibles.lib.mediaitem.QtWidgets.QMessageBox.question',
+                   return_value=QtWidgets.QMessageBox.Yes) as mocked_qmessage_box, \
+                patch.object(self.media_item, 'reload_bibles'):
+
+            # WHEN: on_delete_click is called
+            self.media_item.on_delete_click()
+
+            # THEN: QMessageBox.question should and delete_bible should not have been called
+            self.assertTrue(mocked_qmessage_box.called)
+            self.assertTrue(self.mocked_plugin.manager.delete_bible.called)
+
+    def test_on_search_tab_bar_current_changed_search_tab_selected(self):
+        """
+        Test on_search_tab_bar_current_changed when the search_tab is selected
+        """
+        # GIVEN: An instance of :class:`MediaManagerItem` and mocked out search_tab and select_tab
+        self.media_item.search_tab = MagicMock()
+        self.media_item.select_tab = MagicMock()
+        with patch.object(self.media_item, 'on_focus'):
+
+            # WHEN: The search_tab has been selected
+            self.media_item.on_search_tab_bar_current_changed(0)
+
+            # THEN: search_tab should be setVisible and select_tab should be hidder
+            self.media_item.search_tab.setVisible.assert_called_once_with(True)
+            self.media_item.select_tab.setVisible.assert_called_once_with(False)
+
+    def test_on_search_tab_bar_current_changed_select_tab_selected(self):
+        """
+        Test on_search_tab_bar_current_changed when the select_tab is selected
+        """
+        # GIVEN: An instance of :class:`MediaManagerItem` and mocked out search_tab and select_tab
+        self.media_item.search_tab = MagicMock()
+        self.media_item.select_tab = MagicMock()
+        with patch.object(self.media_item, 'on_focus'):
+
+            # WHEN: The select_tab has been selected
+            self.media_item.on_search_tab_bar_current_changed(1)
+
+            # THEN: search_tab should be setVisible and select_tab should be hidder
+            self.media_item.search_tab.setVisible.assert_called_once_with(False)
+            self.media_item.select_tab.setVisible.assert_called_once_with(True)
+
+    def test_on_book_order_button_toggled_checked(self):
+        """
+        Test that 'on_book_order_button_toggled' changes the order of the book list
+        """
+        self.media_item.select_book_combo_box = MagicMock()
+
+        # WHEN: When the book_order_button is checked
+        self.media_item.on_book_order_button_toggled(True)
+
+        # THEN: The select_book_combo_box model should have been sorted
+        self.media_item.select_book_combo_box.model().sort.assert_called_once_with(0)
+
+    def test_on_book_order_button_toggled_un_checked(self):
+        """
+        Test that 'on_book_order_button_toggled' changes the order of the book list
+        """
+        self.media_item.select_book_combo_box = MagicMock()
+
+        # WHEN: When the book_order_button is un-checked
+        self.media_item.on_book_order_button_toggled(False)
+
+        # THEN: The select_book_combo_box model sort should have been reset
+        self.media_item.select_book_combo_box.model().sort.assert_called_once_with(-1)
 
     def test_on_clear_button_clicked(self):
         """
-        Test that the on_clear_button_clicked works properly. (Used by Bible search tab)
+        Test on_clear_button_clicked
         """
-        # GIVEN: Mocked list_view, check_search_results & quick_search_edit.
+        # GIVEN: An instance of :class:`MediaManagerItem` and mocked out search_tab and select_tab and a mocked out
+        #        list_view and search_edit
         self.media_item.list_view = MagicMock()
-        self.media_item.quick_search_edit = MagicMock()
-
-        # WHEN: on_clear_button_clicked is called
-        self.media_item.on_clear_button_clicked()
-
-        # THEN: Search result should be reset and search field should receive focus.
-        self.media_item.list_view.clear.assert_called_once_with(),
-        self.media_item.quick_search_edit.clear.assert_called_once_with(),
-        self.media_item.quick_search_edit.setFocus.assert_called_once_with()
+        self.media_item.search_edit = MagicMock()
+        with patch.object(self.media_item, 'on_focus'):
+
+            # WHEN: Calling on_clear_button_clicked
+            self.media_item.on_clear_button_clicked()
+
+            # THEN: The list_view and the search_edit should be cleared
+            self.media_item.list_view.clear.assert_called_once_with()
+            self.media_item.search_edit.clear.assert_called_once_with()
 
     def test_on_lock_button_toggled_search_tab_lock_icon(self):
         """
-        Test that "on_lock_button_toggled" gives focus to the right field and toggles the lock properly.
+        Test that "on_lock_button_toggled" toggles the lock properly.
         """
-        # GIVEN: Mocked sender & Search edit, quickTab returning value = True on isVisible.
-        self.media_item.sender = MagicMock()
-        self.media_item.quick_search_edit = MagicMock()
-        self.media_item.quickTab = MagicMock(**{'isVisible.return_value': True})
-
+        # GIVEN: An instance of :class:`MediaManagerItem` a mocked sender and list_view
+        self.media_item.list_view = MagicMock()
         self.media_item.lock_icon = 'lock icon'
-        sender_instance_mock = MagicMock()
-        self.media_item.sender = MagicMock(return_value=sender_instance_mock)
-
-        # WHEN: on_lock_button_toggled is called and checked returns = True.
-        self.media_item.on_lock_button_toggled(True)
-
-        # THEN: on_quick_search_edit should receive focus and Lock icon should be set.
-        self.media_item.quick_search_edit.setFocus.assert_called_once_with()
-        sender_instance_mock.setIcon.assert_called_once_with('lock icon')
+        mocked_sender_instance = MagicMock()
+        with patch.object(self.media_item, 'sender', return_value=mocked_sender_instance):
+
+            # WHEN: When the lock_button is checked
+            self.media_item.on_lock_button_toggled(True)
+
+            # THEN: list_view should be 'locked' and the lock icon set
+            self.assertTrue(self.media_item.list_view.locked)
+            mocked_sender_instance.setIcon.assert_called_once_with('lock icon')
 
     def test_on_lock_button_toggled_unlock_icon(self):
         """
-         Test that lock button unlocks properly and lock toggles properly.
+        Test that "on_lock_button_toggled" toggles the lock properly.
         """
-        # GIVEN: Mocked sender & Search edit, quickTab returning value = False on isVisible.
-        self.media_item.sender = MagicMock()
-        self.media_item.quick_search_edit = MagicMock()
-        self.media_item.quickTab = MagicMock()
-        self.media_item.quickTab.isVisible = MagicMock()
+        # GIVEN: An instance of :class:`MediaManagerItem` a mocked sender and list_view
+        self.media_item.list_view = MagicMock()
         self.media_item.unlock_icon = 'unlock icon'
-        sender_instance_mock = MagicMock()
-        self.media_item.sender = MagicMock(return_value=sender_instance_mock)
-
-        # WHEN: on_lock_button_toggled is called and checked returns = False.
-        self.media_item.on_lock_button_toggled(False)
-
-        # THEN: Unlock icon should be set.
-        sender_instance_mock.setIcon.assert_called_once_with('unlock icon')
+        mocked_sender_instance = MagicMock()
+        with patch.object(self.media_item, 'sender', return_value=mocked_sender_instance):
+
+            # WHEN: When the lock_button is unchecked
+            self.media_item.on_lock_button_toggled(False)
+
+            # THEN: list_view should be 'unlocked' and the unlock icon set
+            self.assertFalse(self.media_item.list_view.locked)
+            mocked_sender_instance.setIcon.assert_called_once_with('unlock icon')
+
+    def test_on_style_combo_box_changed(self):
+        """
+        Test on_style_combo_box_index_changed
+        """
+        # GIVEN: An instance of :class:`MediaManagerItem` a mocked media_item.settings
+        self.media_item.settings = MagicMock()
+
+        # WHEN: Calling on_style_combo_box_index_changed
+        self.media_item.on_style_combo_box_index_changed(2)
+
+        # THEN: The layput_style settimg should have been set
+        self.assertEqual(self.media_item.settings.layout_style, 2)
+        self.media_item.settings.layout_style_combo_box.setCurrentIndex.assert_called_once_with(2)
+        self.mocked_settings_instance.setValue.assert_called_once_with('bibles/verse layout style', 2)
+
+    def test_on_version_combo_box_index_changed_no_bible(self):
+        """
+        Test on_version_combo_box_index_changed when there is no main bible.
+        """
+        # GIVEN: An instance of :class:`MediaManagerItem` and mocked media_item.settings and select_book_combo_box
+        self.media_item.version_combo_box = MagicMock(**{'currentData.return_value': None})
+        self.media_item.select_book_combo_box = MagicMock()
+        with patch.object(self.media_item, 'initialise_advanced_bible') as mocked_initialise_advanced_bible:
+
+            # WHEN: Calling on_version_combo_box_index_changed
+            self.media_item.on_version_combo_box_index_changed()
+
+            # THEN: The vesion should be saved to settings and the 'select tab' should be initialised
+            self.assertFalse(self.mocked_settings_instance.setValue.called)
+            self.assertTrue(self.media_item.initialise_advanced_bible.called)
+
+    def test_on_version_combo_box_index_changed_bible_selected(self):
+        """
+        Test on_version_combo_box_index_changed when a bible has been selected.
+        """
+        # GIVEN: An instance of :class:`MediaManagerItem` and mocked media_item.settings and select_book_combo_box
+        mocked_bible_db = MagicMock()
+        mocked_bible_db.name = 'ABC'
+        self.media_item.version_combo_box = MagicMock(**{'currentData.return_value': mocked_bible_db})
+        self.media_item.select_book_combo_box = MagicMock()
+        with patch.object(self.media_item, 'initialise_advanced_bible') as mocked_initialise_advanced_bible:
+
+            # WHEN: Calling on_version_combo_box_index_changed
+            self.media_item.on_version_combo_box_index_changed()
+
+            # THEN: The vesion should be saved to settings and the 'select tab' should be initialised
+            self.mocked_settings_instance.setValue.assert_called_once_with('bibles/primary bible', 'ABC')
+            self.assertTrue(self.media_item.initialise_advanced_bible.called)
+
+    def test_on_second_combo_box_index_changed_mode_not_changed(self):
+        """
+        Test on_second_combo_box_index_changed when the user does not change from dual mode
+        results and the user chooses no to the message box
+        """
+        # GIVEN: An instance of :class:`MediaManagerItem`
+        self.media_item.list_view = MagicMock(**{'count.return_value': 5})
+        self.media_item.style_combo_box = MagicMock()
+        self.media_item.select_book_combo_box = MagicMock()
+        with patch.object(self.media_item, 'initialise_advanced_bible') as mocked_initialise_advanced_bible, \
+                patch('openlp.plugins.bibles.lib.mediaitem.critical_error_message_box') \
+                as mocked_critical_error_message_box:
+
+            # WHEN: The previously selected bible is one bible and the new selection is annother bible
+            self.media_item.second_bible = self.mocked_bible_1
+            self.media_item.second_combo_box = MagicMock(**{'currentData.return_value': self.mocked_bible_2})
+            self.media_item.on_second_combo_box_index_changed(5)
+
+            # THEN: The new bible should now be the current bible
+            self.assertFalse(mocked_critical_error_message_box.called)
+            self.media_item.style_combo_box.setEnabled.assert_called_once_with(False)
+            self.assertEqual(self.media_item.second_bible, self.mocked_bible_2)
+
+    def test_on_second_combo_box_index_changed_single_to_dual_user_abort(self):
+        """
+        Test on_second_combo_box_index_changed when the user changes from single to dual bible mode, there are search
+        results and the user chooses no to the message box
+        """
+        # GIVEN: An instance of :class:`MediaManagerItem`
+        self.media_item.list_view = MagicMock(**{'count.return_value': 5})
+        self.media_item.style_combo_box = MagicMock()
+        self.media_item.select_book_combo_box = MagicMock()
+        with patch.object(self.media_item, 'initialise_advanced_bible') as mocked_initialise_advanced_bible, \
+                patch('openlp.plugins.bibles.lib.mediaitem.critical_error_message_box',
+                      return_value=QtWidgets.QMessageBox.No) as mocked_critical_error_message_box:
+
+            # WHEN: The previously selected bible is None and the new selection is a bible and the user selects yes
+            #       to the dialog box
+            self.media_item.second_bible = None
+            self.media_item.second_combo_box = MagicMock(**{'currentData.return_value': self.mocked_bible_1})
+            self.media_item.on_second_combo_box_index_changed(5)
+
+            # THEN: The list_view should be cleared and the currently selected bible should not be channged
+            self.assertTrue(mocked_critical_error_message_box.called)
+            self.assertTrue(self.media_item.second_combo_box.setCurrentIndex.called)
+            self.assertFalse(self.media_item.style_combo_box.setEnabled.called)
+            self.assertEqual(self.media_item.second_bible, None)
+
+    def test_on_second_combo_box_index_changed_single_to_dual(self):
+        """
+        Test on_second_combo_box_index_changed when the user changes from single to dual bible mode, there are search
+        results and the user chooses yes to the message box
+        """
+        # GIVEN: An instance of :class:`MediaManagerItem`
+        self.media_item.list_view = MagicMock(**{'count.return_value': 5})
+        self.media_item.style_combo_box = MagicMock()
+        self.media_item.select_book_combo_box = MagicMock()
+        with patch.object(self.media_item, 'initialise_advanced_bible') as mocked_initialise_advanced_bible, \
+                patch('openlp.plugins.bibles.lib.mediaitem.critical_error_message_box',
+                      return_value=QtWidgets.QMessageBox.Yes) as mocked_critical_error_message_box:
+
+            # WHEN: The previously selected bible is None and the new selection is a bible and the user selects yes
+            #       to the dialog box
+            self.media_item.second_bible = None
+            self.media_item.second_combo_box = MagicMock(**{'currentData.return_value': self.mocked_bible_1})
+            self.media_item.on_second_combo_box_index_changed(5)
+
+            # THEN: The list_view should be cleared and the selected bible should be set as the current bible
+            self.assertTrue(mocked_critical_error_message_box.called)
+            self.media_item.list_view.clear.assert_called_once_with(override_lock=True)
+            self.media_item.style_combo_box.setEnabled.assert_called_once_with(False)
+            self.assertTrue(mocked_initialise_advanced_bible.called)
+            self.assertEqual(self.media_item.second_bible, self.mocked_bible_1)
+
+    def test_on_second_combo_box_index_changed_dual_to_single(self):
+        """
+        Test on_second_combo_box_index_changed when the user changes from dual to single bible mode, there are search
+        results and the user chooses yes to the message box
+        """
+        # GIVEN: An instance of :class:`MediaManagerItem`
+        self.media_item.list_view = MagicMock(**{'count.return_value': 5})
+        self.media_item.style_combo_box = MagicMock()
+        self.media_item.select_book_combo_box = MagicMock()
+        with patch.object(self.media_item, 'initialise_advanced_bible') as mocked_initialise_advanced_bible, \
+                patch('openlp.plugins.bibles.lib.mediaitem.critical_error_message_box',
+                      return_value=QtWidgets.QMessageBox.Yes) as mocked_critical_error_message_box:
+            # WHEN: The previously is a bible new selection is None and the user selects yes
+            #       to the dialog box
+            self.media_item.second_bible = self.mocked_bible_1
+            self.media_item.second_combo_box = MagicMock(**{'currentData.return_value': None})
+            self.media_item.on_second_combo_box_index_changed(0)
+
+            # THEN: The list_view should be cleared and the selected bible should be set as the current bible
+            self.assertTrue(mocked_critical_error_message_box.called)
+            self.media_item.list_view.clear.assert_called_once_with(override_lock=True)
+            self.media_item.style_combo_box.setEnabled.assert_called_once_with(True)
+            self.assertFalse(mocked_initialise_advanced_bible.called)
+            self.assertEqual(self.media_item.second_bible, None)
+
+    def test_on_advanced_book_combo_box(self):
+        """
+        Test on_advanced_book_combo_box when the book returns 0 for the verse count.
+        """
+        # GIVEN: An instance of :class:`MediaManagerItem` and a mocked get_verse_count_by_book_ref_id which returns 0
+        self.media_item.select_book_combo_box = MagicMock(**{'currentData.return_value': 2})
+        self.media_item.bible = self.mocked_bible_1
+        self.mocked_plugin.manager.get_verse_count_by_book_ref_id.return_value = 0
+        self.media_item.search_button = MagicMock()
+        with patch('openlp.plugins.bibles.lib.mediaitem.critical_error_message_box') \
+                as mocked_critical_error_message_box:
+
+            # WHEN: Calling on_advanced_book_combo_box
+            self.media_item.on_advanced_book_combo_box()
+
+            # THEN: The user should be informed  that the bible cannot be used and the search button should be disabled
+            self.mocked_plugin.manager.get_book_by_id.assert_called_once_with('Bible 1', 2)
+            self.media_item.search_button.setEnabled.assert_called_once_with(False)
+            self.assertTrue(mocked_critical_error_message_box.called)
+
+    def test_on_advanced_book_combo_box_set_up_comboboxes(self):
+        """
+        Test on_advanced_book_combo_box when the book returns 6 for the verse count.
+        """
+        # GIVEN: An instance of :class:`MediaManagerItem` and a mocked get_verse_count_by_book_ref_id which returns 6
+        self.media_item.from_chapter = 0
+        self.media_item.to_chapter = 0
+        self.media_item.from_verse = 0
+        self.media_item.to_verse = 0
+        self.media_item.select_book_combo_box = MagicMock(**{'currentData.return_value': 2})
+        self.media_item.bible = self.mocked_bible_1
+        self.mocked_plugin.manager.get_verse_count_by_book_ref_id.return_value = 6
+        self.media_item.search_button = MagicMock()
+        with patch.object(self.media_item, 'adjust_combo_box') as mocked_adjust_combo_box:
+            # WHEN: Calling on_advanced_book_combo_box
+            self.media_item.on_advanced_book_combo_box()
+
+            # THEN: The verse selection combobox's should be set up
+            self.mocked_plugin.manager.get_book_by_id.assert_called_once_with('Bible 1', 2)
+            self.media_item.search_button.setEnabled.assert_called_once_with(True)
+            self.assertEqual(mocked_adjust_combo_box.call_count, 4)
+
+    def test_on_from_chapter_activated_invalid_to_chapter(self):
+        """
+        Test on_from_chapter_activated when the to_chapter is less than the from_chapter
+        """
+        # GIVEN: An instance of :class:`MediaManagerItem`, some mocked comboboxes with test data
+        self.media_item.chapter_count = 25
+        self.media_item.bible = self.mocked_bible_1
+        self.media_item.select_book_combo_box = MagicMock()
+        self.media_item.from_chapter = MagicMock(**{'currentData.return_value': 10})
+        self.media_item.to_chapter = MagicMock(**{'currentData.return_value': 5})
+        self.media_item.from_verse = MagicMock()
+        self.media_item.to_verse = MagicMock()
+        self.mocked_plugin.manager.get_verse_count_by_book_ref_id.return_value = 20
+        with patch.object(self.media_item, 'adjust_combo_box') as mocked_adjust_combo_box:
+
+            # WHEN: Calling on_from_chapter_activated
+            self.media_item.on_from_chapter_activated()
+
+            # THEN: The to_verse and to_chapter comboboxes should be updated appropriately
+            self.assertEqual(mocked_adjust_combo_box.call_args_list, [
+                call(1, 20, self.media_item.from_verse), call(1, 20, self.media_item.to_verse, False),
+                call(10, 25, self.media_item.to_chapter, False)])
+
+    def test_on_from_chapter_activated_same_chapter(self):
+        """
+        Test on_from_chapter_activated when the to_chapter is the same as from_chapter
+        """
+        # GIVEN: An instance of :class:`MediaManagerItem`, some mocked comboboxes with test data
+        self.media_item.chapter_count = 25
+        self.media_item.bible = self.mocked_bible_1
+        self.media_item.select_book_combo_box = MagicMock()
+        self.media_item.from_chapter = MagicMock(**{'currentData.return_value': 5})
+        self.media_item.to_chapter = MagicMock(**{'currentData.return_value': 5})
+        self.media_item.from_verse = MagicMock()
+        self.media_item.to_verse = MagicMock()
+        self.mocked_plugin.manager.get_verse_count_by_book_ref_id.return_value = 20
+        with patch.object(self.media_item, 'adjust_combo_box') as mocked_adjust_combo_box:
+
+            # WHEN: Calling on_from_chapter_activated
+            self.media_item.on_from_chapter_activated()
+
+            # THEN: The to_verse and to_chapter comboboxes should be updated appropriately
+            self.assertEqual(mocked_adjust_combo_box.call_args_list, [
+                call(1, 20, self.media_item.from_verse), call(1, 20, self.media_item.to_verse, True),
+                call(5, 25, self.media_item.to_chapter, False)])
+
+    def test_on_from_chapter_activated_lower_chapter(self):
+        """
+        Test on_from_chapter_activated when the to_chapter is greater than the from_chapter
+        """
+        # GIVEN: An instance of :class:`MediaManagerItem`, some mocked comboboxes with test data
+        self.media_item.chapter_count = 25
+        self.media_item.bible = self.mocked_bible_1
+        self.media_item.select_book_combo_box = MagicMock()
+        self.media_item.from_chapter = MagicMock(**{'currentData.return_value': 5})
+        self.media_item.to_chapter = MagicMock(**{'currentData.return_value': 7})
+        self.media_item.from_verse = MagicMock()
+        self.media_item.to_verse = MagicMock()
+        self.mocked_plugin.manager.get_verse_count_by_book_ref_id.return_value = 20
+        with patch.object(self.media_item, 'adjust_combo_box') as mocked_adjust_combo_box:
+            # WHEN: Calling on_from_chapter_activated
+            self.media_item.on_from_chapter_activated()
+
+            # THEN: The to_verse and to_chapter comboboxes should be updated appropriately
+            self.assertEqual(mocked_adjust_combo_box.call_args_list, [
+                call(1, 20, self.media_item.from_verse), call(5, 25, self.media_item.to_chapter, True)])
+
+    def test_on_from_verse(self):
+        """
+        Test on_from_verse when the to_chapter is not equal to the from_chapter
+        """
+        # GIVEN: An instance of :class:`MediaManagerItem`, some mocked comboboxes with test data
+        self.media_item.select_book_combo_box = MagicMock()
+        self.media_item.from_chapter = MagicMock(**{'currentData.return_value': 2})
+        self.media_item.to_chapter = MagicMock(**{'currentData.return_value': 5})
+
+        # WHEN: Calling on_from_verse
+        self.media_item.on_from_verse()
+
+        # THEN: select_book_combo_box.currentData should nto be called
+        self.assertFalse(self.media_item.select_book_combo_box.currentData.called)
+
+    def test_on_from_verse_equal(self):
+        """
+        Test on_from_verse when the to_chapter is equal to the from_chapter
+        """
+        # GIVEN: An instance of :class:`MediaManagerItem`, some mocked comboboxes with test data
+        self.media_item.bible = self.mocked_bible_1
+        self.media_item.select_book_combo_box = MagicMock()
+        self.media_item.from_chapter = MagicMock(**{'currentData.return_value': 5})
+        self.media_item.to_chapter = MagicMock(**{'currentData.return_value': 5})
+        self.media_item.from_verse = MagicMock(**{'currentData.return_value': 7})
+        self.media_item.to_verse = MagicMock()
+        self.mocked_plugin.manager.get_verse_count_by_book_ref_id.return_value = 20
+        with patch.object(self.media_item, 'adjust_combo_box') as mocked_adjust_combo_box:
+
+            # WHEN: Calling on_from_verse
+            self.media_item.on_from_verse()
+
+            # THEN: The to_verse should have been updated
+            mocked_adjust_combo_box.assert_called_once_with(7, 20, self.media_item.to_verse, True)
+
+    def test_on_to_chapter_same_chapter_from_greater_than(self):
+        """
+        Test on_to_chapter when the to_chapter is equal to the from_chapter and the from_verse is greater than the
+        to_verse
+        """
+        # GIVEN: An instance of :class:`MediaManagerItem`, some mocked comboboxes with test data
+        self.media_item.bible = self.mocked_bible_1
+        self.media_item.select_book_combo_box = MagicMock()
+        self.media_item.from_chapter = MagicMock(**{'currentData.return_value': 5})
+        self.media_item.to_chapter = MagicMock(**{'currentData.return_value': 5})
+        self.media_item.from_verse = MagicMock(**{'currentData.return_value': 10})
+        self.media_item.to_verse = MagicMock(**{'currentData.return_value': 7})
+        self.mocked_plugin.manager.get_verse_count_by_book_ref_id.return_value = 20
+        with patch.object(self.media_item, 'adjust_combo_box') as mocked_adjust_combo_box:
+
+            # WHEN: Calling on_tp_chapter
+            self.media_item.on_to_chapter()
+
+            # THEN: The to_verse should have been updated
+            mocked_adjust_combo_box.assert_called_once_with(10, 20, self.media_item.to_verse)
+
+    def test_on_from_verse_chapters_not_equal(self):
+        """
+        Test on_from_verse when the to_chapter is not equal to the from_chapter
+        """
+        # GIVEN: An instance of :class:`MediaManagerItem`, some mocked comboboxes with test data
+        self.media_item.bible = self.mocked_bible_1
+        self.media_item.select_book_combo_box = MagicMock()
+        self.media_item.from_chapter = MagicMock(**{'currentData.return_value': 7})
+        self.media_item.to_chapter = MagicMock(**{'currentData.return_value': 5})
+        self.media_item.from_verse = MagicMock(**{'currentData.return_value': 10})
+        self.media_item.to_verse = MagicMock(**{'currentData.return_value': 7})
+        self.mocked_plugin.manager.get_verse_count_by_book_ref_id.return_value = 20
+        with patch.object(self.media_item, 'adjust_combo_box') as mocked_adjust_combo_box:
+
+            # WHEN: Calling on_from_chapter_activated
+            self.media_item.on_to_chapter()
+
+            # THEN: The to_verse should have been updated
+            mocked_adjust_combo_box.assert_called_once_with(1, 20, self.media_item.to_verse)
+
+    def test_on_from_verse_from_verse_less_than(self):
+        """
+        Test on_from_verse when the to_chapter is equal to the from_chapter and from_verse is less than to_verse
+        """
+        # GIVEN: An instance of :class:`MediaManagerItem`, some mocked comboboxes with test data
+        self.media_item.bible = self.mocked_bible_1
+        self.media_item.select_book_combo_box = MagicMock()
+        self.media_item.from_chapter = MagicMock(**{'currentData.return_value': 5})
+        self.media_item.to_chapter = MagicMock(**{'currentData.return_value': 5})
+        self.media_item.from_verse = MagicMock(**{'currentData.return_value': 6})
+        self.media_item.to_verse = MagicMock(**{'currentData.return_value': 7})
+        self.mocked_plugin.manager.get_verse_count_by_book_ref_id.return_value = 20
+        with patch.object(self.media_item, 'adjust_combo_box') as mocked_adjust_combo_box:
+
+            # WHEN: Calling on_from_chapter_activated
+            self.media_item.on_to_chapter()
+
+            # THEN: The to_verse should have been updated
+            mocked_adjust_combo_box.assert_called_once_with(1, 20, self.media_item.to_verse)
+
+    def test_adjust_combo_box_no_restore(self):
+        """
+        Test adjust_combo_box  when being used with out the restore function
+        """
+        # GIVEN: An instance of :class:`MediaManagerItem`
+        mocked_combo_box = MagicMock()
+
+        # WHEN: Calling adjust_combo_box with out setting the kwarg `restore`
+        self.media_item.adjust_combo_box(10, 13, mocked_combo_box)
+
+        # THEN: The combo_box should be cleared, and new items added
+        mocked_combo_box.clear.assert_called_once_with()
+        self.assertEqual(mocked_combo_box.addItem.call_args_list,
+                         [call('10', 10), call('11', 11), call('12', 12), call('13', 13)])
+
+    def test_adjust_combo_box_restore_found(self):
+        """
+        Test adjust_combo_box  when being used with out the restore function
+        """
+        # GIVEN: An instance of :class:`MediaManagerItem`, with the 2nd item '12' selected
+        mocked_combo_box = MagicMock(**{'currentData.return_value': 12, 'findData.return_value': 2})
+
+        # WHEN: Calling adjust_combo_box with the kwarg `restore` set to True
+        self.media_item.adjust_combo_box(10, 13, mocked_combo_box, True)
+
+        # THEN: The combo_box should be cleared, and new items added. Finally the previously selected item should be
+        #       reselected
+        mocked_combo_box.clear.assert_called_once_with()
+        self.assertEqual(mocked_combo_box.addItem.call_args_list,
+                         [call('10', 10), call('11', 11), call('12', 12), call('13', 13)])
+        mocked_combo_box.setCurrentIndex.assert_called_once_with(2)
+
+    def test_adjust_combo_box_restore_not_found(self):
+        """
+        Test adjust_combo_box  when being used with out the restore function when the selected item is not available
+        after the combobox has been updated
+        """
+        # GIVEN: An instance of :class:`MediaManagerItem`, with the 2nd item '12' selected
+        mocked_combo_box = MagicMock(**{'currentData.return_value': 9, 'findData.return_value': -1})
+
+        # WHEN: Calling adjust_combo_box with the kwarg `restore` set to True
+        self.media_item.adjust_combo_box(10, 13, mocked_combo_box, True)
+
+        # THEN: The combo_box should be cleared, and new items added. Finally the first item should be selected
+        mocked_combo_box.clear.assert_called_once_with()
+        self.assertEqual(mocked_combo_box.addItem.call_args_list,
+                         [call('10', 10), call('11', 11), call('12', 12), call('13', 13)])
+        mocked_combo_box.setCurrentIndex.assert_called_once_with(0)
+
+    def test_on_search_button_no_bible(self):
+        """
+        Test on_search_button_clicked when there is no bible selected
+        """
+        # GIVEN: An instance of :class:`MediaManagerItem`
+        # WHEN calling on_search_button_clicked and there is no selected bible
+        self.media_item.bible = None
+        self.media_item.on_search_button_clicked()
+
+        # THEN: The user should be informed that there are no bibles selected
+        self.assertEqual(self.mocked_main_window.information_message.call_count, 1)
+
+    def test_on_search_button_search_tab(self):
+        """
+        Test on_search_button_clicked when the `Search` tab is selected
+        """
+        # GIVEN: An instance of :class:`MediaManagerItem`, and a mocked text_search method
+        self.media_item.bible = self.mocked_bible_1
+        self.media_item.search_button = MagicMock()
+        self.media_item.search_tab = MagicMock(**{'isVisible.return_value': True})
+        with patch.object(self.media_item, 'text_search') as mocked_text_search:
+
+            # WHEN: Calling on_search_button_clicked and the 'Search' tab is selected
+            self.media_item.on_search_button_clicked()
+
+            # THEN: The text_search method should have been called
+            mocked_text_search.assert_called_once_with()
+
+    def test_on_search_button_select_tab(self):
+        """
+        Test on_search_button_clicked when the `Select` tab is selected
+        """
+        # GIVEN: An instance of :class:`MediaManagerItem`, and a mocked select_search method
+        self.media_item.bible = self.mocked_bible_1
+        self.media_item.search_button = MagicMock()
+        self.media_item.search_tab = MagicMock(**{'isVisible.return_value': False})
+        self.media_item.select_tab = MagicMock(**{'isVisible.return_value': True})
+        with patch.object(self.media_item, 'select_search') as mocked_select_search:
+
+            # WHEN: Calling on_search_button_clicked and the 'Select' tab is selected
+            self.media_item.on_search_button_clicked()
+
+            # THEN: The text_search method should have been called
+            mocked_select_search.assert_called_once_with()
+
+    def test_select_search_single_bible(self):
+        """
+        Test select_search when only one bible is selected
+        """
+        # GIVEN: An instance of :class:`MediaManagerItem` and mocked plugin.manager.get_verses
+        self.media_item.select_book_combo_box = MagicMock()
+        self.media_item.from_chapter = MagicMock()
+        self.media_item.from_verse = MagicMock()
+        self.media_item.to_chapter = MagicMock()
+        self.media_item.to_verse = MagicMock()
+        with patch.object(self.media_item, 'display_results') as mocked_display_results:
+
+            # WHEN: Calling select_search and there is only one bible selected
+            self.media_item.bible = self.mocked_bible_1
+            self.media_item.second_bible = None
+            self.media_item.select_search()
+
+            # THEN: reference_search should only be called once
+            self.assertEqual(self.mocked_plugin.manager.get_verses.call_count, 1)
+            mocked_display_results.assert_called_once_with()
+
+    def test_select_search_dual_bibles(self):
+        """
+        Test select_search when two bibles are selected
+        """
+        # GIVEN: An instance of :class:`MediaManagerItem` and mocked_reference_search
+        self.media_item.select_book_combo_box = MagicMock()
+        self.media_item.from_chapter = MagicMock()
+        self.media_item.from_verse = MagicMock()
+        self.media_item.to_chapter = MagicMock()
+        self.media_item.to_verse = MagicMock()
+        with patch.object(self.media_item, 'display_results') as mocked_display_results:
+
+            # WHEN: Calling select_search and there are two bibles selected
+            self.media_item.bible = self.mocked_bible_1
+            self.media_item.second_bible = self.mocked_bible_2
+            self.media_item.select_search()
+
+            # THEN: reference_search should be called twice
+            self.assertEqual(self.mocked_plugin.manager.get_verses.call_count, 2)
+            mocked_display_results.assert_called_once_with()
+
+    def test_text_reference_search_single_bible(self):
+        """
+        Test text_reference_search when only one bible is selected
+        """
+        # GIVEN: An instance of :class:`MediaManagerItem` and mocked plugin.manager.get_verses
+        with patch.object(self.media_item, 'display_results') as mocked_display_results:
+
+            # WHEN: Calling text_reference_search with only one bible selected
+            self.media_item.bible = self.mocked_bible_1
+            self.media_item.second_bible = None
+            self.media_item.text_reference_search('Search Text')
+
+            # THEN: reference_search should only be called once
+            self.assertEqual(self.mocked_plugin.manager.get_verses.call_count, 1)
+            mocked_display_results.assert_called_once_with()
+
+            def text_reference_search(self, search_text, search_while_type=False):
+                """
+                We are doing a 'Reference Search'.
+                This search is called on def text_search by Reference and Combined Searches.
+                """
+                verse_refs = self.plugin.manager.parse_ref(self.bible.name, search_text)
+                self.search_results = self.reference_search(verse_refs, self.bible)
+                if self.second_bible and self.search_results:
+                    self.second_search_results = self.reference_search(verse_refs, self.second_bible)
+                self.display_results()
+
+    def test_text_reference_search_dual_bible_no_results(self):
+        """
+        Test text_reference_search when two bible are selected, but the search of the first bible does not return any
+        results
+        """
+        # GIVEN: An instance of :class:`MediaManagerItem` and mocked plugin.manager.get_verses
+        # WHEN: Calling text_reference_search with two bibles selected, but no results are found in the first bible
+        with patch.object(self.media_item, 'display_results') as mocked_display_results:
+            self.mocked_plugin.manager.get_verses.return_value = []
+            self.media_item.bible = self.mocked_bible_1
+            self.media_item.second_bible = self.mocked_bible_2
+            self.media_item.text_reference_search('Search Text')
+
+            # THEN: reference_search should only be called once
+            self.assertEqual(self.mocked_plugin.manager.get_verses.call_count, 1)
+            mocked_display_results.assert_called_once_with()
+
+    def test_text_reference_search_dual_bible(self):
+        """
+        Test text_reference_search when two bible are selected
+        """
+        # GIVEN: An instance of :class:`MediaManagerItem` and mocked plugin.manager.get_verses
+        with patch.object(self.media_item, 'display_results') as mocked_display_results:
+            self.media_item.bible = self.mocked_bible_1
+            self.media_item.second_bible = self.mocked_bible_2
+
+            # WHEN: Calling text_reference_search with two bibles selected
+            self.media_item.text_reference_search('Search Text')
+
+            # THEN: reference_search should be called twice
+            self.assertEqual(self.mocked_plugin.manager.get_verses.call_count, 2)
+            mocked_display_results.assert_called_once_with()
+
+    def test_on_text_search_single_bible(self):
+        """
+        Test on_text_search when only one bible is selected
+        """
+        # GIVEN: An instance of :class:`MediaManagerItem`
+        self.media_item.bible = self.mocked_bible_1
+        self.media_item.second_bible = None
+
+        # WHEN: Calling on_text_search and plugin.manager.verse_search returns a list of results
+        self.mocked_plugin.manager.verse_search.return_value = ['results', 'list']
+        with patch.object(self.media_item, 'display_results') as mocked_display_results:
+            self.media_item.on_text_search('Search Text')
+
+            # THEN: The search results should be the same as those returned by plugin.manager.verse_search
+            self.assertEqual(self.media_item.search_results, ['results', 'list'])
+            mocked_display_results.assert_called_once_with()
+
+    def test_on_text_search_no_results(self):
+        """
+        Test on_text_search when the search of the first bible does not return any results
+        """
+        # GIVEN: An instance of :class:`MediaManagerItem`
+        self.media_item.bible = self.mocked_bible_1
+        self.media_item.second_bible = self.mocked_bible_2
+
+        # WHEN: Calling on_text_search and plugin.manager.verse_search returns an empty list
+        self.mocked_plugin.manager.verse_search.return_value = []
+        with patch.object(self.media_item, 'display_results') as mocked_display_results:
+            self.media_item.on_text_search('Search Text')
+
+            # THEN: The search results should be an empty list
+            self.assertEqual(self.media_item.search_results, [])
+            mocked_display_results.assert_called_once_with()
+
+    def test_on_text_search_all_results_in_both_books(self):
+        """
+        Test on_text_search when all of the results from the first bible are found in the second
+        """
+        # GIVEN: An instance of :class:`MediaManagerItem` and some test data
+        mocked_verse_1 = MagicMock(**{'book.book_reference_id': 1, 'chapter': 2, 'verse': 3})
+        mocked_verse_1a = MagicMock(**{'book.book_reference_id': 1, 'chapter': 2, 'verse': 3})
+        mocked_verse_2 = MagicMock(**{'book.book_reference_id': 4, 'chapter': 5, 'verse': 6})
+        mocked_verse_2a = MagicMock(**{'book.book_reference_id': 4, 'chapter': 5, 'verse': 6})
+        self.media_item.bible = self.mocked_bible_1
+        self.media_item.second_bible = self.mocked_bible_2
+        self.media_item.second_search_results = []
+
+        # WHEN: Calling on_text_search and plugin.manager.verse_search returns a list of search results
+        self.mocked_plugin.manager.verse_search.return_value = [mocked_verse_1, mocked_verse_2]
+        self.media_item.second_bible.get_verses.side_effect = [[mocked_verse_1a], [mocked_verse_2a]]
+        with patch.object(self.media_item, 'display_results') as mocked_display_results:
+            self.media_item.on_text_search('Search Text')
+
+            # THEN: The search results for both bibles should be returned
+            self.assertEqual(self.media_item.search_results, [mocked_verse_1, mocked_verse_2])
+            self.assertEqual(self.media_item.second_search_results, [mocked_verse_1a, mocked_verse_2a])
+            self.assertFalse(self.mocked_log.debug.called)
+            self.assertFalse(self.mocked_main_window.information_message.called)
+            mocked_display_results.assert_called_once_with()
+
+    def test_on_text_search_not_all_results_in_both_books(self):
+        """
+        Test on_text_search when not all of the results from the first bible are found in the second
+        """
+        # GIVEN: An instance of :class:`MediaManagerItem` and some test data
+        mocked_verse_1 = MagicMock(**{'book.book_reference_id': 1, 'chapter': 2, 'verse': 3})
+        mocked_verse_1a = MagicMock(**{'book.book_reference_id': 1, 'chapter': 2, 'verse': 3})
+        mocked_verse_2 = MagicMock(**{'book.book_reference_id': 4, 'chapter': 5, 'verse': 6})
+        mocked_verse_3 = MagicMock(**{'book.book_reference_id': 7, 'chapter': 8, 'verse': 9})
+        self.media_item.bible = self.mocked_bible_1
+        self.media_item.second_bible = self.mocked_bible_2
+        self.media_item.second_search_results = []
+
+        # WHEN: Calling on_text_search and not all results are found in the second bible
+        self.mocked_plugin.manager.verse_search.return_value = [mocked_verse_1, mocked_verse_2, mocked_verse_3]
+        self.media_item.second_bible.get_verses.side_effect = [[mocked_verse_1a], [], []]
+        with patch.object(self.media_item, 'display_results') as mocked_display_results:
+            self.media_item.on_text_search('Search Text')
+
+            # THEN: The search results included in  both bibles should be returned and the user should be notified of
+            #       the missing verses
+            self.assertEqual(self.media_item.search_results, [mocked_verse_1])
+            self.assertEqual(self.media_item.second_search_results, [mocked_verse_1a])
+            self.assertEqual(self.mocked_log.debug.call_count, 2)
+            self.assertTrue(self.mocked_main_window.information_message.called)
+            mocked_display_results.assert_called_once_with()
+
+    # TODO: Test text_search
+
+    def test_on_search_edit_text_changed_search_while_typing_disabled(self):
+        """
+        Test on_search_edit_text_changed when 'search while typing' is disabled
+        """
+        # GIVEN: An instance of BibleMediaItem and mocked Settings which returns False when the value
+        #       'bibles/is search while typing enabled' is requested
+        self.setting_values = {'bibles/is search while typing enabled': False}
+        self.mocked_qtimer.isActive.return_value = False
+
+        # WHEN: Calling on_search_edit_text_changed
+        self.media_item.on_search_edit_text_changed()
+
+        # THEN: The method should not have checked if the timer is active
+        self.assertFalse(self.media_item.search_timer.isActive.called)
+
+    def test_on_search_edit_text_changed_search_while_typing_enabled(self):
+        """
+        Test on_search_edit_text_changed when 'search while typing' is enabled
+        """
+        # GIVEN: An instance of BibleMediaItem and mocked Settings which returns True when the value
+        #       'bibles/is search while typing enabled' is requested
+        self.setting_values = {'bibles/is search while typing enabled': True}
+        self.media_item.search_timer.isActive.return_value = False
+
+        # WHEN: Calling on_search_edit_text_changed
+        self.media_item.on_search_edit_text_changed()
+
+        # THEN: The method should start the search_timer
+        self.media_item.search_timer.isActive.assert_called_once_with()
+        self.media_item.search_timer.start.assert_called_once_with()
+
+    def test_on_search_timer_timeout(self):
+        """
+        Test on_search_timer_timeout
+        """
+        # GIVEN: An instance of BibleMediaItem
+        with patch.object(self.media_item, 'text_search') as mocked_text_search:
+
+            # WHEN: Calling on_search_timer_timeout
+            self.media_item.on_search_timer_timeout()
+
+            # THEN: The text_search method should have been called with True
+            mocked_text_search.assert_called_once_with(True)
+
+    def test_display_results_no_results(self):
+        """
+        Test the display_results method when there are no items to display
+        """
+        # GIVEN: An instance of BibleMediaItem and a mocked build_display_results which returns an empty list
+        self.media_item.list_view = MagicMock()
+        self.media_item.bible = self.mocked_bible_1
+        self.media_item.second_bible = self.mocked_bible_2
+        self.media_item.search_results = []
+
+        with patch.object(self.media_item, 'build_display_results', return_value=[]):
+
+            # WHEN: Calling display_results with True
+            self.media_item.display_results()
+
+            # THEN: No items should be added to the list
+            self.media_item.list_view.clear.assert_called_once_with()
+            self.assertFalse(self.media_item.list_view.addItem.called)
+
+    def test_display_results_results(self):
+        """
+        Test the display_results method when there are items to display
+        """
+        # GIVEN: An instance of BibleMediaItem and a mocked build_display_results which returns a list of results
+        with patch.object(self.media_item, 'build_display_results', return_value=['list', 'items']):
+            self.media_item.search_results = ['results']
+            self.media_item.list_view = MagicMock()
+
+            # WHEN: Calling display_results
+            self.media_item.display_results()
+
+            # THEN: addItem should have been with the display items
+            self.media_item.list_view.clear.assert_called_once_with()
+            self.assertEqual(self.media_item.list_view.addItem.call_args_list, [call('list'), call('items')])

=== modified file 'tests/resources/themes/Moss_on_tree.otz' (properties changed: +x to -x)

Follow ups