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

Proposed by Phill
Status: Merged
Merged at revision: 2722
Proposed branch: lp:~phill-ridout/openlp/bible_media_item_refactors
Merge into: lp:openlp
Diff against target: 3823 lines (+2179/-1131)
12 files modified
openlp/core/common/settings.py (+3/-1)
openlp/core/lib/mediamanageritem.py (+0/-5)
openlp/core/ui/lib/listwidgetwithdnd.py (+8/-1)
openlp/plugins/bibles/bibleplugin.py (+1/-2)
openlp/plugins/bibles/lib/__init__.py (+2/-2)
openlp/plugins/bibles/lib/db.py (+11/-6)
openlp/plugins/bibles/lib/manager.py (+28/-27)
openlp/plugins/bibles/lib/mediaitem.py (+593/-924)
resources/images/openlp-2.qrc (+1/-0)
tests/functional/openlp_core_ui_lib/test_listwidgetwithdnd.py (+31/-0)
tests/functional/openlp_plugins/bibles/test_lib.py (+142/-5)
tests/functional/openlp_plugins/bibles/test_mediaitem.py (+1359/-158)
To merge this branch: bzr merge lp:~phill-ridout/openlp/bible_media_item_refactors
Reviewer Review Type Date Requested Status
Raoul Snyman Approve
Tim Bentley Approve
Azaziah Pending
Review via email: mp+317705@code.launchpad.net

This proposal supersedes a proposal from 2017-02-18.

Description of the change

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!

To post a comment you must log in.
Revision history for this message
Tim Bentley (trb143) wrote : Posted in a previous version of this proposal

Tried this and found two issues
Search for bible verses and add to preview and blank first slide appears.

The second bible does not work. Trunk is broken but this is worse.
Trunk does not add the 2nd text but your changes block the search when the 2nd bible is added.

review: Needs Fixing
Revision history for this message
Azaziah (suutari-olli) wrote : Posted in a previous version of this proposal

Issues:

- Text search does not work if "Search while typing" is disabled
- If you do a reference search and enter faulty book name and number,
  the following traceback will appear:

Traceback (most recent call last):
  File "D:\bzr\openlp\bible_media_item_refactors\openlp\plugins\bibles\lib\mediaitem.py", line 612, in on_search_button_clicked
    self.text_search()
  File "D:\bzr\openlp\bible_media_item_refactors\openlp\plugins\bibles\lib\mediaitem.py", line 700, in text_search
    self.text_reference_search(text)
  File "D:\bzr\openlp\bible_media_item_refactors\openlp\plugins\bibles\lib\mediaitem.py", line 643, in text_reference_search
    self.display_results()
  File "D:\bzr\openlp\bible_media_item_refactors\openlp\plugins\bibles\lib\mediaitem.py", line 737, in display_results
    items = self.build_display_results(self.bible, self.second_bible, self.search_results)
  File "D:\bzr\openlp\bible_media_item_refactors\openlp\plugins\bibles\lib\mediaitem.py", line 763, in build_display_results
    for count, verse in enumerate(search_results):
TypeError: 'NoneType' object is not iterable

review: Needs Fixing
Revision history for this message
Phill (phill-ridout) wrote : Posted in a previous version of this proposal

Fixed the above issues!

Revision history for this message
Phill (phill-ridout) wrote : Posted in a previous version of this proposal
Revision history for this message
Tim Bentley (trb143) wrote : Posted in a previous version of this proposal

On the select tab there is a stray button next to book, no icon and no obvious use.
Import bibles the select language could not find the language. Should it not default to the UI language but still ask?

Seeing this on the console but not in trunk.

QLayout: Attempting to add QLayout "" to QWidget "", which already has a layout
QLayout: Attempting to add QLayout "" to QWidget "", which already has a layout
QLayout: Attempting to add QLayout "" to QWidget "", which already has a layout
QLayout: Attempting to add QLayout "" to QWidget "", which already has a layout
QLayout: Attempting to add QLayout "" to QGroupBox "", which already has a layout

Looking alike a good clean up though.

review: Needs Fixing
Revision history for this message
Phill (phill-ridout) wrote : Posted in a previous version of this proposal

> On the select tab there is a stray button next to book, no icon and no obvious
> use.

That button is to sort books alphabetically. The resources need to be regened for the icon to show. I havn't included the compiled resources as to keep the diff clean! Will submit when this gets merged!

> Import bibles the select language could not find the language. Should it not
> default to the UI language but still ask?
>
> Seeing this on the console but not in trunk.
>
> QLayout: Attempting to add QLayout "" to QWidget "", which already has a
> layout
> QLayout: Attempting to add QLayout "" to QWidget "", which already has a
> layout
> QLayout: Attempting to add QLayout "" to QWidget "", which already has a
> layout
> QLayout: Attempting to add QLayout "" to QWidget "", which already has a
> layout
> QLayout: Attempting to add QLayout "" to QGroupBox "", which already has a
> layout

I'll take a look at these other issues.

Revision history for this message
Phill (phill-ridout) wrote : Posted in a previous version of this proposal

> Import bibles the select language could not find the language. Should it not
> default to the UI language but still ask?

This is no different to the functionality in trunk. I've just concentrated on the bible mediaitem. I had planned to take a look at other parts of the bible plugin after this, but my time is limited atm. Can you submit this as a wish list bug?

Revision history for this message
Tim Bentley (trb143) wrote :

All issues fixed.
Cannot find any issues with this.

review: Approve
Revision history for this message
Raoul Snyman (raoul-snyman) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'openlp/core/common/settings.py'
--- openlp/core/common/settings.py 2016-12-31 11:01:36 +0000
+++ openlp/core/common/settings.py 2017-02-18 20:35:53 +0000
@@ -217,7 +217,9 @@
217 ('advanced/default image', 'core/logo file', []), # Default image renamed + moved to general after 2.4.217 ('advanced/default image', 'core/logo file', []), # Default image renamed + moved to general after 2.4.
218 ('shortcuts/escapeItem', 'shortcuts/desktopScreenEnable', []), # Escape item was removed in 2.6.218 ('shortcuts/escapeItem', 'shortcuts/desktopScreenEnable', []), # Escape item was removed in 2.6.
219 ('shortcuts/offlineHelpItem', 'shortcuts/userManualItem', []), # Online and Offline help were combined in 2.6.219 ('shortcuts/offlineHelpItem', 'shortcuts/userManualItem', []), # Online and Offline help were combined in 2.6.
220 ('shortcuts/onlineHelpItem', 'shortcuts/userManualItem', []) # Online and Offline help were combined in 2.6.220 ('shortcuts/onlineHelpItem', 'shortcuts/userManualItem', []), # Online and Offline help were combined in 2.6.
221 ('bibles/advanced bible', '', []), # Common bible search widgets combined in 2.6
222 ('bibles/quick bible', 'bibles/primary bible', []) # Common bible search widgets combined in 2.6
221 ]223 ]
222224
223 @staticmethod225 @staticmethod
224226
=== modified file 'openlp/core/lib/mediamanageritem.py'
--- openlp/core/lib/mediamanageritem.py 2016-12-31 11:01:36 +0000
+++ openlp/core/lib/mediamanageritem.py 2017-02-18 20:35:53 +0000
@@ -197,14 +197,9 @@
197 """197 """
198 # Add the List widget198 # Add the List widget
199 self.list_view = ListWidgetWithDnD(self, self.plugin.name)199 self.list_view = ListWidgetWithDnD(self, self.plugin.name)
200 self.list_view.setSpacing(1)
201 self.list_view.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
202 self.list_view.setAlternatingRowColors(True)
203 self.list_view.setObjectName('{name}ListView'.format(name=self.plugin.name))200 self.list_view.setObjectName('{name}ListView'.format(name=self.plugin.name))
204 # Add to page_layout201 # Add to page_layout
205 self.page_layout.addWidget(self.list_view)202 self.page_layout.addWidget(self.list_view)
206 # define and add the context menu
207 self.list_view.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
208 if self.has_edit_icon:203 if self.has_edit_icon:
209 create_widget_action(self.list_view,204 create_widget_action(self.list_view,
210 text=self.plugin.get_string(StringContent.Edit)['title'],205 text=self.plugin.get_string(StringContent.Edit)['title'],
211206
=== modified file 'openlp/core/ui/lib/listwidgetwithdnd.py'
--- openlp/core/ui/lib/listwidgetwithdnd.py 2016-12-31 11:01:36 +0000
+++ openlp/core/ui/lib/listwidgetwithdnd.py 2017-02-18 20:35:53 +0000
@@ -40,6 +40,11 @@
40 super().__init__(parent)40 super().__init__(parent)
41 self.mime_data_text = name41 self.mime_data_text = name
42 self.no_results_text = UiStrings().NoResults42 self.no_results_text = UiStrings().NoResults
43 self.setSpacing(1)
44 self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
45 self.setAlternatingRowColors(True)
46 self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
47 self.locked = False
4348
44 def activateDnD(self):49 def activateDnD(self):
45 """50 """
@@ -49,13 +54,15 @@
49 self.setDragDropMode(QtWidgets.QAbstractItemView.DragDrop)54 self.setDragDropMode(QtWidgets.QAbstractItemView.DragDrop)
50 Registry().register_function(('%s_dnd' % self.mime_data_text), self.parent().load_file)55 Registry().register_function(('%s_dnd' % self.mime_data_text), self.parent().load_file)
5156
52 def clear(self, search_while_typing=False):57 def clear(self, search_while_typing=False, override_lock=False):
53 """58 """
54 Re-implement clear, so that we can customise feedback when using 'Search as you type'59 Re-implement clear, so that we can customise feedback when using 'Search as you type'
5560
56 :param search_while_typing: True if we want to display the customised message61 :param search_while_typing: True if we want to display the customised message
57 :return: None62 :return: None
58 """63 """
64 if self.locked and not override_lock:
65 return
59 if search_while_typing:66 if search_while_typing:
60 self.no_results_text = UiStrings().ShortResults67 self.no_results_text = UiStrings().ShortResults
61 else:68 else:
6269
=== modified file 'openlp/plugins/bibles/bibleplugin.py'
--- openlp/plugins/bibles/bibleplugin.py 2016-12-31 11:01:36 +0000
+++ openlp/plugins/bibles/bibleplugin.py 2017-02-18 20:35:53 +0000
@@ -46,8 +46,7 @@
46 'bibles/is verse number visible': True,46 'bibles/is verse number visible': True,
47 'bibles/display new chapter': False,47 'bibles/display new chapter': False,
48 'bibles/second bibles': True,48 'bibles/second bibles': True,
49 'bibles/advanced bible': '',49 'bibles/primary bible': '',
50 'bibles/quick bible': '',
51 'bibles/proxy name': '',50 'bibles/proxy name': '',
52 'bibles/proxy address': '',51 'bibles/proxy address': '',
53 'bibles/proxy username': '',52 'bibles/proxy username': '',
5453
=== modified file 'openlp/plugins/bibles/lib/__init__.py'
--- openlp/plugins/bibles/lib/__init__.py 2016-12-31 11:01:36 +0000
+++ openlp/plugins/bibles/lib/__init__.py 2017-02-18 20:35:53 +0000
@@ -230,7 +230,7 @@
230 REFERENCE_MATCHES['range_separator'] = re.compile(REFERENCE_SEPARATORS['sep_l'], re.UNICODE)230 REFERENCE_MATCHES['range_separator'] = re.compile(REFERENCE_SEPARATORS['sep_l'], re.UNICODE)
231 # full reference match: <book>(<range>(,(?!$)|(?=$)))+231 # full reference match: <book>(<range>(,(?!$)|(?=$)))+
232 REFERENCE_MATCHES['full'] = \232 REFERENCE_MATCHES['full'] = \
233 re.compile('^\s*(?!\s)(?P<book>[\d]*[^\d]+)(?<!\s)\s*'233 re.compile('^\s*(?!\s)(?P<book>[\d]*[^\d\.]+)\.*(?<!\s)\s*'
234 '(?P<ranges>(?:%(range_regex)s(?:%(sep_l)s(?!\s*$)|(?=\s*$)))+)\s*$'234 '(?P<ranges>(?:%(range_regex)s(?:%(sep_l)s(?!\s*$)|(?=\s*$)))+)\s*$'
235 % dict(list(REFERENCE_SEPARATORS.items()) + [('range_regex', range_regex)]), re.UNICODE)235 % dict(list(REFERENCE_SEPARATORS.items()) + [('range_regex', range_regex)]), re.UNICODE)
236236
@@ -326,7 +326,7 @@
326326
327 ``^\s*(?!\s)(?P<book>[\d]*[^\d]+)(?<!\s)\s*``327 ``^\s*(?!\s)(?P<book>[\d]*[^\d]+)(?<!\s)\s*``
328 The ``book`` group starts with the first non-whitespace character. There are optional leading digits followed by328 The ``book`` group starts with the first non-whitespace character. There are optional leading digits followed by
329 non-digits. The group ends before the whitspace in front of the next digit.329 non-digits. The group ends before the whitspace, or a full stop in front of the next digit.
330330
331 ``(?P<ranges>(?:%(range_regex)s(?:%(sep_l)s(?!\s*$)|(?=\s*$)))+)\s*$``331 ``(?P<ranges>(?:%(range_regex)s(?:%(sep_l)s(?!\s*$)|(?=\s*$)))+)\s*$``
332 The second group contains all ``ranges``. This can be multiple declarations of range_regex separated by a list332 The second group contains all ``ranges``. This can be multiple declarations of range_regex separated by a list
333333
=== modified file 'openlp/plugins/bibles/lib/db.py'
--- openlp/plugins/bibles/lib/db.py 2016-12-31 11:01:36 +0000
+++ openlp/plugins/bibles/lib/db.py 2017-02-18 20:35:53 +0000
@@ -36,7 +36,7 @@
36from openlp.core.common import AppLocation, translate, clean_filename36from openlp.core.common import AppLocation, translate, clean_filename
37from openlp.core.lib.db import BaseModel, init_db, Manager37from openlp.core.lib.db import BaseModel, init_db, Manager
38from openlp.core.lib.ui import critical_error_message_box38from openlp.core.lib.ui import critical_error_message_box
39from openlp.plugins.bibles.lib import upgrade39from openlp.plugins.bibles.lib import BibleStrings, LanguageSelection, upgrade
4040
41log = logging.getLogger(__name__)41log = logging.getLogger(__name__)
4242
@@ -52,9 +52,15 @@
5252
53class Book(BaseModel):53class Book(BaseModel):
54 """54 """
55 Song model55 Bible Book model
56 """56 """
57 pass57 def get_name(self, language_selection=LanguageSelection.Bible):
58 if language_selection == LanguageSelection.Bible:
59 return self.name
60 elif language_selection == LanguageSelection.Application:
61 return BibleStrings().BookNames[BiblesResourcesDB.get_book_by_id(self.book_reference_id)['abbreviation']]
62 elif language_selection == LanguageSelection.English:
63 return BiblesResourcesDB.get_book_by_id(self.book_reference_id)['name']
5864
5965
60class Verse(BaseModel):66class Verse(BaseModel):
@@ -380,13 +386,12 @@
380 """386 """
381 log.debug('BibleDB.verse_search("{text}")'.format(text=text))387 log.debug('BibleDB.verse_search("{text}")'.format(text=text))
382 verses = self.session.query(Verse)388 verses = self.session.query(Verse)
383 # TODO: Find out what this is doing before converting to format()
384 if text.find(',') > -1:389 if text.find(',') > -1:
385 keywords = ['%%%s%%' % keyword.strip() for keyword in text.split(',')]390 keywords = ['%{keyword}%'.format(keyword=keyword.strip()) for keyword in text.split(',') if keyword.strip()]
386 or_clause = [Verse.text.like(keyword) for keyword in keywords]391 or_clause = [Verse.text.like(keyword) for keyword in keywords]
387 verses = verses.filter(or_(*or_clause))392 verses = verses.filter(or_(*or_clause))
388 else:393 else:
389 keywords = ['%%%s%%' % keyword.strip() for keyword in text.split(' ')]394 keywords = ['%{keyword}%'.format(keyword=keyword.strip()) for keyword in text.split(' ') if keyword.strip()]
390 for keyword in keywords:395 for keyword in keywords:
391 verses = verses.filter(Verse.text.like(keyword))396 verses = verses.filter(Verse.text.like(keyword))
392 verses = verses.all()397 verses = verses.all()
393398
=== modified file 'openlp/plugins/bibles/lib/manager.py'
--- openlp/plugins/bibles/lib/manager.py 2017-01-08 19:12:12 +0000
+++ openlp/plugins/bibles/lib/manager.py 2017-02-18 20:35:53 +0000
@@ -250,14 +250,20 @@
250 '"{book}", "{chapter}")'.format(bible=bible, book=book_ref_id, chapter=chapter))250 '"{book}", "{chapter}")'.format(bible=bible, book=book_ref_id, chapter=chapter))
251 return self.db_cache[bible].get_verse_count(book_ref_id, chapter)251 return self.db_cache[bible].get_verse_count(book_ref_id, chapter)
252252
253 def get_verses(self, bible, verse_text, book_ref_id=False, show_error=True):253 def parse_ref(self, bible, reference_text, book_ref_id=False):
254 if not bible:
255 return
256 language_selection = self.get_language_selection(bible)
257 return parse_reference(reference_text, self.db_cache[bible], language_selection, book_ref_id)
258
259 def get_verses(self, bible, ref_list, show_error=True):
254 """260 """
255 Parses a scripture reference, fetches the verses from the Bible261 Parses a scripture reference, fetches the verses from the Bible
256 specified, and returns a list of ``Verse`` objects.262 specified, and returns a list of ``Verse`` objects.
257263
258 :param bible: Unicode. The Bible to use.264 :param bible: Unicode. The Bible to use.
259 :param verse_text:265 :param verse_text:
260 Unicode. The scripture reference. Valid scripture references are:266 String. The scripture reference. Valid scripture references are:
261267
262 - Genesis 1268 - Genesis 1
263 - Genesis 1-2269 - Genesis 1-2
@@ -271,22 +277,9 @@
271 For second bible this is necessary.277 For second bible this is necessary.
272 :param show_error:278 :param show_error:
273 """279 """
274 # If no bibles are installed, message is given.280 if not bible or not ref_list:
275 log.debug('BibleManager.get_verses("{bible}", "{verse}")'.format(bible=bible, verse=verse_text))281 return None
276 if not bible:282 return self.db_cache[bible].get_verses(ref_list, show_error)
277 if show_error:
278 self.main_window.information_message(
279 UiStrings().BibleNoBiblesTitle,
280 UiStrings().BibleNoBibles)
281 return None
282 # Get the language for books.
283 language_selection = self.get_language_selection(bible)
284 ref_list = parse_reference(verse_text, self.db_cache[bible], language_selection, book_ref_id)
285 if ref_list:
286 return self.db_cache[bible].get_verses(ref_list, show_error)
287 # If nothing is found. Message is given if this is not combined search. (defined in mediaitem.py)
288 else:
289 return None
290283
291 def get_language_selection(self, bible):284 def get_language_selection(self, bible):
292 """285 """
@@ -308,7 +301,7 @@
308 language_selection = LanguageSelection.Application301 language_selection = LanguageSelection.Application
309 return language_selection302 return language_selection
310303
311 def verse_search(self, bible, second_bible, text):304 def verse_search(self, bible, text):
312 """305 """
313 Does a verse search for the given bible and text.306 Does a verse search for the given bible and text.
314307
@@ -325,20 +318,14 @@
325 return None318 return None
326 # Check if the bible or second_bible is a web bible.319 # Check if the bible or second_bible is a web bible.
327 web_bible = self.db_cache[bible].get_object(BibleMeta, 'download_source')320 web_bible = self.db_cache[bible].get_object(BibleMeta, 'download_source')
328 second_web_bible = ''321 if web_bible:
329 if second_bible:
330 second_web_bible = self.db_cache[second_bible].get_object(BibleMeta, 'download_source')
331 if web_bible or second_web_bible:
332 # If either Bible is Web, cursor is reset to normal and message is given.322 # If either Bible is Web, cursor is reset to normal and message is given.
333 self.application.set_normal_cursor()323 self.application.set_normal_cursor()
334 self.main_window.information_message(324 self.main_window.information_message(
335 translate('BiblesPlugin.BibleManager', 'Web Bible cannot be used in Text Search'),325 translate('BiblesPlugin.BibleManager', 'Web Bible cannot be used in Text Search'),
336 translate('BiblesPlugin.BibleManager', 'Text Search is not available with Web Bibles.\n'326 translate('BiblesPlugin.BibleManager', 'Text Search is not available with Web Bibles.\n'
337 'Please use the Scripture Reference Search instead.\n\n'327 'Please use the Scripture Reference Search instead.\n\n'
338 'This means that the currently used Bible\nor Second Bible '328 'This means that the currently selected Bible is a Web Bible.')
339 'is installed as Web Bible.\n\n'
340 'If you were trying to perform a Reference search\nin Combined '
341 'Search, your reference is invalid.')
342 )329 )
343 return None330 return None
344 # Shorter than 3 char searches break OpenLP with very long search times, thus they are blocked.331 # Shorter than 3 char searches break OpenLP with very long search times, thus they are blocked.
@@ -380,6 +367,20 @@
380 else:367 else:
381 return None368 return None
382369
370 def process_verse_range(self, book_ref_id, chapter_from, verse_from, chapter_to, verse_to):
371 verse_ranges = []
372 for chapter in range(chapter_from, chapter_to + 1):
373 if chapter == chapter_from:
374 start_verse = verse_from
375 else:
376 start_verse = 1
377 if chapter == chapter_to:
378 end_verse = verse_to
379 else:
380 end_verse = -1
381 verse_ranges.append((book_ref_id, chapter, start_verse, end_verse))
382 return verse_ranges
383
383 def save_meta_data(self, bible, version, copyright, permissions, full_license, book_name_language=None):384 def save_meta_data(self, bible, version, copyright, permissions, full_license, book_name_language=None):
384 """385 """
385 Saves the bibles meta data.386 Saves the bibles meta data.
386387
=== modified file 'openlp/plugins/bibles/lib/mediaitem.py'
--- openlp/plugins/bibles/lib/mediaitem.py 2017-01-08 19:12:12 +0000
+++ openlp/plugins/bibles/lib/mediaitem.py 2017-02-18 20:35:53 +0000
@@ -21,28 +21,36 @@
21###############################################################################21###############################################################################
2222
23import logging23import logging
24import re
2425
25from PyQt5 import QtCore, QtWidgets26from PyQt5 import QtCore, QtWidgets
2627
27from openlp.core.common import Registry, Settings, UiStrings, translate28from openlp.core.common import Registry, Settings, UiStrings, translate
28from openlp.core.lib import MediaManagerItem, ItemCapabilities, ServiceItemContext, create_separated_list29from openlp.core.lib import MediaManagerItem, ItemCapabilities, ServiceItemContext
29from openlp.core.lib.searchedit import SearchEdit30from openlp.core.lib.searchedit import SearchEdit
30from openlp.core.lib.ui import set_case_insensitive_completer, create_horizontal_adjusting_combo_box, \31from openlp.core.lib.ui import set_case_insensitive_completer, create_horizontal_adjusting_combo_box, \
31 critical_error_message_box, find_and_set_in_combo_box, build_icon32 critical_error_message_box, find_and_set_in_combo_box, build_icon
32from openlp.core.common.languagemanager import get_locale_key33from openlp.core.common.languagemanager import get_locale_key
33from openlp.plugins.bibles.forms.bibleimportform import BibleImportForm34from openlp.plugins.bibles.forms.bibleimportform import BibleImportForm
34from openlp.plugins.bibles.forms.editbibleform import EditBibleForm35from openlp.plugins.bibles.forms.editbibleform import EditBibleForm
35from openlp.plugins.bibles.lib import LayoutStyle, DisplayStyle, VerseReferenceList, get_reference_separator, \36from openlp.plugins.bibles.lib import DisplayStyle, LayoutStyle, VerseReferenceList, \
36 LanguageSelection, BibleStrings37 get_reference_match, get_reference_separator
37from openlp.plugins.bibles.lib.db import BiblesResourcesDB
38import re
3938
40log = logging.getLogger(__name__)39log = logging.getLogger(__name__)
4140
4241
42VALID_TEXT_SEARCH = re.compile('\w\w\w')
43
44
45def get_reference_separators():
46 return {'verse': get_reference_separator('sep_v_display'),
47 'range': get_reference_separator('sep_r_display'),
48 'list': get_reference_separator('sep_l_display')}
49
50
43class BibleSearch(object):51class BibleSearch(object):
44 """52 """
45 Enumeration class for the different search methods for the "quick search".53 Enumeration class for the different search methods for the "Search" tab.
46 """54 """
47 Reference = 155 Reference = 1
48 Text = 256 Text = 2
@@ -57,15 +65,31 @@
57 bibles_add_to_service = QtCore.pyqtSignal(list)65 bibles_add_to_service = QtCore.pyqtSignal(list)
58 log.info('Bible Media Item loaded')66 log.info('Bible Media Item loaded')
5967
60 def __init__(self, parent, plugin):68 def __init__(self, *args, **kwargs):
69 """
70 Constructor
71
72 :param args: Positional arguments to pass to the super method. (tuple)
73 :param kwargs: Keyword arguments to pass to the super method. (dict)
74 """
61 self.clear_icon = build_icon(':/bibles/bibles_search_clear.png')75 self.clear_icon = build_icon(':/bibles/bibles_search_clear.png')
62 self.lock_icon = build_icon(':/bibles/bibles_search_lock.png')76 self.lock_icon = build_icon(':/bibles/bibles_search_lock.png')
63 self.unlock_icon = build_icon(':/bibles/bibles_search_unlock.png')77 self.unlock_icon = build_icon(':/bibles/bibles_search_unlock.png')
64 MediaManagerItem.__init__(self, parent, plugin)78 self.sort_icon = build_icon(':/bibles/bibles_book_sort.png')
79 self.bible = None
80 self.second_bible = None
81 # TODO: Make more central and clean up after!
82 self.search_timer = QtCore.QTimer()
83 self.search_timer.setInterval(200)
84 self.search_timer.setSingleShot(True)
85 self.search_timer.timeout.connect(self.on_search_timer_timeout)
86 super().__init__(*args, **kwargs)
6587
66 def setup_item(self):88 def setup_item(self):
67 """89 """
68 Do some additional setup.90 Do some additional setup.
91
92 :return: None
69 """93 """
70 self.bibles_go_live.connect(self.go_live_remote)94 self.bibles_go_live.connect(self.go_live_remote)
71 self.bibles_add_to_service.connect(self.add_to_service_remote)95 self.bibles_add_to_service.connect(self.add_to_service_remote)
@@ -73,251 +97,174 @@
73 self.settings = self.plugin.settings_tab97 self.settings = self.plugin.settings_tab
74 self.quick_preview_allowed = True98 self.quick_preview_allowed = True
75 self.has_search = True99 self.has_search = True
76 self.search_results = {}100 self.search_results = []
77 self.second_search_results = {}101 self.second_search_results = []
78 Registry().register_function('bibles_load_list', self.reload_bibles)102 Registry().register_function('bibles_load_list', self.reload_bibles)
79103
80 def __check_second_bible(self, bible, second_bible):
81 """
82 Check if the first item is a second bible item or not.
83 """
84 if not self.list_view.count():
85 self.display_results(bible, second_bible)
86 return
87 item_second_bible = self._decode_qt_object(self.list_view.item(0), 'second_bible')
88 if item_second_bible and second_bible or not item_second_bible and not second_bible:
89 self.display_results(bible, second_bible)
90 elif critical_error_message_box(
91 message=translate('BiblesPlugin.MediaItem',
92 'You cannot combine single and dual Bible verse search results. '
93 'Do you want to delete your search results and start a new search?'),
94 parent=self, question=True) == QtWidgets.QMessageBox.Yes:
95 self.list_view.clear()
96 self.display_results(bible, second_bible)
97
98 def _decode_qt_object(self, bitem, key):
99 reference = bitem.data(QtCore.Qt.UserRole)
100 obj = reference[str(key)]
101 return str(obj).strip()
102
103 def required_icons(self):104 def required_icons(self):
104 """105 """
105 Set which icons the media manager tab should show106 Set which icons the media manager tab should show
107
108 :return: None
106 """109 """
107 MediaManagerItem.required_icons(self)110 super().required_icons()
108 self.has_import_icon = True111 self.has_import_icon = True
109 self.has_new_icon = False112 self.has_new_icon = False
110 self.has_edit_icon = True113 self.has_edit_icon = True
111 self.has_delete_icon = True114 self.has_delete_icon = True
112 self.add_to_service_item = False115 self.add_to_service_item = False
113116
114 def add_search_tab(self, prefix, name):
115 self.search_tab_bar.addTab(name)
116 tab = QtWidgets.QWidget()
117 tab.setObjectName(prefix + 'Tab')
118 tab.setSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum)
119 layout = QtWidgets.QGridLayout(tab)
120 layout.setObjectName(prefix + 'Layout')
121 setattr(self, prefix + 'Tab', tab)
122 setattr(self, prefix + 'Layout', layout)
123
124 def add_search_fields(self, prefix, name):
125 """
126 Creates and adds generic search tab.
127
128 :param prefix: The prefix of the tab, this is either ``quick`` or ``advanced``.
129 :param name: The translated string to display.
130 """
131 if prefix == 'quick':
132 idx = 2
133 else:
134 idx = 5
135 tab = getattr(self, prefix + 'Tab')
136 layout = getattr(self, prefix + 'Layout')
137 version_label = QtWidgets.QLabel(tab)
138 version_label.setObjectName(prefix + 'VersionLabel')
139 layout.addWidget(version_label, idx, 0, QtCore.Qt.AlignRight)
140 version_combo_box = create_horizontal_adjusting_combo_box(tab, prefix + 'VersionComboBox')
141 version_label.setBuddy(version_combo_box)
142 layout.addWidget(version_combo_box, idx, 1, 1, 2)
143 second_label = QtWidgets.QLabel(tab)
144 second_label.setObjectName(prefix + 'SecondLabel')
145 layout.addWidget(second_label, idx + 1, 0, QtCore.Qt.AlignRight)
146 second_combo_box = create_horizontal_adjusting_combo_box(tab, prefix + 'SecondComboBox')
147 version_label.setBuddy(second_combo_box)
148 layout.addWidget(second_combo_box, idx + 1, 1, 1, 2)
149 style_label = QtWidgets.QLabel(tab)
150 style_label.setObjectName(prefix + 'StyleLabel')
151 layout.addWidget(style_label, idx + 2, 0, QtCore.Qt.AlignRight)
152 style_combo_box = create_horizontal_adjusting_combo_box(tab, prefix + 'StyleComboBox')
153 style_combo_box.addItems(['', '', ''])
154 layout.addWidget(style_combo_box, idx + 2, 1, 1, 2)
155 search_button_layout = QtWidgets.QHBoxLayout()
156 search_button_layout.setObjectName(prefix + 'search_button_layout')
157 search_button_layout.addStretch()
158 # Note: If we use QPushButton instead of the QToolButton, the icon will be larger than the Lock icon.
159 clear_button = QtWidgets.QToolButton(tab)
160 clear_button.setIcon(self.clear_icon)
161 clear_button.setObjectName(prefix + 'ClearButton')
162 lock_button = QtWidgets.QToolButton(tab)
163 lock_button.setIcon(self.unlock_icon)
164 lock_button.setCheckable(True)
165 lock_button.setObjectName(prefix + 'LockButton')
166 search_button_layout.addWidget(clear_button)
167 search_button_layout.addWidget(lock_button)
168 search_button = QtWidgets.QPushButton(tab)
169 search_button.setObjectName(prefix + 'SearchButton')
170 search_button_layout.addWidget(search_button)
171 layout.addLayout(search_button_layout, idx + 3, 1, 1, 2)
172 self.page_layout.addWidget(tab)
173 tab.setVisible(False)
174 lock_button.toggled.connect(self.on_lock_button_toggled)
175 second_combo_box.currentIndexChanged.connect(self.on_second_bible_combobox_index_changed)
176 setattr(self, prefix + 'VersionLabel', version_label)
177 setattr(self, prefix + 'VersionComboBox', version_combo_box)
178 setattr(self, prefix + 'SecondLabel', second_label)
179 setattr(self, prefix + 'SecondComboBox', second_combo_box)
180 setattr(self, prefix + 'StyleLabel', style_label)
181 setattr(self, prefix + 'StyleComboBox', style_combo_box)
182 setattr(self, prefix + 'ClearButton', clear_button)
183 setattr(self, prefix + 'LockButton', lock_button)
184 setattr(self, prefix + 'SearchButtonLayout', search_button_layout)
185 setattr(self, prefix + 'SearchButton', search_button)
186
187 def add_end_header_bar(self):117 def add_end_header_bar(self):
188 self.search_tab_bar = QtWidgets.QTabBar(self)118 self.search_tab_bar = QtWidgets.QTabBar(self)
189 self.search_tab_bar.setExpanding(False)119 self.search_tab_bar.setExpanding(False)
190 self.search_tab_bar.setObjectName('search_tab_bar')
191 self.page_layout.addWidget(self.search_tab_bar)120 self.page_layout.addWidget(self.search_tab_bar)
192 # Add the Quick Search tab.121 # Add the Search tab.
193 self.add_search_tab('quick', translate('BiblesPlugin.MediaItem', 'Search'))122 self.search_tab = QtWidgets.QWidget()
194 self.quick_search_label = QtWidgets.QLabel(self.quickTab)123 self.search_tab.setSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum)
195 self.quick_search_label.setObjectName('quick_search_label')124 self.search_tab_bar.addTab(translate('BiblesPlugin.MediaItem', 'Find'))
196 self.quickLayout.addWidget(self.quick_search_label, 0, 0, QtCore.Qt.AlignRight)125 self.search_layout = QtWidgets.QFormLayout(self.search_tab)
197 self.quick_search_edit = SearchEdit(self.quickTab, self.settings_section)126 self.search_edit = SearchEdit(self.search_tab, self.settings_section)
198 self.quick_search_edit.setSizePolicy(QtWidgets.QSizePolicy.Ignored, QtWidgets.QSizePolicy.Fixed)127 self.search_layout.addRow(translate('BiblesPlugin.MediaItem', 'Find:'), self.search_edit)
199 self.quick_search_edit.setObjectName('quick_search_edit')128 self.search_tab.setVisible(True)
200 self.quick_search_label.setBuddy(self.quick_search_edit)129 self.page_layout.addWidget(self.search_tab)
201 self.quickLayout.addWidget(self.quick_search_edit, 0, 1, 1, 2)130 # Add the Select tab.
202 self.add_search_fields('quick', translate('BiblesPlugin.MediaItem', 'Search'))131 self.select_tab = QtWidgets.QWidget()
203 self.quickTab.setVisible(True)132 self.select_tab.setSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum)
204 # Add the Advanced Search tab.133 self.search_tab_bar.addTab(translate('BiblesPlugin.MediaItem', 'Select'))
205 self.add_search_tab('advanced', translate('BiblesPlugin.MediaItem', 'Select'))134 self.select_layout = QtWidgets.QFormLayout(self.select_tab)
206 self.advanced_book_label = QtWidgets.QLabel(self.advancedTab)135 self.book_layout = QtWidgets.QHBoxLayout()
207 self.advanced_book_label.setObjectName('advanced_book_label')136 self.select_book_combo_box = create_horizontal_adjusting_combo_box(self.select_tab, 'select_book_combo_box')
208 self.advancedLayout.addWidget(self.advanced_book_label, 0, 0, QtCore.Qt.AlignRight)137 self.book_layout.addWidget(self.select_book_combo_box)
209 self.advanced_book_combo_box = create_horizontal_adjusting_combo_box(self.advancedTab,138 self.book_order_button = QtWidgets.QToolButton()
210 'advanced_book_combo_box')139 self.book_order_button.setIcon(self.sort_icon)
211 self.advanced_book_label.setBuddy(self.advanced_book_combo_box)140 self.book_order_button.setCheckable(True)
212 self.advancedLayout.addWidget(self.advanced_book_combo_box, 0, 1, 1, 2)141 self.book_order_button.setToolTip(translate('BiblesPlugin.MediaItem', 'Sort bible books alphabetically.'))
213 self.advanced_chapter_label = QtWidgets.QLabel(self.advancedTab)142 self.book_layout.addWidget(self.book_order_button)
214 self.advanced_chapter_label.setObjectName('advanced_chapter_label')143 self.select_layout.addRow(translate('BiblesPlugin.MediaItem', 'Book:'), self.book_layout)
215 self.advancedLayout.addWidget(self.advanced_chapter_label, 1, 1, 1, 2)144 self.verse_title_layout = QtWidgets.QHBoxLayout()
216 self.advanced_verse_label = QtWidgets.QLabel(self.advancedTab)145 self.chapter_label = QtWidgets.QLabel(self.select_tab)
217 self.advanced_verse_label.setObjectName('advanced_verse_label')146 self.verse_title_layout.addWidget(self.chapter_label)
218 self.advancedLayout.addWidget(self.advanced_verse_label, 1, 2)147 self.verse_label = QtWidgets.QLabel(self.select_tab)
219 self.advanced_from_label = QtWidgets.QLabel(self.advancedTab)148 self.verse_title_layout.addWidget(self.verse_label)
220 self.advanced_from_label.setObjectName('advanced_from_label')149 self.select_layout.addRow('', self.verse_title_layout)
221 self.advancedLayout.addWidget(self.advanced_from_label, 3, 0, QtCore.Qt.AlignRight)150 self.from_layout = QtWidgets.QHBoxLayout()
222 self.advanced_from_chapter = QtWidgets.QComboBox(self.advancedTab)151 self.from_chapter = QtWidgets.QComboBox(self.select_tab)
223 self.advanced_from_chapter.setObjectName('advanced_from_chapter')152 self.from_layout.addWidget(self.from_chapter)
224 self.advancedLayout.addWidget(self.advanced_from_chapter, 3, 1)153 self.from_verse = QtWidgets.QComboBox(self.select_tab)
225 self.advanced_from_verse = QtWidgets.QComboBox(self.advancedTab)154 self.from_layout.addWidget(self.from_verse)
226 self.advanced_from_verse.setObjectName('advanced_from_verse')155 self.select_layout.addRow(translate('BiblesPlugin.MediaItem', 'From:'), self.from_layout)
227 self.advancedLayout.addWidget(self.advanced_from_verse, 3, 2)156 self.to_layout = QtWidgets.QHBoxLayout()
228 self.advanced_to_label = QtWidgets.QLabel(self.advancedTab)157 self.to_chapter = QtWidgets.QComboBox(self.select_tab)
229 self.advanced_to_label.setObjectName('advanced_to_label')158 self.to_layout.addWidget(self.to_chapter)
230 self.advancedLayout.addWidget(self.advanced_to_label, 4, 0, QtCore.Qt.AlignRight)159 self.to_verse = QtWidgets.QComboBox(self.select_tab)
231 self.advanced_to_chapter = QtWidgets.QComboBox(self.advancedTab)160 self.to_layout.addWidget(self.to_verse)
232 self.advanced_to_chapter.setObjectName('advanced_to_chapter')161 self.select_layout.addRow(translate('BiblesPlugin.MediaItem', 'To:'), self.to_layout)
233 self.advancedLayout.addWidget(self.advanced_to_chapter, 4, 1)162 self.select_tab.setVisible(False)
234 self.advanced_to_verse = QtWidgets.QComboBox(self.advancedTab)163 self.page_layout.addWidget(self.select_tab)
235 self.advanced_to_verse.setObjectName('advanced_to_verse')164 # General Search Opions
236 self.advancedLayout.addWidget(self.advanced_to_verse, 4, 2)165 self.options_widget = QtWidgets.QGroupBox(translate('BiblesPlugin.MediaItem', 'Options'), self)
237 self.add_search_fields('advanced', UiStrings().Advanced)166 self.general_bible_layout = QtWidgets.QFormLayout(self.options_widget)
167 self.version_combo_box = create_horizontal_adjusting_combo_box(self, 'version_combo_box')
168 self.general_bible_layout.addRow('{version}:'.format(version=UiStrings().Version), self.version_combo_box)
169 self.second_combo_box = create_horizontal_adjusting_combo_box(self, 'second_combo_box')
170 self.general_bible_layout.addRow(translate('BiblesPlugin.MediaItem', 'Second:'), self.second_combo_box)
171 self.style_combo_box = create_horizontal_adjusting_combo_box(self, 'style_combo_box')
172 self.style_combo_box.addItems(['', '', ''])
173 self.general_bible_layout.addRow(UiStrings().LayoutStyle, self.style_combo_box)
174 self.search_button_layout = QtWidgets.QHBoxLayout()
175 self.search_button_layout.addStretch()
176 # Note: If we use QPushButton instead of the QToolButton, the icon will be larger than the Lock icon.
177 self.clear_button = QtWidgets.QToolButton(self)
178 self.clear_button.setIcon(self.clear_icon)
179 self.lock_button = QtWidgets.QToolButton(self)
180 self.lock_button.setIcon(self.unlock_icon)
181 self.lock_button.setCheckable(True)
182 self.search_button_layout.addWidget(self.clear_button)
183 self.search_button_layout.addWidget(self.lock_button)
184 self.search_button = QtWidgets.QPushButton(self)
185 self.search_button_layout.addWidget(self.search_button)
186 self.general_bible_layout.addRow(self.search_button_layout)
187 self.page_layout.addWidget(self.options_widget)
188
189 def setupUi(self):
190 super().setupUi()
191 sort_model = QtCore.QSortFilterProxyModel(self.select_book_combo_box)
192 model = self.select_book_combo_box.model()
193 # Reparent the combo box model to the sort proxy, otherwise it will be deleted when we change the comobox's
194 # model
195 model.setParent(sort_model)
196 sort_model.setSourceModel(model)
197 self.select_book_combo_box.setModel(sort_model)
198
199 # Signals & Slots
238 # Combo Boxes200 # Combo Boxes
239 self.quickVersionComboBox.activated.connect(self.update_auto_completer)201 self.select_book_combo_box.activated.connect(self.on_advanced_book_combo_box)
240 self.quickSecondComboBox.activated.connect(self.update_auto_completer)202 self.from_chapter.activated.connect(self.on_from_chapter_activated)
241 self.advancedVersionComboBox.activated.connect(self.on_advanced_version_combo_box)203 self.from_verse.activated.connect(self.on_from_verse)
242 self.advancedSecondComboBox.activated.connect(self.on_advanced_second_combo_box)204 self.to_chapter.activated.connect(self.on_to_chapter)
243 self.advanced_book_combo_box.activated.connect(self.on_advanced_book_combo_box)205 self.version_combo_box.currentIndexChanged.connect(self.on_version_combo_box_index_changed)
244 self.advanced_from_chapter.activated.connect(self.on_advanced_from_chapter)206 self.version_combo_box.currentIndexChanged.connect(self.update_auto_completer)
245 self.advanced_from_verse.activated.connect(self.on_advanced_from_verse)207 self.second_combo_box.currentIndexChanged.connect(self.on_second_combo_box_index_changed)
246 self.advanced_to_chapter.activated.connect(self.on_advanced_to_chapter)208 self.second_combo_box.currentIndexChanged.connect(self.update_auto_completer)
247 self.quick_search_edit.searchTypeChanged.connect(self.update_auto_completer)209 self.style_combo_box.currentIndexChanged.connect(self.on_style_combo_box_index_changed)
248 self.quickVersionComboBox.activated.connect(self.update_auto_completer)210 self.search_edit.searchTypeChanged.connect(self.update_auto_completer)
249 self.quickStyleComboBox.activated.connect(self.on_quick_style_combo_box_changed)
250 self.advancedStyleComboBox.activated.connect(self.on_advanced_style_combo_box_changed)
251 # Buttons211 # Buttons
252 self.advancedClearButton.clicked.connect(self.on_advanced_clear_button_clicked)212 self.book_order_button.toggled.connect(self.on_book_order_button_toggled)
253 self.quickClearButton.clicked.connect(self.on_clear_button_clicked)213 self.clear_button.clicked.connect(self.on_clear_button_clicked)
254 self.advancedSearchButton.clicked.connect(self.on_advanced_search_button)214 self.lock_button.toggled.connect(self.on_lock_button_toggled)
255 self.quickSearchButton.clicked.connect(self.on_quick_search_button)215 self.search_button.clicked.connect(self.on_search_button_clicked)
256 # Other stuff216 # Other stuff
257 self.quick_search_edit.returnPressed.connect(self.on_quick_search_button)217 self.search_edit.returnPressed.connect(self.on_search_button_clicked)
258 self.search_tab_bar.currentChanged.connect(self.on_search_tab_bar_current_changed)218 self.search_tab_bar.currentChanged.connect(self.on_search_tab_bar_current_changed)
259 self.quick_search_edit.textChanged.connect(self.on_search_text_edit_changed)219 self.search_edit.textChanged.connect(self.on_search_edit_text_changed)
220
221 def retranslateUi(self):
222 log.debug('retranslateUi')
223 self.chapter_label.setText(translate('BiblesPlugin.MediaItem', 'Chapter:'))
224 self.verse_label.setText(translate('BiblesPlugin.MediaItem', 'Verse:'))
225 self.style_combo_box.setItemText(LayoutStyle.VersePerSlide, UiStrings().VersePerSlide)
226 self.style_combo_box.setItemText(LayoutStyle.VersePerLine, UiStrings().VersePerLine)
227 self.style_combo_box.setItemText(LayoutStyle.Continuous, UiStrings().Continuous)
228 self.clear_button.setToolTip(translate('BiblesPlugin.MediaItem', 'Clear the search results.'))
229 self.lock_button.setToolTip(
230 translate('BiblesPlugin.MediaItem', 'Toggle to keep or clear the previous results.'))
231 self.search_button.setText(UiStrings().Search)
260232
261 def on_focus(self):233 def on_focus(self):
262 if self.quickTab.isVisible():234 """
263 self.quick_search_edit.setFocus()235 Set focus on the appropriate widget when BibleMediaItem receives focus
264 self.quick_search_edit.selectAll()236
237 Reimplements MediaManagerItem.on_focus()
238
239 :return: None
240 """
241 if self.search_tab.isVisible():
242 self.search_edit.setFocus()
243 self.search_edit.selectAll()
265 else:244 else:
266 self.advanced_book_combo_box.setFocus()245 self.select_book_combo_box.setFocus()
267246
268 def config_update(self):247 def config_update(self):
248 """
249 Change the visible widgets when the config changes
250
251 :return: None
252 """
269 log.debug('config_update')253 log.debug('config_update')
270 if Settings().value(self.settings_section + '/second bibles'):254 visible = Settings().value('{settings_section}/second bibles'.format(settings_section=self.settings_section))
271 self.quickSecondLabel.setVisible(True)255 self.general_bible_layout.labelForField(self.second_combo_box).setVisible(visible)
272 self.quickSecondComboBox.setVisible(True)256 self.second_combo_box.setVisible(visible)
273 self.advancedSecondLabel.setVisible(True)
274 self.advancedSecondComboBox.setVisible(True)
275 self.quickSecondLabel.setVisible(True)
276 self.quickSecondComboBox.setVisible(True)
277 else:
278 self.quickSecondLabel.setVisible(False)
279 self.quickSecondComboBox.setVisible(False)
280 self.advancedSecondLabel.setVisible(False)
281 self.advancedSecondComboBox.setVisible(False)
282 self.quickSecondLabel.setVisible(False)
283 self.quickSecondComboBox.setVisible(False)
284 self.quickStyleComboBox.setCurrentIndex(self.settings.layout_style)
285 self.advancedStyleComboBox.setCurrentIndex(self.settings.layout_style)
286
287 def retranslateUi(self):
288 log.debug('retranslateUi')
289 self.quick_search_label.setText(translate('BiblesPlugin.MediaItem', 'Find:'))
290 self.quickVersionLabel.setText('{version}:'.format(version=UiStrings().Version))
291 self.quickSecondLabel.setText(translate('BiblesPlugin.MediaItem', 'Second:'))
292 self.quickStyleLabel.setText(UiStrings().LayoutStyle)
293 self.quickStyleComboBox.setItemText(LayoutStyle.VersePerSlide, UiStrings().VersePerSlide)
294 self.quickStyleComboBox.setItemText(LayoutStyle.VersePerLine, UiStrings().VersePerLine)
295 self.quickStyleComboBox.setItemText(LayoutStyle.Continuous, UiStrings().Continuous)
296 self.quickClearButton.setToolTip(translate('BiblesPlugin.MediaItem', 'Clear the search results.'))
297 self.quickLockButton.setToolTip(translate('BiblesPlugin.MediaItem',
298 'Toggle to keep or clear the previous results.'))
299 self.quickSearchButton.setText(UiStrings().Search)
300 self.advanced_book_label.setText(translate('BiblesPlugin.MediaItem', 'Book:'))
301 self.advanced_chapter_label.setText(translate('BiblesPlugin.MediaItem', 'Chapter:'))
302 self.advanced_verse_label.setText(translate('BiblesPlugin.MediaItem', 'Verse:'))
303 self.advanced_from_label.setText(translate('BiblesPlugin.MediaItem', 'From:'))
304 self.advanced_to_label.setText(translate('BiblesPlugin.MediaItem', 'To:'))
305 self.advancedVersionLabel.setText('{version}:'.format(version=UiStrings().Version))
306 self.advancedSecondLabel.setText(translate('BiblesPlugin.MediaItem', 'Second:'))
307 self.advancedStyleLabel.setText(UiStrings().LayoutStyle)
308 self.advancedStyleComboBox.setItemText(LayoutStyle.VersePerSlide, UiStrings().VersePerSlide)
309 self.advancedStyleComboBox.setItemText(LayoutStyle.VersePerLine, UiStrings().VersePerLine)
310 self.advancedStyleComboBox.setItemText(LayoutStyle.Continuous, UiStrings().Continuous)
311 self.advancedClearButton.setToolTip(translate('BiblesPlugin.MediaItem', 'Clear the search results.'))
312 self.advancedLockButton.setToolTip(translate('BiblesPlugin.MediaItem',
313 'Toggle to keep or clear the previous results.'))
314 self.advancedSearchButton.setText(UiStrings().Search)
315257
316 def initialise(self):258 def initialise(self):
259 """
260 Called to complete initialisation that could not be completed in the constructor.
261
262 :return: None
263 """
317 log.debug('bible manager initialise')264 log.debug('bible manager initialise')
318 self.plugin.manager.media = self265 self.plugin.manager.media = self
319 self.load_bibles()266 self.populate_bible_combo_boxes()
320 self.quick_search_edit.set_search_types([267 self.search_edit.set_search_types([
321 (BibleSearch.Combined, ':/bibles/bibles_search_combined.png',268 (BibleSearch.Combined, ':/bibles/bibles_search_combined.png',
322 translate('BiblesPlugin.MediaItem', 'Text or Reference'),269 translate('BiblesPlugin.MediaItem', 'Text or Reference'),
323 translate('BiblesPlugin.MediaItem', 'Text or Reference...')),270 translate('BiblesPlugin.MediaItem', 'Text or Reference...')),
@@ -328,164 +275,107 @@
328 translate('BiblesPlugin.MediaItem', 'Text Search'),275 translate('BiblesPlugin.MediaItem', 'Text Search'),
329 translate('BiblesPlugin.MediaItem', 'Search Text...'))276 translate('BiblesPlugin.MediaItem', 'Search Text...'))
330 ])277 ])
331 if Settings().value(self.settings_section + '/reset to combined quick search'):278 if Settings().value(
332 self.quick_search_edit.set_current_search_type(BibleSearch.Combined)279 '{settings_section}/reset to combined quick search'.format(settings_section=self.settings_section)):
280 self.search_edit.set_current_search_type(BibleSearch.Combined)
333 self.config_update()281 self.config_update()
334 log.debug('bible manager initialise complete')282 log.debug('bible manager initialise complete')
335283
336 def load_bibles(self):284 def populate_bible_combo_boxes(self):
285 """
286 Populate the bible combo boxes with the list of bibles that have been loaded
287
288 :return: None
289 """
337 log.debug('Loading Bibles')290 log.debug('Loading Bibles')
338 self.quickVersionComboBox.clear()291 self.version_combo_box.clear()
339 self.quickSecondComboBox.clear()292 self.second_combo_box.clear()
340 self.advancedVersionComboBox.clear()293 self.second_combo_box.addItem('', None)
341 self.advancedSecondComboBox.clear()
342 self.quickSecondComboBox.addItem('')
343 self.advancedSecondComboBox.addItem('')
344 # Get all bibles and sort the list.294 # Get all bibles and sort the list.
345 bibles = list(self.plugin.manager.get_bibles().keys())295 bibles = self.plugin.manager.get_bibles()
346 bibles = [_f for _f in bibles if _f]296 bibles = [(_f, bibles[_f]) for _f in bibles if _f]
347 bibles.sort(key=get_locale_key)297 bibles.sort(key=lambda k: get_locale_key(k[0]))
348 # Load the bibles into the combo boxes.298 for bible in bibles:
349 self.quickVersionComboBox.addItems(bibles)299 self.version_combo_box.addItem(bible[0], bible[1])
350 self.quickSecondComboBox.addItems(bibles)300 self.second_combo_box.addItem(bible[0], bible[1])
351 self.advancedVersionComboBox.addItems(bibles)
352 self.advancedSecondComboBox.addItems(bibles)
353 # set the default value301 # set the default value
354 bible = Settings().value(self.settings_section + '/advanced bible')302 bible = Settings().value('{settings_section}/primary bible'.format(settings_section=self.settings_section))
355 if bible in bibles:303 find_and_set_in_combo_box(self.version_combo_box, bible)
356 find_and_set_in_combo_box(self.advancedVersionComboBox, bible)304
357 self.initialise_advanced_bible(str(bible))305 def reload_bibles(self):
358 elif bibles:306 """
359 self.initialise_advanced_bible(bibles[0])307 Reload the bibles and update the combo boxes
360 bible = Settings().value(self.settings_section + '/quick bible')308
361 find_and_set_in_combo_box(self.quickVersionComboBox, bible)309 :return: None
362310 """
363 def reload_bibles(self, process=False):
364 log.debug('Reloading Bibles')311 log.debug('Reloading Bibles')
365 self.plugin.manager.reload_bibles()312 self.plugin.manager.reload_bibles()
366 self.load_bibles()313 self.populate_bible_combo_boxes()
367 # If called from first time wizard re-run, process any new bibles.314
368 if process:315 def get_common_books(self, first_bible, second_bible=None):
369 self.plugin.app_startup()316 """
370 self.update_auto_completer()317 Return a list of common books between two bibles.
371318
372 def initialise_advanced_bible(self, bible, last_book_id=None):319 :param first_bible: The first bible (BibleDB)
320 :param second_bible: The second bible. (Optional, BibleDB
321 :return: A list of common books between the two bibles. Or if only one bible is supplied a list of that bibles
322 books (list of Book objects)
323 """
324 if not second_bible:
325 return first_bible.get_books()
326 book_data = []
327 for book in first_bible.get_books():
328 for second_book in second_bible.get_books():
329 if book.book_reference_id == second_book.book_reference_id:
330 book_data.append(book)
331 return book_data
332
333 def initialise_advanced_bible(self, last_book=None):
373 """334 """
374 This initialises the given bible, which means that its book names and their chapter numbers is added to the335 This initialises the given bible, which means that its book names and their chapter numbers is added to the
375 combo boxes on the 'Advanced Search' Tab. This is not of any importance of the 'Quick Search' Tab.336 combo boxes on the 'Select' Tab. This is not of any importance of the 'Search' Tab.
376337
377 :param bible: The bible to initialise (unicode).
378 :param last_book_id: The "book reference id" of the book which is chosen at the moment. (int)338 :param last_book_id: The "book reference id" of the book which is chosen at the moment. (int)
339 :return: None
379 """340 """
380 log.debug('initialise_advanced_bible {bible}, {ref}'.format(bible=bible, ref=last_book_id))341 log.debug('initialise_advanced_bible {bible}, {ref}'.format(bible=self.bible, ref=last_book))
381 book_data = self.plugin.manager.get_books(bible)342 self.select_book_combo_box.clear()
382 second_bible = self.advancedSecondComboBox.currentText()343 if self.bible is None:
383 if second_bible != '':344 return
384 second_book_data = self.plugin.manager.get_books(second_bible)345 book_data = self.get_common_books(self.bible, self.second_bible)
385 book_data_temp = []346 language_selection = self.plugin.manager.get_language_selection(self.bible.name)
386 for book in book_data:347 self.select_book_combo_box.model().setDynamicSortFilter(False)
387 for second_book in second_book_data:
388 if book['book_reference_id'] == second_book['book_reference_id']:
389 book_data_temp.append(book)
390 book_data = book_data_temp
391 self.advanced_book_combo_box.clear()
392 first = True
393 initialise_chapter_verse = False
394 language_selection = self.plugin.manager.get_language_selection(bible)
395 book_names = BibleStrings().BookNames
396 for book in book_data:348 for book in book_data:
397 row = self.advanced_book_combo_box.count()349 self.select_book_combo_box.addItem(book.get_name(language_selection), book.book_reference_id)
398 if language_selection == LanguageSelection.Bible:350 self.select_book_combo_box.model().setDynamicSortFilter(True)
399 self.advanced_book_combo_box.addItem(book['name'])351 if last_book:
400 elif language_selection == LanguageSelection.Application:352 index = self.select_book_combo_box.findData(last_book)
401 data = BiblesResourcesDB.get_book_by_id(book['book_reference_id'])353 self.select_book_combo_box.setCurrentIndex(index if index != -1 else 0)
402 self.advanced_book_combo_box.addItem(book_names[data['abbreviation']])354 self.on_advanced_book_combo_box()
403 elif language_selection == LanguageSelection.English:
404 data = BiblesResourcesDB.get_book_by_id(book['book_reference_id'])
405 self.advanced_book_combo_box.addItem(data['name'])
406 self.advanced_book_combo_box.setItemData(row, book['book_reference_id'])
407 if first:
408 first = False
409 first_book = book
410 initialise_chapter_verse = True
411 if last_book_id and last_book_id == int(book['book_reference_id']):
412 index = self.advanced_book_combo_box.findData(book['book_reference_id'])
413 if index == -1:
414 # Not Found.
415 index = 0
416 self.advanced_book_combo_box.setCurrentIndex(index)
417 initialise_chapter_verse = False
418 if initialise_chapter_verse:
419 self.initialise_chapter_verse(bible, first_book['name'], first_book['book_reference_id'])
420
421 def initialise_chapter_verse(self, bible, book, book_ref_id):
422 log.debug('initialise_chapter_verse {bible}, {book}, {ref}'.format(bible=bible, book=book, ref=book_ref_id))
423 book = self.plugin.manager.get_book_by_id(bible, book_ref_id)
424 self.chapter_count = self.plugin.manager.get_chapter_count(bible, book)
425 verse_count = self.plugin.manager.get_verse_count_by_book_ref_id(bible, book_ref_id, 1)
426 if verse_count == 0:
427 self.advancedSearchButton.setEnabled(False)
428 log.warning('Not enough chapters in %s', book_ref_id)
429 critical_error_message_box(message=translate('BiblesPlugin.MediaItem', 'Bible not fully loaded.'))
430 else:
431 self.advancedSearchButton.setEnabled(True)
432 self.adjust_combo_box(1, self.chapter_count, self.advanced_from_chapter)
433 self.adjust_combo_box(1, self.chapter_count, self.advanced_to_chapter)
434 self.adjust_combo_box(1, verse_count, self.advanced_from_verse)
435 self.adjust_combo_box(1, verse_count, self.advanced_to_verse)
436355
437 def update_auto_completer(self):356 def update_auto_completer(self):
438 """357 """
439 This updates the bible book completion list for the search field. The completion depends on the bible. It is358 This updates the bible book completion list for the search field. The completion depends on the bible. It is
440 only updated when we are doing reference or combined search, in text search the completion list is removed.359 only updated when we are doing reference or combined search, in text search the completion list is removed.
360
361 :return: None
441 """362 """
442 log.debug('update_auto_completer')
443 # Save the current bible to the configuration.
444 Settings().setValue('{section}/quick bible'.format(section=self.settings_section),
445 self.quickVersionComboBox.currentText())
446 books = []363 books = []
447 # We have to do a 'Reference Search' (Or as part of Combined Search).364 # We have to do a 'Reference Search' (Or as part of Combined Search).
448 if self.quick_search_edit.current_search_type() is not BibleSearch.Text:365 if self.search_edit.current_search_type() is not BibleSearch.Text:
449 bibles = self.plugin.manager.get_bibles()366 if self.bible:
450 bible = self.quickVersionComboBox.currentText()367 book_data = self.get_common_books(self.bible, self.second_bible)
451 if bible:368 language_selection = self.plugin.manager.get_language_selection(self.bible.name)
452 book_data = bibles[bible].get_books()369 books = [book.get_name(language_selection) for book in book_data]
453 second_bible = self.quickSecondComboBox.currentText()
454 if second_bible != '':
455 second_book_data = bibles[second_bible].get_books()
456 book_data_temp = []
457 for book in book_data:
458 for second_book in second_book_data:
459 if book.book_reference_id == second_book.book_reference_id:
460 book_data_temp.append(book)
461 book_data = book_data_temp
462 language_selection = self.plugin.manager.get_language_selection(bible)
463 if language_selection == LanguageSelection.Bible:
464 books = [book.name + ' ' for book in book_data]
465 elif language_selection == LanguageSelection.Application:
466 book_names = BibleStrings().BookNames
467 for book in book_data:
468 data = BiblesResourcesDB.get_book_by_id(book.book_reference_id)
469 books.append(str(book_names[data['abbreviation']]) + ' ')
470 elif language_selection == LanguageSelection.English:
471 for book in book_data:
472 data = BiblesResourcesDB.get_book_by_id(book.book_reference_id)
473 books.append(data['name'] + ' ')
474 books.sort(key=get_locale_key)370 books.sort(key=get_locale_key)
475 set_case_insensitive_completer(books, self.quick_search_edit)371 set_case_insensitive_completer(books, self.search_edit)
476
477 def on_second_bible_combobox_index_changed(self, selection):
478 """
479 Activate the style combobox only when no second bible is selected
480 """
481 if selection == 0:
482 self.quickStyleComboBox.setEnabled(True)
483 self.advancedStyleComboBox.setEnabled(True)
484 else:
485 self.quickStyleComboBox.setEnabled(False)
486 self.advancedStyleComboBox.setEnabled(False)
487372
488 def on_import_click(self):373 def on_import_click(self):
374 """
375 Create, if not already, the `BibleImportForm` and execute it
376
377 :return: None
378 """
489 if not hasattr(self, 'import_wizard'):379 if not hasattr(self, 'import_wizard'):
490 self.import_wizard = BibleImportForm(self, self.plugin.manager, self.plugin)380 self.import_wizard = BibleImportForm(self, self.plugin.manager, self.plugin)
491 # If the import was not cancelled then reload.381 # If the import was not cancelled then reload.
@@ -493,141 +383,199 @@
493 self.reload_bibles()383 self.reload_bibles()
494384
495 def on_edit_click(self):385 def on_edit_click(self):
496 if self.quickTab.isVisible():386 """
497 bible = self.quickVersionComboBox.currentText()387 Load the EditBibleForm and reload the bibles if the user accepts it
498 elif self.advancedTab.isVisible():388
499 bible = self.advancedVersionComboBox.currentText()389 :return: None
500 if bible:390 """
391 if self.bible:
501 self.edit_bible_form = EditBibleForm(self, self.main_window, self.plugin.manager)392 self.edit_bible_form = EditBibleForm(self, self.main_window, self.plugin.manager)
502 self.edit_bible_form.load_bible(bible)393 self.edit_bible_form.load_bible(self.bible.name)
503 if self.edit_bible_form.exec():394 if self.edit_bible_form.exec():
504 self.reload_bibles()395 self.reload_bibles()
505396
506 def on_delete_click(self):397 def on_delete_click(self):
507 """398 """
508 When the delete button is pressed399 Confirm that the user wants to delete the main bible
400
401 :return: None
509 """402 """
510 bible = None403 if self.bible:
511 if self.quickTab.isVisible():
512 bible = self.quickVersionComboBox.currentText()
513 elif self.advancedTab.isVisible():
514 bible = self.advancedVersionComboBox.currentText()
515 if bible:
516 if QtWidgets.QMessageBox.question(404 if QtWidgets.QMessageBox.question(
517 self, UiStrings().ConfirmDelete,405 self, UiStrings().ConfirmDelete,
518 translate('BiblesPlugin.MediaItem',406 translate('BiblesPlugin.MediaItem',
519 'Are you sure you want to completely delete "{bible}" Bible '407 'Are you sure you want to completely delete "{bible}" Bible from OpenLP?\n\n'
520 'from OpenLP?\n\nYou will need to re-import this Bible to use it '408 'You will need to re-import this Bible to use it again.').format(bible=self.bible.name),
521 'again.').format(bible=bible),409 defaultButton=QtWidgets.QMessageBox.No) == QtWidgets.QMessageBox.No:
522 QtWidgets.QMessageBox.StandardButtons(QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No),
523 QtWidgets.QMessageBox.Yes) == QtWidgets.QMessageBox.No:
524 return410 return
525 self.plugin.manager.delete_bible(bible)411 self.plugin.manager.delete_bible(self.bible.name)
526 self.reload_bibles()412 self.reload_bibles()
527413
528 def on_search_tab_bar_current_changed(self, index):414 def on_search_tab_bar_current_changed(self, index):
529 if index == 0:415 """
530 self.advancedTab.setVisible(False)416 Show the selected tab and set focus to it
531 self.quickTab.setVisible(True)417
532 self.quick_search_edit.setFocus()418 :param index: The tab selected (int)
419 :return: None
420 """
421 search_tab = index == 0
422 self.search_tab.setVisible(search_tab)
423 self.select_tab.setVisible(not search_tab)
424 self.on_focus()
425
426 def on_book_order_button_toggled(self, checked):
427 """
428 Change the sort order of the book names
429
430 :param checked: Indicates if the button is checked or not (Bool)
431 :return: None
432 """
433 if checked:
434 self.select_book_combo_box.model().sort(0)
533 else:435 else:
534 self.quickTab.setVisible(False)436 # -1 Removes the sorting, and returns the items to the order they were added in
535 self.advancedTab.setVisible(True)437 self.select_book_combo_box.model().sort(-1)
536 self.advanced_book_combo_box.setFocus()
537438
538 def on_clear_button_clicked(self):439 def on_clear_button_clicked(self):
539 # Clear the list, then set the "No search Results" message, then clear the text field and give it focus.440 """
540 self.list_view.clear()441 Clear the list_view and the search_edit
541 self.quick_search_edit.clear()
542 self.quick_search_edit.setFocus()
543442
544 def on_advanced_clear_button_clicked(self):443 :return: None
545 # The same as the on_clear_button_clicked, but gives focus to Book name field in "Select" (advanced).444 """
546 self.list_view.clear()445 self.list_view.clear()
547 self.advanced_book_combo_box.setFocus()446 self.search_edit.clear()
447 self.on_focus()
548448
549 def on_lock_button_toggled(self, checked):449 def on_lock_button_toggled(self, checked):
550 """450 """
551 Toggle the lock button, if Search tab is used, set focus to search field.451 Toggle the lock button, if Search tab is used, set focus to search field.
552 :param checked: The state of the toggle button. bool452
453 :param checked: The state of the toggle button. (bool)
553 :return: None454 :return: None
554 """455 """
456 self.list_view.locked = checked
555 if checked:457 if checked:
556 self.sender().setIcon(self.lock_icon)458 self.sender().setIcon(self.lock_icon)
557 else:459 else:
558 self.sender().setIcon(self.unlock_icon)460 self.sender().setIcon(self.unlock_icon)
559 if self.quickTab.isVisible():461
560 self.quick_search_edit.setFocus()462 def on_style_combo_box_index_changed(self, index):
561463 """
562 def on_quick_style_combo_box_changed(self):464 Change the layout style and save the setting
563 self.settings.layout_style = self.quickStyleComboBox.currentIndex()465
564 self.advancedStyleComboBox.setCurrentIndex(self.settings.layout_style)466 :param index: The index of the current item in the combobox (int)
565 self.settings.layout_style_combo_box.setCurrentIndex(self.settings.layout_style)467 :return: None
566 Settings().setValue(self.settings_section + '/verse layout style', self.settings.layout_style)468 """
567469 # TODO: Change layout_style to a property
568 def on_advanced_style_combo_box_changed(self):470 self.settings.layout_style = index
569 self.settings.layout_style = self.advancedStyleComboBox.currentIndex()471 self.settings.layout_style_combo_box.setCurrentIndex(index)
570 self.quickStyleComboBox.setCurrentIndex(self.settings.layout_style)472 Settings().setValue('{section}/verse layout style'.format(section=self.settings_section), index)
571 self.settings.layout_style_combo_box.setCurrentIndex(self.settings.layout_style)473
572 Settings().setValue(self.settings_section + '/verse layout style', self.settings.layout_style)474 def on_version_combo_box_index_changed(self):
573475 """
574 def on_advanced_version_combo_box(self):476 Update the main bible and save it to settings
575 Settings().setValue(self.settings_section + '/advanced bible', self.advancedVersionComboBox.currentText())477
576 self.initialise_advanced_bible(478 :return: None
577 self.advancedVersionComboBox.currentText(),479 """
578 self.advanced_book_combo_box.itemData(int(self.advanced_book_combo_box.currentIndex())))480 self.bible = self.version_combo_box.currentData()
579481 if self.bible is not None:
580 def on_advanced_second_combo_box(self):482 Settings().setValue('{section}/primary bible'.format(section=self.settings_section), self.bible.name)
581 self.initialise_advanced_bible(483 self.initialise_advanced_bible(self.select_book_combo_box.currentData())
582 self.advancedVersionComboBox.currentText(),484
583 self.advanced_book_combo_box.itemData(int(self.advanced_book_combo_box.currentIndex())))485 def on_second_combo_box_index_changed(self, selection):
486 """
487 Update the second bible. If changing from single to dual bible modes as if the user wants to clear the search
488 results, if not revert to the previously selected bible
489
490 :return: None
491 """
492 new_selection = self.second_combo_box.currentData()
493 if self.list_view.count():
494 # Exclusive or (^) the new and previous selections to detect if the user has switched between single and
495 # dual bible mode
496 if (new_selection is None) ^ (self.second_bible is None):
497 if critical_error_message_box(
498 message=translate('BiblesPlugin.MediaItem',
499 'OpenLP cannot combine single and dual Bible verse search results. '
500 'Do you want to clear your search results and start a new search?'),
501 parent=self, question=True) == QtWidgets.QMessageBox.Yes:
502 self.list_view.clear(override_lock=True)
503 else:
504 self.second_combo_box.setCurrentIndex(self.second_combo_box.findData(self.second_bible))
505 return
506 self.second_bible = new_selection
507 if new_selection is None:
508 self.style_combo_box.setEnabled(True)
509 else:
510 self.style_combo_box.setEnabled(False)
511 self.initialise_advanced_bible(self.select_book_combo_box.currentData())
584512
585 def on_advanced_book_combo_box(self):513 def on_advanced_book_combo_box(self):
586 item = int(self.advanced_book_combo_box.currentIndex())514 """
587 self.initialise_chapter_verse(515 Update the verse selection boxes
588 self.advancedVersionComboBox.currentText(),516
589 self.advanced_book_combo_box.currentText(),517 :return: None
590 self.advanced_book_combo_box.itemData(item))518 """
591519 book_ref_id = self.select_book_combo_box.currentData()
592 def on_advanced_from_verse(self):520 book = self.plugin.manager.get_book_by_id(self.bible.name, book_ref_id)
593 chapter_from = int(self.advanced_from_chapter.currentText())521 self.chapter_count = self.plugin.manager.get_chapter_count(self.bible.name, book)
594 chapter_to = int(self.advanced_to_chapter.currentText())522 verse_count = self.plugin.manager.get_verse_count_by_book_ref_id(self.bible.name, book_ref_id, 1)
523 if verse_count == 0:
524 self.search_button.setEnabled(False)
525 log.warning('Not enough chapters in %s', book_ref_id)
526 critical_error_message_box(message=translate('BiblesPlugin.MediaItem', 'Bible not fully loaded.'))
527 else:
528 self.search_button.setEnabled(True)
529 self.adjust_combo_box(1, self.chapter_count, self.from_chapter)
530 self.adjust_combo_box(1, self.chapter_count, self.to_chapter)
531 self.adjust_combo_box(1, verse_count, self.from_verse)
532 self.adjust_combo_box(1, verse_count, self.to_verse)
533
534 def on_from_chapter_activated(self):
535 """
536 Update the verse selection boxes
537
538 :return: None
539 """
540 book_ref_id = self.select_book_combo_box.currentData()
541 chapter_from = self.from_chapter.currentData()
542 chapter_to = self.to_chapter.currentData()
543 verse_count = self.plugin.manager.get_verse_count_by_book_ref_id(self.bible.name, book_ref_id, chapter_from)
544 self.adjust_combo_box(1, verse_count, self.from_verse)
545 if chapter_from >= chapter_to:
546 self.adjust_combo_box(1, verse_count, self.to_verse, chapter_from == chapter_to)
547 self.adjust_combo_box(chapter_from, self.chapter_count, self.to_chapter, chapter_from < chapter_to)
548
549 def on_from_verse(self):
550 """
551 Update the verse selection boxes
552
553 :return: None
554 """
555 chapter_from = self.from_chapter.currentData()
556 chapter_to = self.to_chapter.currentData()
595 if chapter_from == chapter_to:557 if chapter_from == chapter_to:
596 bible = self.advancedVersionComboBox.currentText()558 book_ref_id = self.select_book_combo_box.currentData()
597 book_ref_id = self.advanced_book_combo_box.itemData(int(self.advanced_book_combo_box.currentIndex()))559 verse_from = self.from_verse.currentData()
598 verse_from = int(self.advanced_from_verse.currentText())560 verse_count = self.plugin.manager.get_verse_count_by_book_ref_id(self.bible.name, book_ref_id, chapter_to)
599 verse_count = self.plugin.manager.get_verse_count_by_book_ref_id(bible, book_ref_id, chapter_to)561 self.adjust_combo_box(verse_from, verse_count, self.to_verse, True)
600 self.adjust_combo_box(verse_from, verse_count, self.advanced_to_verse, True)562
601563 def on_to_chapter(self):
602 def on_advanced_to_chapter(self):564 """
603 bible = self.advancedVersionComboBox.currentText()565 Update the verse selection boxes
604 book_ref_id = self.advanced_book_combo_box.itemData(int(self.advanced_book_combo_box.currentIndex()))566
605 chapter_from = int(self.advanced_from_chapter.currentText())567 :return: None
606 chapter_to = int(self.advanced_to_chapter.currentText())568 """
607 verse_from = int(self.advanced_from_verse.currentText())569 book_ref_id = self.select_book_combo_box.currentData()
608 verse_to = int(self.advanced_to_verse.currentText())570 chapter_from = self.from_chapter.currentData()
609 verse_count = self.plugin.manager.get_verse_count_by_book_ref_id(bible, book_ref_id, chapter_to)571 chapter_to = self.to_chapter.currentData()
572 verse_from = self.from_verse.currentData()
573 verse_to = self.to_verse.currentData()
574 verse_count = self.plugin.manager.get_verse_count_by_book_ref_id(self.bible.name, book_ref_id, chapter_to)
610 if chapter_from == chapter_to and verse_from > verse_to:575 if chapter_from == chapter_to and verse_from > verse_to:
611 self.adjust_combo_box(verse_from, verse_count, self.advanced_to_verse)576 self.adjust_combo_box(verse_from, verse_count, self.to_verse)
612 else:577 else:
613 self.adjust_combo_box(1, verse_count, self.advanced_to_verse)578 self.adjust_combo_box(1, verse_count, self.to_verse)
614
615 def on_advanced_from_chapter(self):
616 bible = self.advancedVersionComboBox.currentText()
617 book_ref_id = self.advanced_book_combo_box.itemData(
618 int(self.advanced_book_combo_box.currentIndex()))
619 chapter_from = int(self.advanced_from_chapter.currentText())
620 chapter_to = int(self.advanced_to_chapter.currentText())
621 verse_count = self.plugin.manager.get_verse_count_by_book_ref_id(bible, book_ref_id, chapter_from)
622 self.adjust_combo_box(1, verse_count, self.advanced_from_verse)
623 if chapter_from > chapter_to:
624 self.adjust_combo_box(1, verse_count, self.advanced_to_verse)
625 self.adjust_combo_box(chapter_from, self.chapter_count, self.advanced_to_chapter)
626 elif chapter_from == chapter_to:
627 self.adjust_combo_box(chapter_from, self.chapter_count, self.advanced_to_chapter)
628 self.adjust_combo_box(1, verse_count, self.advanced_to_verse, True)
629 else:
630 self.adjust_combo_box(chapter_from, self.chapter_count, self.advanced_to_chapter, True)
631579
632 def adjust_combo_box(self, range_from, range_to, combo, restore=False):580 def adjust_combo_box(self, range_from, range_to, combo, restore=False):
633 """581 """
@@ -640,380 +588,195 @@
640 """588 """
641 log.debug('adjust_combo_box {box}, {start}, {end}'.format(box=combo, start=range_from, end=range_to))589 log.debug('adjust_combo_box {box}, {start}, {end}'.format(box=combo, start=range_from, end=range_to))
642 if restore:590 if restore:
643 old_text = combo.currentText()591 old_selection = combo.currentData()
644 combo.clear()592 combo.clear()
645 combo.addItems(list(map(str, list(range(range_from, range_to + 1)))))593 for item in range(range_from, range_to + 1):
646 if restore and combo.findText(old_text) != -1:594 combo.addItem(str(item), item)
647 combo.setCurrentIndex(combo.findText(old_text))595 if restore:
648596 index = combo.findData(old_selection)
649 def on_advanced_search_button(self):597 combo.setCurrentIndex(index if index != -1 else 0)
650 """598
651 Does an advanced search and saves the search results.599 def on_search_button_clicked(self):
652 """600 """
653 log.debug('Advanced Search Button clicked')601 Call the correct search function depending on which tab the user is using
654 self.advancedSearchButton.setEnabled(False)602
603 :return: None
604 """
605 if not self.bible:
606 self.main_window.information_message(UiStrings().BibleNoBiblesTitle, UiStrings().BibleNoBibles)
607 return
608 self.search_button.setEnabled(False)
609 self.application.set_busy_cursor()
655 self.application.process_events()610 self.application.process_events()
656 bible = self.advancedVersionComboBox.currentText()611 if self.search_tab.isVisible():
657 second_bible = self.advancedSecondComboBox.currentText()612 self.text_search()
658 book = self.advanced_book_combo_box.currentText()613 elif self.select_tab.isVisible():
659 book_ref_id = self.advanced_book_combo_box.itemData(int(self.advanced_book_combo_box.currentIndex()))614 self.select_search()
660 chapter_from = self.advanced_from_chapter.currentText()615 self.search_button.setEnabled(True)
661 chapter_to = self.advanced_to_chapter.currentText()
662 verse_from = self.advanced_from_verse.currentText()
663 verse_to = self.advanced_to_verse.currentText()
664 verse_separator = get_reference_separator('sep_v_display')
665 range_separator = get_reference_separator('sep_r_display')
666 verse_range = chapter_from + verse_separator + verse_from + range_separator + chapter_to + \
667 verse_separator + verse_to
668 verse_text = '{book} {verse}'.format(book=book, verse=verse_range)
669 self.application.set_busy_cursor()
670 self.search_results = self.plugin.manager.get_verses(bible, verse_text, book_ref_id)
671 if second_bible:
672 self.second_search_results = self.plugin.manager.get_verses(second_bible, verse_text, book_ref_id)
673 if not self.advancedLockButton.isChecked():
674 self.list_view.clear()
675 if self.list_view.count() != 0:
676 self.__check_second_bible(bible, second_bible)
677 elif self.search_results:
678 self.display_results(bible, second_bible)
679 self.advancedSearchButton.setEnabled(True)
680 self.application.set_normal_cursor()616 self.application.set_normal_cursor()
681617
682 def on_quick_reference_search(self):618 def select_search(self):
619 """
620 Preform a search using the passage selected on the `Select` tab
621
622 :return: None
623 """
624 verse_range = self.plugin.manager.process_verse_range(
625 self.select_book_combo_box.currentData(), self.from_chapter.currentData(), self.from_verse.currentData(),
626 self.to_chapter.currentData(), self.to_verse.currentData())
627 self.search_results = self.plugin.manager.get_verses(self.bible.name, verse_range, False)
628 if self.second_bible:
629 self.second_search_results = self.plugin.manager.get_verses(self.second_bible.name, verse_range, False)
630 self.display_results()
631
632 def text_reference_search(self, search_text):
683 """633 """
684 We are doing a 'Reference Search'.634 We are doing a 'Reference Search'.
685 This search is called on def on_quick_search_button by Quick Reference and Combined Searches.635 This search is called on def text_search by Reference and Combined Searches.
686 """636
687 # Set Bibles to use the text input from Quick search field.637 :return: None
688 bible = self.quickVersionComboBox.currentText()638 """
689 second_bible = self.quickSecondComboBox.currentText()639 verse_refs = self.plugin.manager.parse_ref(self.bible.name, search_text)
690 """640 self.search_results = self.plugin.manager.get_verses(self.bible.name, verse_refs, True)
691 Get input from field and replace 'A-Z + . ' with ''641 if self.second_bible and self.search_results:
692 This will check if field has any '.' after A-Z and removes them. Eg. Gen. 1 = Ge 1 = Genesis 1642 self.search_results = self.plugin.manager.get_verses(self.second_bible.name, verse_refs, True)
693 If Book name has '.' after number. eg. 1. Genesis, the search fails without the dot, and vice versa.643 self.display_results()
694 A better solution would be to make '.' optional in the search results. Current solution was easier to code.644
695 """645 def on_text_search(self, text, search_while_type=False):
696 text = self.quick_search_edit.text()
697 text = re.sub('\D[.]\s', ' ', text)
698 # This is triggered on reference search, use the search from manager.py
699 if self.quick_search_edit.current_search_type() != BibleSearch.Text:
700 self.search_results = self.plugin.manager.get_verses(bible, text)
701 if second_bible and self.search_results:
702 self.second_search_results = \
703 self.plugin.manager.get_verses(second_bible, text, self.search_results[0].book.book_reference_id)
704
705 def on_quick_text_search(self):
706 """646 """
707 We are doing a 'Text Search'.647 We are doing a 'Text Search'.
708 This search is called on def on_quick_search_button by Quick Text and Combined Searches.648 This search is called on def text_search by 'Search' Text and Combined Searches.
709 """649 """
710 # Set Bibles to use the text input from Quick search field.650 self.search_results = self.plugin.manager.verse_search(self.bible.name, text)
711 bible = self.quickVersionComboBox.currentText()651 if self.second_bible and self.search_results:
712 second_bible = self.quickSecondComboBox.currentText()652 filtered_search_results = []
713 text = self.quick_search_edit.text()653 not_found_count = 0
714 # If Text search ends with "," OpenLP will crash, prevent this from happening by removing all ","s.
715 text = re.sub('[,]', '', text)
716 self.application.set_busy_cursor()
717 # Get Bibles list
718 bibles = self.plugin.manager.get_bibles()
719 # Add results to "search_results"
720 self.search_results = self.plugin.manager.verse_search(bible, second_bible, text)
721 if second_bible and self.search_results:
722 # new_search_results is needed to make sure 2nd bible contains all verses. (And counting them on error)
723 text = []
724 new_search_results = []
725 count = 0
726 passage_not_found = False
727 # Search second bible for results of search_results to make sure everythigns there.
728 # Count all the unfound passages.
729 for verse in self.search_results:654 for verse in self.search_results:
730 db_book = bibles[second_bible].get_book_by_book_ref_id(verse.book.book_reference_id)655 second_verse = self.second_bible.get_verses(
731 if not db_book:656 [(verse.book.book_reference_id, verse.chapter, verse.verse, verse.verse)], False)
732 log.debug('Passage "{name} {chapter:d}:{verse:d}" not found in '657 if second_verse:
733 'Second Bible'.format(name=verse.book.name, chapter=verse.chapter, verse=verse.verse))658 filtered_search_results.append(verse)
734 passage_not_found = True659 self.second_search_results += second_verse
735 count += 1660 else:
736 continue661 log.debug('Verse "{name} {chapter:d}:{verse:d}" not found in Second Bible "{bible_name}"'.format(
737 new_search_results.append(verse)662 name=verse.book.name, chapter=verse.chapter,
738 text.append((verse.book.book_reference_id, verse.chapter, verse.verse, verse.verse))663 verse=verse.verse, bible_name=self.second_bible.name))
739 if passage_not_found:664 not_found_count += 1
740 # This is for the 2nd Bible.665 self.search_results = filtered_search_results
666 if not_found_count != 0 and not search_while_type:
741 self.main_window.information_message(667 self.main_window.information_message(
742 translate('BiblesPlugin.MediaItem', 'Information'),668 translate('BiblesPlugin.MediaItem', 'Verses not found'),
743 translate('BiblesPlugin.MediaItem', 'The second Bible does not contain all the verses '669 translate('BiblesPlugin.MediaItem',
744 'that are in the main Bible.\nOnly verses found in both Bibles'670 'The second Bible "{second_name}" does not contain all the verses that are in the main '
745 ' will be shown.\n\n{count:d} verses have not been included '671 'Bible "{name}".\nOnly verses found in both Bibles will be shown.\n\n'
746 'in the results.').format(count=count))672 '{count:d} verses have not been included in the results.'
747 # Join the searches so only verses that are found on both Bibles are shown.673 ).format(second_name=self.second_bible.name, name=self.bible.name, count=not_found_count))
748 self.search_results = new_search_results674 self.display_results()
749 self.second_search_results = bibles[second_bible].get_verses(text)675
750676 def text_search(self, search_while_type=False):
751 def on_quick_text_search_while_typing(self):677 """
752 """678 This triggers the proper 'Search' search based on which search type is used.
753 We are doing a 'Text Search' while typing
754 Call the verse_search_while_typing from manager.py
755 It does not show web bible errors while typing.
756 (It would result in the error popping every time a char is entered or removed)
757 """
758 # Set Bibles to use the text input from Quick search field.
759 bible = self.quickVersionComboBox.currentText()
760 second_bible = self.quickSecondComboBox.currentText()
761 text = self.quick_search_edit.text()
762 # If Text search ends with "," OpenLP will crash, prevent this from happening by removing all ","s.
763 text = re.sub('[,]', '', text)
764 self.application.set_busy_cursor()
765 # Get Bibles list
766 bibles = self.plugin.manager.get_bibles()
767 # Add results to "search_results"
768 self.search_results = self.plugin.manager.verse_search_while_typing(bible, second_bible, text)
769 if second_bible and self.search_results:
770 # new_search_results is needed to make sure 2nd bible contains all verses. (And counting them on error)
771 text = []
772 new_search_results = []
773 count = 0
774 passage_not_found = False
775 # Search second bible for results of search_results to make sure everythigns there.
776 # Count all the unfound passages. Even thou no error is shown, this needs to be done or
777 # the code breaks later on.
778 for verse in self.search_results:
779 db_book = bibles[second_bible].get_book_by_book_ref_id(verse.book.book_reference_id)
780 if not db_book:
781 log.debug('Passage ("{versebookname}","{versechapter}","{verseverse}") not found in Second Bible'
782 .format(versebookname=verse.book.name, versechapter='verse.chapter',
783 verseverse=verse.verse))
784 count += 1
785 continue
786 new_search_results.append(verse)
787 text.append((verse.book.book_reference_id, verse.chapter, verse.verse, verse.verse))
788 # Join the searches so only verses that are found on both Bibles are shown.
789 self.search_results = new_search_results
790 self.second_search_results = bibles[second_bible].get_verses(text)
791
792 def on_quick_search_button(self):
793 """
794 This triggers the proper Quick search based on which search type is used.
795 "Eg. "Reference Search", "Text Search" or "Combined search".679 "Eg. "Reference Search", "Text Search" or "Combined search".
796 """680 """
797 log.debug('Quick Search Button clicked')681 log.debug('text_search called')
798 self.quickSearchButton.setEnabled(False)682 text = self.search_edit.text()
799 self.application.process_events()683 if text == '':
800 bible = self.quickVersionComboBox.currentText()684 self.list_view.clear()
801 second_bible = self.quickSecondComboBox.currentText()685 return
802 text = self.quick_search_edit.text()686 self.list_view.clear(search_while_typing=search_while_type)
803 if self.quick_search_edit.current_search_type() == BibleSearch.Reference:687 if self.search_edit.current_search_type() == BibleSearch.Reference:
804 # We are doing a 'Reference Search'. (Get script from def on_quick_reference_search)688 if get_reference_match('full').match(text):
805 self.on_quick_reference_search()689 # Valid reference found. Do reference search.
806 # Get reference separators from settings.690 self.text_reference_search(text)
807 if not self.search_results:691 elif not search_while_type:
808 reference_separators = {
809 'verse': get_reference_separator('sep_v_display'),
810 'range': get_reference_separator('sep_r_display'),
811 'list': get_reference_separator('sep_l_display')}
812 self.main_window.information_message(692 self.main_window.information_message(
813 translate('BiblesPlugin.BibleManager', 'Scripture Reference Error'),693 translate('BiblesPlugin.BibleManager', 'Scripture Reference Error'),
814 translate('BiblesPlugin.BibleManager', '<strong>OpenLP couldn’t find anything '694 translate('BiblesPlugin.BibleManager',
815 'with your search.<br><br>'695 '<strong>The reference you typed is invalid!<br><br>'
816 'Please make sure that your reference follows '696 'Please make sure that your reference follows one of these patterns:</strong><br><br>%s')
817 'one of these patterns:</strong><br><br>%s'697 % UiStrings().BibleScriptureError % get_reference_separators())
818 % UiStrings().BibleScriptureError % reference_separators))698 elif self.search_edit.current_search_type() == BibleSearch.Combined and get_reference_match('full').match(text):
819 elif self.quick_search_edit.current_search_type() == BibleSearch.Text:699 # Valid reference found. Do reference search.
820 # We are doing a 'Text Search'. (Get script from def on_quick_text_search)700 self.text_reference_search(text)
821 self.on_quick_text_search()701 else:
822 if not self.search_results and len(text) - text.count(' ') < 3 and bible:702 # It can only be a 'Combined' search without a valid reference, or a 'Text' search
823 self.main_window.information_message(703 if search_while_type:
824 UiStrings().BibleShortSearchTitle,704 if len(text) > 8 and VALID_TEXT_SEARCH.search(text):
825 UiStrings().BibleShortSearch)705 self.on_text_search(text, True)
826 elif self.quick_search_edit.current_search_type() == BibleSearch.Combined:706 elif VALID_TEXT_SEARCH.search(text):
827 # We are doing a 'Combined search'. Starting with reference search.707 self.on_text_search(text)
828 # Perform only if text contains any numbers708
829 if (char.isdigit() for char in text):709 def on_search_edit_text_changed(self):
830 self.on_quick_reference_search()710 """
831 """711 If 'search_as_you_type' is enabled, start a timer when the search_edit emits a textChanged signal. This is to
832 If results are found, search will be finalized.712 prevent overloading the system by submitting too many search requests in a short space of time.
833 This check needs to be here in order to avoid duplicate errors.713
834 If keyword is shorter than 3 (not including spaces), message is given. It's actually possible to find714 :return: None
835 verses with less than 3 chars (Eg. G1 = Genesis 1) thus this error is not shown if any results are found.
836 if no Bibles are installed, this message is not shown - "No bibles" message is shown instead.
837 """
838 if not self.search_results and len(text) - text.count(' ') < 3 and bible:
839 self.main_window.information_message(
840 UiStrings().BibleShortSearchTitle,
841 UiStrings().BibleShortSearch)
842 if not self.search_results and len(text) - text.count(' ') > 2 and bible:
843 # Text search starts here if no reference was found and keyword is longer than 2.
844 # > 2 check is required in order to avoid duplicate error messages for short keywords.
845 self.on_quick_text_search()
846 if not self.search_results and not \
847 Settings().value(self.settings_section + '/hide combined quick error'):
848 self.application.set_normal_cursor()
849 # Reference separators need to be defined both, in here and on reference search,
850 # error won't work if they are left out from one.
851 reference_separators = {
852 'verse': get_reference_separator('sep_v_display'),
853 'range': get_reference_separator('sep_r_display'),
854 'list': get_reference_separator('sep_l_display')}
855 self.main_window.information_message(translate('BiblesPlugin.BibleManager', 'Nothing found'),
856 translate('BiblesPlugin.BibleManager',
857 '<strong>OpenLP couldn’t find anything with your'
858 ' search.</strong><br><br>If you tried to search'
859 ' with Scripture Reference, please make<br> sure'
860 ' that your reference follows one of these'
861 ' patterns: <br><br>%s'
862 % UiStrings().BibleScriptureError %
863 reference_separators))
864 # Finalizing the search
865 # List is cleared if not locked, results are listed, button is set available, cursor is set to normal.
866 if not self.quickLockButton.isChecked():
867 self.list_view.clear()
868 if self.list_view.count() != 0 and self.search_results:
869 self.__check_second_bible(bible, second_bible)
870 elif self.search_results:
871 self.display_results(bible, second_bible)
872 self.quickSearchButton.setEnabled(True)
873 self.application.set_normal_cursor()
874
875 def on_quick_search_while_typing(self):
876 """
877 This function is called when "Search as you type" is enabled for Bibles.
878 It is basically the same thing as "on_quick_search_search" but all the error messages are removed.
879 This also has increased min len for text search for performance reasons.
880 For commented version, please visit def on_quick_search_button.
881 """
882 bible = self.quickVersionComboBox.currentText()
883 second_bible = self.quickSecondComboBox.currentText()
884 text = self.quick_search_edit.text()
885 if self.quick_search_edit.current_search_type() == BibleSearch.Combined:
886 # If text has no numbers, auto search limit is min 8 characters for performance reasons.
887 # If you change this value, also change it in biblestab.py (Count) in enabling search while typing.
888 if (char.isdigit() for char in text) and len(text) > 2:
889 self.on_quick_reference_search()
890 if not self.search_results and len(text) > 7:
891 self.on_quick_text_search_while_typing()
892 elif self.quick_search_edit.current_search_type() == BibleSearch.Reference:
893 self.on_quick_reference_search()
894 elif self.quick_search_edit.current_search_type() == BibleSearch.Text:
895 if len(text) > 7:
896 self.on_quick_text_search_while_typing()
897 if not self.quickLockButton.isChecked():
898 self.list_view.clear()
899 if self.list_view.count() != 0 and self.search_results:
900 self.__check_second_bible(bible, second_bible)
901 elif self.search_results:
902 self.display_results(bible, second_bible)
903 self.application.set_normal_cursor()
904
905 def on_search_text_edit_changed(self):
906 """
907 If search automatically while typing is enabled, perform the search and list results when conditions are met.
908 """715 """
909 if Settings().value('bibles/is search while typing enabled'):716 if Settings().value('bibles/is search while typing enabled'):
910 text = self.quick_search_edit.text()717 if not self.search_timer.isActive():
911 """718 self.search_timer.start()
912 Use Regex for finding space + number in reference search and space + 2 characters in text search.719
913 Also search for two characters (Searches require at least two sets of two characters)720 def on_search_timer_timeout(self):
914 These are used to prevent bad search queries from starting. (Long/crashing queries)721 """
915 """722 Perform a search when the search timer timeouts. The search timer is used for 'search_as_you_type' so that we
916 space_and_digit_reference = re.compile(' \d')723 don't overload the system buy submitting too many search requests in a short space of time.
917 two_chars_text = re.compile('\S\S')724
918 space_and_two_chars_text = re.compile(' \S\S')725 :return: None
919 # Turn this into a format that may be used in if statement.726 """
920 count_space_digit_reference = space_and_digit_reference.findall(text)727 self.text_search(True)
921 count_two_chars_text = two_chars_text.findall(text)728
922 count_spaces_two_chars_text = space_and_two_chars_text.findall(text)729 def display_results(self):
923 """730 """
924 The Limit is required for setting the proper "No items found" message.731 Add the search results to the media manager list.
925 "Limit" is also hard coded to on_quick_search_while_typing, it must be there to avoid bad search732
926 performance. Limit 8 = Text search, 3 = Reference search.733 :return: None
927 """734 """
928 limit = 8735 self.list_view.clear()
929 if self.quick_search_edit.current_search_type() == BibleSearch.Combined:736 if self.search_results:
930 if len(count_space_digit_reference) != 0:737 items = self.build_display_results(self.bible, self.second_bible, self.search_results)
931 limit = 3738 for item in items:
932 elif self.quick_search_edit.current_search_type() == BibleSearch.Reference:739 self.list_view.addItem(item)
933 limit = 3740 self.list_view.selectAll()
934 """741 self.search_results = []
935 If text is empty, clear the list.742 self.second_search_results = []
936 else: Start by checking if the search is suitable for "Search while typing"
937 """
938 if len(text) == 0:
939 if not self.quickLockButton.isChecked():
940 self.list_view.clear()
941 else:
942 if limit == 3 and (len(text) < limit or len(count_space_digit_reference) == 0):
943 if not self.quickLockButton.isChecked():
944 self.list_view.clear()
945 elif limit == 8 and (len(text) < limit or len(count_two_chars_text) < 2):
946 if not self.quickLockButton.isChecked():
947 self.list_view.clear(search_while_typing=True)
948 else:
949 """
950 Start search if no chars are entered or deleted for 0.2 s
951 If no Timer is set, Text search will break the search by sending repeative search Quaries on
952 all chars. Use the self.on_quick_search_while_typing, this does not contain any error messages.
953 """
954 self.search_timer = ()
955 if self.search_timer:
956 self.search_timer.stop()
957 self.search_timer.deleteLater()
958 self.search_timer = QtCore.QTimer()
959 self.search_timer.timeout.connect(self.on_quick_search_while_typing)
960 self.search_timer.setSingleShot(True)
961 self.search_timer.start(200)
962
963 def display_results(self, bible, second_bible=''):
964 """
965 Displays the search results in the media manager. All data needed for further action is saved for/in each row.
966 """
967 items = self.build_display_results(bible, second_bible, self.search_results)
968 for bible_verse in items:
969 self.list_view.addItem(bible_verse)
970 self.list_view.selectAll()
971 self.search_results = {}
972 self.second_search_results = {}
973743
974 def build_display_results(self, bible, second_bible, search_results):744 def build_display_results(self, bible, second_bible, search_results):
975 """745 """
976 Displays the search results in the media manager. All data needed for further action is saved for/in each row.746 Displays the search results in the media manager. All data needed for further action is saved for/in each row.
977 """747 """
978 verse_separator = get_reference_separator('sep_v_display')748 verse_separator = get_reference_separators()['verse']
979 version = self.plugin.manager.get_meta_data(bible, 'name').value749 version = self.plugin.manager.get_meta_data(self.bible.name, 'name').value
980 copyright = self.plugin.manager.get_meta_data(bible, 'copyright').value750 copyright = self.plugin.manager.get_meta_data(self.bible.name, 'copyright').value
981 permissions = self.plugin.manager.get_meta_data(bible, 'permissions').value751 permissions = self.plugin.manager.get_meta_data(self.bible.name, 'permissions').value
752 second_name = ''
982 second_version = ''753 second_version = ''
983 second_copyright = ''754 second_copyright = ''
984 second_permissions = ''755 second_permissions = ''
985 if second_bible:756 if second_bible:
986 second_version = self.plugin.manager.get_meta_data(second_bible, 'name').value757 second_name = second_bible.name
987 second_copyright = self.plugin.manager.get_meta_data(second_bible, 'copyright').value758 second_version = self.plugin.manager.get_meta_data(self.second_bible.name, 'name').value
988 second_permissions = self.plugin.manager.get_meta_data(second_bible, 'permissions').value759 second_copyright = self.plugin.manager.get_meta_data(self.second_bible.name, 'copyright').value
760 second_permissions = self.plugin.manager.get_meta_data(self.second_bible.name, 'permissions').value
989 items = []761 items = []
990 language_selection = self.plugin.manager.get_language_selection(bible)762 language_selection = self.plugin.manager.get_language_selection(self.bible.name)
991 for count, verse in enumerate(search_results):763 for count, verse in enumerate(search_results):
992 book = None
993 if language_selection == LanguageSelection.Bible:
994 book = verse.book.name
995 elif language_selection == LanguageSelection.Application:
996 book_names = BibleStrings().BookNames
997 data = BiblesResourcesDB.get_book_by_id(verse.book.book_reference_id)
998 book = str(book_names[data['abbreviation']])
999 elif language_selection == LanguageSelection.English:
1000 data = BiblesResourcesDB.get_book_by_id(verse.book.book_reference_id)
1001 book = data['name']
1002 data = {764 data = {
1003 'book': book,765 'book': verse.book.get_name(language_selection),
1004 'chapter': verse.chapter,766 'chapter': verse.chapter,
1005 'verse': verse.verse,767 'verse': verse.verse,
1006 'bible': bible,768 'bible': self.bible.name,
1007 'version': version,769 'version': version,
1008 'copyright': copyright,770 'copyright': copyright,
1009 'permissions': permissions,771 'permissions': permissions,
1010 'text': verse.text,772 'text': verse.text,
1011 'second_bible': second_bible,773 'second_bible': second_name,
1012 'second_version': second_version,774 'second_version': second_version,
1013 'second_copyright': second_copyright,775 'second_copyright': second_copyright,
1014 'second_permissions': second_permissions,776 'second_permissions': second_permissions,
1015 'second_text': ''777 'second_text': ''
1016 }778 }
779
1017 if second_bible:780 if second_bible:
1018 try:781 try:
1019 data['second_text'] = self.second_search_results[count].text782 data['second_text'] = self.second_search_results[count].text
@@ -1023,20 +786,10 @@
1023 except TypeError:786 except TypeError:
1024 log.exception('The second_search_results does not have this book.')787 log.exception('The second_search_results does not have this book.')
1025 break788 break
1026 bible_text = ('{book} {chapter:d}{sep}{verse:d} '789 bible_text = '{book} {chapter:d}{sep}{verse:d} ({version}, {second_version})'
1027 '({version1}, {version2})').format(book=book,
1028 chapter=verse.chapter,
1029 sep=verse_separator,
1030 verse=verse.verse,
1031 version1=version,
1032 version2=second_version)
1033 else:790 else:
1034 bible_text = '{book} {chapter:d}{sep}{verse:d} ({version})'.format(book=book,791 bible_text = '{book} {chapter:d}{sep}{verse:d} ({version})'
1035 chapter=verse.chapter,792 bible_verse = QtWidgets.QListWidgetItem(bible_text.format(sep=verse_separator, **data))
1036 sep=verse_separator,
1037 verse=verse.verse,
1038 version=version)
1039 bible_verse = QtWidgets.QListWidgetItem(bible_text)
1040 bible_verse.setData(QtCore.Qt.UserRole, data)793 bible_verse.setData(QtCore.Qt.UserRole, data)
1041 items.append(bible_verse)794 items.append(bible_verse)
1042 return items795 return items
@@ -1060,63 +813,44 @@
1060 if not items:813 if not items:
1061 return False814 return False
1062 bible_text = ''815 bible_text = ''
1063 old_item = None
1064 old_chapter = -1816 old_chapter = -1
1065 raw_slides = []817 raw_slides = []
1066 raw_title = []
1067 verses = VerseReferenceList()818 verses = VerseReferenceList()
1068 for bitem in items:819 for bitem in items:
1069 book = self._decode_qt_object(bitem, 'book')820 data = bitem.data(QtCore.Qt.UserRole)
1070 chapter = int(self._decode_qt_object(bitem, 'chapter'))821 verses.add(
1071 verse = int(self._decode_qt_object(bitem, 'verse'))822 data['book'], data['chapter'], data['verse'], data['version'], data['copyright'], data['permissions'])
1072 bible = self._decode_qt_object(bitem, 'bible')823 verse_text = self.format_verse(old_chapter, data['chapter'], data['verse'])
1073 version = self._decode_qt_object(bitem, 'version')824 # We only support 'Verse Per Slide' when using a scond bible
1074 copyright = self._decode_qt_object(bitem, 'copyright')825 if data['second_bible']:
1075 permissions = self._decode_qt_object(bitem, 'permissions')826 second_text = self.format_verse(old_chapter, data['chapter'], data['verse'])
1076 text = self._decode_qt_object(bitem, 'text')827 bible_text = '{first_version}{data[text]}\n\n{second_version}{data[second_text]}'\
1077 second_bible = self._decode_qt_object(bitem, 'second_bible')828 .format(first_version=verse_text, second_version=second_text, data=data)
1078 second_version = self._decode_qt_object(bitem, 'second_version')
1079 second_copyright = self._decode_qt_object(bitem, 'second_copyright')
1080 second_permissions = self._decode_qt_object(bitem, 'second_permissions')
1081 second_text = self._decode_qt_object(bitem, 'second_text')
1082 verses.add(book, chapter, verse, version, copyright, permissions)
1083 verse_text = self.format_verse(old_chapter, chapter, verse)
1084 if second_bible:
1085 bible_text = '{verse}{text1}\n\n{verse}&nbsp;{text2}'.format(verse=verse_text,
1086 text1=text,
1087 text2=second_text)
1088 raw_slides.append(bible_text.rstrip())829 raw_slides.append(bible_text.rstrip())
1089 bible_text = ''830 bible_text = ''
1090 # If we are 'Verse Per Slide' then create a new slide.831 # If we are 'Verse Per Slide' then create a new slide.
1091 elif self.settings.layout_style == LayoutStyle.VersePerSlide:832 elif self.settings.layout_style == LayoutStyle.VersePerSlide:
1092 bible_text = '{verse}{text}'.format(verse=verse_text, text=text)833 bible_text = '{first_version}{data[text]}'.format(first_version=verse_text, data=data)
1093 raw_slides.append(bible_text.rstrip())834 raw_slides.append(bible_text.rstrip())
1094 bible_text = ''835 bible_text = ''
1095 # If we are 'Verse Per Line' then force a new line.836 # If we are 'Verse Per Line' then force a new line.
1096 elif self.settings.layout_style == LayoutStyle.VersePerLine:837 elif self.settings.layout_style == LayoutStyle.VersePerLine:
1097 bible_text = '{bible}{verse}{text}\n'.format(bible=bible_text, verse=verse_text, text=text)838 bible_text = '{bible} {verse}{data[text]}\n'.format(bible=bible_text, verse=verse_text, data=data)
1098 # We have to be 'Continuous'.839 # We have to be 'Continuous'.
1099 else:840 else:
1100 bible_text = '{bible} {verse}{text}\n'.format(bible=bible_text, verse=verse_text, text=text)841 bible_text = '{bible} {verse}{data[text]}'.format(bible=bible_text, verse=verse_text, data=data)
1101 bible_text = bible_text.strip(' ')842 bible_text = bible_text.strip(' ')
1102 if not old_item:843 old_chapter = data['chapter']
1103 start_item = bitem
1104 elif self.check_title(bitem, old_item):
1105 raw_title.append(self.format_title(start_item, old_item))
1106 start_item = bitem
1107 old_item = bitem
1108 old_chapter = chapter
1109 # Add footer844 # Add footer
1110 service_item.raw_footer.append(verses.format_verses())845 service_item.raw_footer.append(verses.format_verses())
1111 if second_bible:846 if data['second_bible']:
1112 verses.add_version(second_version, second_copyright, second_permissions)847 verses.add_version(data['second_version'], data['second_copyright'], data['second_permissions'])
1113 service_item.raw_footer.append(verses.format_versions())848 service_item.raw_footer.append(verses.format_versions())
1114 raw_title.append(self.format_title(start_item, bitem))
1115 # If there are no more items we check whether we have to add bible_text.849 # If there are no more items we check whether we have to add bible_text.
1116 if bible_text:850 if bible_text:
1117 raw_slides.append(bible_text.lstrip())851 raw_slides.append(bible_text.lstrip())
1118 # Service Item: Capabilities852 # Service Item: Capabilities
1119 if self.settings.layout_style == LayoutStyle.Continuous and not second_bible:853 if self.settings.layout_style == LayoutStyle.Continuous and not data['second_bible']:
1120 # Split the line but do not replace line breaks in renderer.854 # Split the line but do not replace line breaks in renderer.
1121 service_item.add_capability(ItemCapabilities.NoLineBreaks)855 service_item.add_capability(ItemCapabilities.NoLineBreaks)
1122 service_item.add_capability(ItemCapabilities.CanPreview)856 service_item.add_capability(ItemCapabilities.CanPreview)
@@ -1126,77 +860,12 @@
1126 # Service Item: Title860 # Service Item: Title
1127 service_item.title = '{verse} {version}'.format(verse=verses.format_verses(), version=verses.format_versions())861 service_item.title = '{verse} {version}'.format(verse=verses.format_verses(), version=verses.format_versions())
1128 # Service Item: Theme862 # Service Item: Theme
1129 if not self.settings.bible_theme:863 if self.settings.bible_theme:
1130 service_item.theme = None
1131 else:
1132 service_item.theme = self.settings.bible_theme864 service_item.theme = self.settings.bible_theme
1133 for slide in raw_slides:865 for slide in raw_slides:
1134 service_item.add_from_text(slide)866 service_item.add_from_text(slide)
1135 return True867 return True
1136868
1137 def format_title(self, start_bitem, old_bitem):
1138 """
1139 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
1140 want to add Genesis 1:1-6 as well as Daniel 2:14.
1141
1142 :param start_bitem: The first item of a range.
1143 :param old_bitem: The last item of a range.
1144 """
1145 verse_separator = get_reference_separator('sep_v_display')
1146 range_separator = get_reference_separator('sep_r_display')
1147 old_chapter = self._decode_qt_object(old_bitem, 'chapter')
1148 old_verse = self._decode_qt_object(old_bitem, 'verse')
1149 start_book = self._decode_qt_object(start_bitem, 'book')
1150 start_chapter = self._decode_qt_object(start_bitem, 'chapter')
1151 start_verse = self._decode_qt_object(start_bitem, 'verse')
1152 start_bible = self._decode_qt_object(start_bitem, 'bible')
1153 start_second_bible = self._decode_qt_object(start_bitem, 'second_bible')
1154 if start_second_bible:
1155 bibles = '{bible1}, {bible2}'.format(bible1=start_bible, bible2=start_second_bible)
1156 else:
1157 bibles = start_bible
1158 if start_chapter == old_chapter:
1159 if start_verse == old_verse:
1160 verse_range = start_chapter + verse_separator + start_verse
1161 else:
1162 verse_range = start_chapter + verse_separator + start_verse + range_separator + old_verse
1163 else:
1164 verse_range = start_chapter + verse_separator + start_verse + \
1165 range_separator + old_chapter + verse_separator + old_verse
1166 return '{book} {verse} ({bible})'.format(book=start_book, verse=verse_range, bible=bibles)
1167
1168 def check_title(self, bitem, old_bitem):
1169 """
1170 This method checks if we are at the end of an verse range. If that is the case, we return True, otherwise False.
1171 E. g. if we added Genesis 1:1-6, but the next verse is Daniel 2:14, we return True.
1172
1173 :param bitem: The item we are dealing with at the moment.
1174 :param old_bitem: The item we were previously dealing with.
1175 """
1176 # Get all the necessary meta data.
1177 book = self._decode_qt_object(bitem, 'book')
1178 chapter = int(self._decode_qt_object(bitem, 'chapter'))
1179 verse = int(self._decode_qt_object(bitem, 'verse'))
1180 bible = self._decode_qt_object(bitem, 'bible')
1181 second_bible = self._decode_qt_object(bitem, 'second_bible')
1182 old_book = self._decode_qt_object(old_bitem, 'book')
1183 old_chapter = int(self._decode_qt_object(old_bitem, 'chapter'))
1184 old_verse = int(self._decode_qt_object(old_bitem, 'verse'))
1185 old_bible = self._decode_qt_object(old_bitem, 'bible')
1186 old_second_bible = self._decode_qt_object(old_bitem, 'second_bible')
1187 if old_bible != bible or old_second_bible != second_bible or old_book != book:
1188 # The bible, second bible or book has changed.
1189 return True
1190 elif old_verse + 1 != verse and old_chapter == chapter:
1191 # We are still in the same chapter, but a verse has been skipped.
1192 return True
1193 elif old_chapter + 1 == chapter and (verse != 1 or old_verse !=
1194 self.plugin.manager.get_verse_count(old_bible, old_book, old_chapter)):
1195 # We are in the following chapter, but the last verse was not the last verse of the chapter or the current
1196 # verse is not the first one of the chapter.
1197 return True
1198 return False
1199
1200 def format_verse(self, old_chapter, chapter, verse):869 def format_verse(self, old_chapter, chapter, verse):
1201 """870 """
1202 Formats and returns the text, each verse starts with, for the given chapter and verse. The text is either871 Formats and returns the text, each verse starts with, for the given chapter and verse. The text is either
@@ -1207,28 +876,29 @@
1207 :param old_chapter: The previous verse's chapter number (int).876 :param old_chapter: The previous verse's chapter number (int).
1208 :param chapter: The chapter number (int).877 :param chapter: The chapter number (int).
1209 :param verse: The verse number (int).878 :param verse: The verse number (int).
879 :return: An empty or formatted string
1210 """880 """
1211 verse_separator = get_reference_separator('sep_v_display')
1212 if not self.settings.is_verse_number_visible:881 if not self.settings.is_verse_number_visible:
1213 return ''882 return ''
883 verse_separator = get_reference_separators()['verse']
1214 if not self.settings.show_new_chapters or old_chapter != chapter:884 if not self.settings.show_new_chapters or old_chapter != chapter:
1215 verse_text = str(chapter) + verse_separator + str(verse)885 verse_text = '{chapter}{sep}{verse}'.format(chapter=chapter, sep=verse_separator, verse=verse)
1216 else:886 else:
1217 verse_text = str(verse)887 verse_text = verse
1218 if self.settings.display_style == DisplayStyle.Round:888 bracket = {
1219 return '{{su}}({verse}){{/su}}&nbsp;'.format(verse=verse_text)889 DisplayStyle.NoBrackets: ('', ''),
1220 if self.settings.display_style == DisplayStyle.Curly:890 DisplayStyle.Round: ('(', ')'),
1221 return '{{su}}{{{verse}}}{{/su}}&nbsp;'.format(verse=verse_text)891 DisplayStyle.Curly: ('{', '}'),
1222 if self.settings.display_style == DisplayStyle.Square:892 DisplayStyle.Square: ('[', ']')
1223 return '{{su}}[{verse}]{{/su}}&nbsp;'.format(verse=verse_text)893 }[self.settings.display_style]
1224 return '{{su}}{verse}{{/su}}&nbsp;'.format(verse=verse_text)894 return '{{su}}{bracket[0]}{verse_text}{bracket[1]}{{/su}}&nbsp;'.format(verse_text=verse_text, bracket=bracket)
1225895
1226 def search(self, string, showError):896 def search(self, string, showError):
1227 """897 """
1228 Search for some Bible verses (by reference).898 Search for some Bible verses (by reference).
1229 """899 """
1230 bible = self.quickVersionComboBox.currentText()900 reference = self.plugin.manager.parse_ref(self.bible.name, string)
1231 search_results = self.plugin.manager.get_verses(bible, string, False, showError)901 search_results = self.plugin.manager.get_verses(self.bible.name, reference, showError)
1232 if search_results:902 if search_results:
1233 verse_text = ' '.join([verse.text for verse in search_results])903 verse_text = ' '.join([verse.text for verse in search_results])
1234 return [[string, verse_text]]904 return [[string, verse_text]]
@@ -1238,7 +908,6 @@
1238 """908 """
1239 Create a media item from an item id.909 Create a media item from an item id.
1240 """910 """
1241 bible = self.quickVersionComboBox.currentText()911 reference = self.plugin.manager.parse_ref(self.bible.name, item_id)
1242 search_results = self.plugin.manager.get_verses(bible, item_id, False)912 search_results = self.plugin.manager.get_verses(self.bible.name, reference, False)
1243 items = self.build_display_results(bible, '', search_results)913 return self.build_display_results(self.bible, None, search_results)
1244 return items
1245914
=== 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 2017-02-18 20:35:53 +0000
@@ -29,6 +29,7 @@
29 <file>image_new_group.png</file>29 <file>image_new_group.png</file>
30 </qresource>30 </qresource>
31 <qresource prefix="bibles">31 <qresource prefix="bibles">
32 <file>bibles_book_sort.png</file>
32 <file>bibles_search_combined.png</file>33 <file>bibles_search_combined.png</file>
33 <file>bibles_search_text.png</file>34 <file>bibles_search_text.png</file>
34 <file>bibles_search_reference.png</file>35 <file>bibles_search_reference.png</file>
3536
=== modified file 'tests/functional/openlp_core_ui_lib/test_listwidgetwithdnd.py'
--- tests/functional/openlp_core_ui_lib/test_listwidgetwithdnd.py 2016-12-31 11:01:36 +0000
+++ tests/functional/openlp_core_ui_lib/test_listwidgetwithdnd.py 2017-02-18 20:35:53 +0000
@@ -33,6 +33,37 @@
33 """33 """
34 Test the :class:`~openlp.core.lib.listwidgetwithdnd.ListWidgetWithDnD` class34 Test the :class:`~openlp.core.lib.listwidgetwithdnd.ListWidgetWithDnD` class
35 """35 """
36 def test_clear_locked(self):
37 """
38 Test the clear method the list is 'locked'
39 """
40 with patch('openlp.core.ui.lib.listwidgetwithdnd.QtWidgets.QListWidget.clear') as mocked_clear_super_method:
41 # GIVEN: An instance of ListWidgetWithDnD
42 widget = ListWidgetWithDnD()
43
44 # WHEN: The list is 'locked' and clear has been called
45 widget.locked = True
46 widget.clear()
47
48 # THEN: The super method should not have been called (i.e. The list not cleared)
49 self.assertFalse(mocked_clear_super_method.called)
50
51 def test_clear_overide_locked(self):
52 """
53 Test the clear method the list is 'locked', but clear is called with 'override_lock' set to True
54 """
55 with patch('openlp.core.ui.lib.listwidgetwithdnd.QtWidgets.QListWidget.clear') as mocked_clear_super_method:
56 # GIVEN: An instance of ListWidgetWithDnD
57 widget = ListWidgetWithDnD()
58
59 # WHEN: The list is 'locked' and clear has been called with override_lock se to True
60 widget.locked = True
61 widget.clear(override_lock=True)
62
63 # THEN: The super method should have been called (i.e. The list is cleared regardless whether it is locked
64 # or not)
65 mocked_clear_super_method.assert_called_once_with()
66
36 def test_clear(self):67 def test_clear(self):
37 """68 """
38 Test the clear method when called without any arguments.69 Test the clear method when called without any arguments.
3970
=== modified file 'tests/functional/openlp_plugins/bibles/test_lib.py'
--- tests/functional/openlp_plugins/bibles/test_lib.py 2016-12-31 11:01:36 +0000
+++ tests/functional/openlp_plugins/bibles/test_lib.py 2017-02-18 20:35:53 +0000
@@ -25,11 +25,12 @@
25from unittest import TestCase25from unittest import TestCase
2626
27from openlp.plugins.bibles import lib27from openlp.plugins.bibles import lib
28from openlp.plugins.bibles.lib import SearchResults28from openlp.plugins.bibles.lib import SearchResults, get_reference_match
29from tests.functional import patch29from tests.functional import MagicMock, patch
3030from tests.helpers.testmixin import TestMixin
3131
32class TestLib(TestCase):32
33class TestLib(TestCase, TestMixin):
33 """34 """
34 Test the functions in the :mod:`lib` module.35 Test the functions in the :mod:`lib` module.
35 """36 """
@@ -60,6 +61,142 @@
60 self.assertEqual(separators[key], value)61 self.assertEqual(separators[key], value)
61 mocked_update_reference_separators.assert_called_once_with()62 mocked_update_reference_separators.assert_called_once_with()
6263
64 def test_reference_matched_full(self):
65 """
66 Test that the 'full' regex parses bible verse references correctly.
67 """
68 # GIVEN: Some test data which contains different references to parse, with the expected results.
69 with patch('openlp.plugins.bibles.lib.Settings', return_value=MagicMock(**{'value.return_value': ''})):
70 # The following test data tests with 222 variants when using the default 'separators'
71 test_data = [
72 # Input reference, book name, chapter + verse reference
73 ('Psalm 23', 'Psalm', '23'),
74 ('Psalm. 23', 'Psalm', '23'),
75 ('Psalm 23{to}24', 'Psalm', '23-24'),
76 ('Psalm 23{verse}1{to}2', 'Psalm', '23:1-2'),
77 ('Psalm 23{verse}1{to}{end}', 'Psalm', '23:1-end'),
78 ('Psalm 23{verse}1{to}2{_and}5{to}6', 'Psalm', '23:1-2,5-6'),
79 ('Psalm 23{verse}1{to}2{_and}5{to}{end}', 'Psalm', '23:1-2,5-end'),
80 ('Psalm 23{verse}1{to}2{_and}24{verse}1{to}3', 'Psalm', '23:1-2,24:1-3'),
81 ('Psalm 23{verse}1{to}{end}{_and}24{verse}1{to}{end}', 'Psalm', '23:1-end,24:1-end'),
82 ('Psalm 23{verse}1{to}24{verse}1', 'Psalm', '23:1-24:1'),
83 ('Psalm 23{_and}24', 'Psalm', '23,24'),
84 ('1 John 23', '1 John', '23'),
85 ('1 John. 23', '1 John', '23'),
86 ('1 John 23{to}24', '1 John', '23-24'),
87 ('1 John 23{verse}1{to}2', '1 John', '23:1-2'),
88 ('1 John 23{verse}1{to}{end}', '1 John', '23:1-end'),
89 ('1 John 23{verse}1{to}2{_and}5{to}6', '1 John', '23:1-2,5-6'),
90 ('1 John 23{verse}1{to}2{_and}5{to}{end}', '1 John', '23:1-2,5-end'),
91 ('1 John 23{verse}1{to}2{_and}24{verse}1{to}3', '1 John', '23:1-2,24:1-3'),
92 ('1 John 23{verse}1{to}{end}{_and}24{verse}1{to}{end}', '1 John', '23:1-end,24:1-end'),
93 ('1 John 23{verse}1{to}24{verse}1', '1 John', '23:1-24:1'),
94 ('1 John 23{_and}24', '1 John', '23,24')]
95
96 full_reference_match = get_reference_match('full')
97 for reference_text, book_result, ranges_result in test_data:
98 to_separators = ['-', ' - ', 'to', ' to '] if '{to}' in reference_text else ['']
99 verse_separators = [':', ' : ', 'v', ' v ', 'V', ' V ', 'verse', ' verse ', 'verses', ' verses '] \
100 if '{verse}' in reference_text else ['']
101 and_separators = [',', ' , ', 'and', ' and '] if '{_and}' in reference_text else ['']
102 end_separators = ['end', ' end '] if '{end}' in reference_text else ['']
103
104 for to in to_separators:
105 for verse in verse_separators:
106 for _and in and_separators:
107 for end in end_separators:
108 reference_text = reference_text.format(to=to, verse=verse, _and=_and, end=end)
109
110 # WHEN: Attempting to parse the input string
111 match = full_reference_match.match(reference_text)
112
113 # THEN: A match should be returned, and the book and reference should match the
114 # expected result
115 self.assertIsNotNone(match, '{text} should provide a match'.format(text=reference_text))
116 self.assertEqual(book_result, match.group('book'),
117 '{text} does not provide the expected result for the book group.'
118 .format(text=reference_text))
119 self.assertEqual(ranges_result, match.group('ranges'),
120 '{text} does not provide the expected result for the ranges group.'
121 .format(text=reference_text))
122
123 def test_reference_matched_range(self):
124 """
125 Test that the 'range' regex parses bible verse references correctly.
126 Note: This test takes in to account that the regex does not work quite as expected!
127 see https://bugs.launchpad.net/openlp/+bug/1638620
128 """
129 # GIVEN: Some test data which contains different references to parse, with the expected results.
130 with patch('openlp.plugins.bibles.lib.Settings', return_value=MagicMock(**{'value.return_value': ''})):
131 # The following test data tests with 45 variants when using the default 'separators'
132 test_data = [
133 ('23', None, '23', None, None, None),
134 ('23{to}24', None, '23', '-24', None, '24'),
135 ('23{verse}1{to}2', '23', '1', '-2', None, '2'),
136 ('23{verse}1{to}{end}', '23', '1', '-end', None, None),
137 ('23{verse}1{to}24{verse}1', '23', '1', '-24:1', '24', '1')]
138 full_reference_match = get_reference_match('range')
139 for reference_text, from_chapter, from_verse, range_to, to_chapter, to_verse in test_data:
140 to_separators = ['-', ' - ', 'to', ' to '] if '{to}' in reference_text else ['']
141 verse_separators = [':', ' : ', 'v', ' v ', 'V', ' V ', 'verse', ' verse ', 'verses', ' verses '] \
142 if '{verse}' in reference_text else ['']
143 and_separators = [',', ' , ', 'and', ' and '] if '{_and}' in reference_text else ['']
144 end_separators = ['end', ' end '] if '{end}' in reference_text else ['']
145
146 for to in to_separators:
147 for verse in verse_separators:
148 for _and in and_separators:
149 for end in end_separators:
150 reference_text = reference_text.format(to=to, verse=verse, _and=_and, end=end)
151
152 # WHEN: Attempting to parse the input string
153 match = full_reference_match.match(reference_text)
154
155 # THEN: A match should be returned, and the to/from chapter/verses should match as
156 # expected
157 self.assertIsNotNone(match, '{text} should provide a match'.format(text=reference_text))
158 self.assertEqual(match.group('from_chapter'), from_chapter)
159 self.assertEqual(match.group('from_verse'), from_verse)
160 self.assertEqual(match.group('range_to'), range_to)
161 self.assertEqual(match.group('to_chapter'), to_chapter)
162 self.assertEqual(match.group('to_verse'), to_verse)
163
164 def test_reference_matched_range_separator(self):
165 # GIVEN: Some test data which contains different references to parse, with the expected results.
166 with patch('openlp.plugins.bibles.lib.Settings', return_value=MagicMock(**{'value.return_value': ''})):
167 # The following test data tests with 111 variants when using the default 'separators'
168 # The regex for handling ranges is a bit screwy, see https://bugs.launchpad.net/openlp/+bug/1638620
169 test_data = [
170 ('23', ['23']),
171 ('23{to}24', ['23-24']),
172 ('23{verse}1{to}2', ['23:1-2']),
173 ('23{verse}1{to}{end}', ['23:1-end']),
174 ('23{verse}1{to}2{_and}5{to}6', ['23:1-2', '5-6']),
175 ('23{verse}1{to}2{_and}5{to}{end}', ['23:1-2', '5-end']),
176 ('23{verse}1{to}2{_and}24{verse}1{to}3', ['23:1-2', '24:1-3']),
177 ('23{verse}1{to}{end}{_and}24{verse}1{to}{end}', ['23:1-end', '24:1-end']),
178 ('23{verse}1{to}24{verse}1', ['23:1-24:1']),
179 ('23,24', ['23', '24'])]
180 full_reference_match = get_reference_match('range_separator')
181 for reference_text, ranges in test_data:
182 to_separators = ['-', ' - ', 'to', ' to '] if '{to}' in reference_text else ['']
183 verse_separators = [':', ' : ', 'v', ' v ', 'V', ' V ', 'verse', ' verse ', 'verses', ' verses '] \
184 if '{verse}' in reference_text else ['']
185 and_separators = [',', ' , ', 'and', ' and '] if '{_and}' in reference_text else ['']
186 end_separators = ['end', ' end '] if '{end}' in reference_text else ['']
187
188 for to in to_separators:
189 for verse in verse_separators:
190 for _and in and_separators:
191 for end in end_separators:
192 reference_text = reference_text.format(to=to, verse=verse, _and=_and, end=end)
193
194 # WHEN: Attempting to parse the input string
195 references = full_reference_match.split(reference_text)
196
197 # THEN: The list of references should be as the expected results
198 self.assertEqual(references, ranges)
199
63 def test_search_results_creation(self):200 def test_search_results_creation(self):
64 """201 """
65 Test the creation and construction of the SearchResults class202 Test the creation and construction of the SearchResults class
66203
=== modified file 'tests/functional/openlp_plugins/bibles/test_mediaitem.py'
--- tests/functional/openlp_plugins/bibles/test_mediaitem.py 2016-12-31 11:01:36 +0000
+++ tests/functional/openlp_plugins/bibles/test_mediaitem.py 2017-02-18 20:35:53 +0000
@@ -23,10 +23,85 @@
23This module contains tests for the lib submodule of the Presentations plugin.23This module contains tests for the lib submodule of the Presentations plugin.
24"""24"""
25from unittest import TestCase25from unittest import TestCase
26from unittest.mock import MagicMock, call, patch
27
28from PyQt5 import QtCore, QtWidgets
29
30from tests.helpers.testmixin import TestMixin
31
26from openlp.core.common import Registry32from openlp.core.common import Registry
27from openlp.plugins.bibles.lib.mediaitem import BibleMediaItem33from openlp.core.lib import MediaManagerItem
28from tests.functional import MagicMock, patch34from openlp.plugins.bibles.lib.mediaitem import BibleMediaItem, BibleSearch, get_reference_separators, VALID_TEXT_SEARCH
29from tests.helpers.testmixin import TestMixin35
36
37class TestBibleMediaItemModulefunctions(TestCase):
38 """
39 Test the module functions in :mod:`openlp.plugins.bibles.lib.mediaitem`
40 """
41
42 def test_valid_text_search(self):
43 """
44 Test the compiled VALID_TEXT_SEARCH regex expression
45 """
46 # GIVEN: Some test data and some expected results
47 test_data = [('a a a', None), ('a ab a', None), ('a abc a', ((2, 5),)), ('aa 123 aa', ((3, 6),))]
48 for data, expected_result in test_data:
49
50 # WHEN: Calling search on the compiled regex expression
51 result = VALID_TEXT_SEARCH.search(data)
52
53 # THEN: The expected result should be returned
54 if expected_result is None:
55 self.assertIsNone(result, expected_result)
56 else:
57 self.assertEqual(result.regs, expected_result)
58
59 def test_get_reference_separators(self):
60 """
61 Test the module function get_reference_separators
62 """
63 # GIVEN: A mocked get_reference_separator from the :mod:`openlp.plugins.bibles.lib` module
64 with patch('openlp.plugins.bibles.lib.mediaitem.get_reference_separator') as mocked_get_reference_separator:
65
66 # WHEN: Calling get_reference_separators
67 result = get_reference_separators()
68
69 # THEN: The result should contain the 'verse', 'range', 'list' keys and get_reference_separator should have
70 # been called with the expected values.
71 self.assertTrue(all(key in result for key in ('verse', 'range', 'list')))
72 mocked_get_reference_separator.assert_has_calls(
73 [call('sep_v_display'), call('sep_r_display'), call('sep_l_display')])
74
75 def test_bible_search_enum(self):
76 """
77 Test that the :class:`BibleSearch` class contains the expected enumerations
78 """
79 # GIVEN: The BibleSearch class
80 # WHEN: Testing its attributes
81 # THEN: The BibleSearch class should have the following enumrations
82 self.assertTrue(hasattr(BibleSearch, 'Combined'))
83 self.assertTrue(hasattr(BibleSearch, 'Reference'))
84 self.assertTrue(hasattr(BibleSearch, 'Text'))
85
86 def test_bible_media_item_subclass(self):
87 """
88 Test that the :class:`BibleMediaItem` class is a subclass of the :class:`MediaManagerItem` class
89 """
90 # GIVEN: The :class:`BibleMediaItem`
91 # WHEN: Checking if it is a subclass of MediaManagerItem
92 # THEN: BibleMediaItem should be a subclass of MediaManagerItem
93 self.assertTrue(issubclass(BibleMediaItem, MediaManagerItem))
94
95 def test_bible_media_item_signals(self):
96 """
97 Test that the :class:`BibleMediaItem` class has the expected signals
98 """
99 # GIVEN: The :class:`BibleMediaItem`
100 # THEN: The :class:`BibleMediaItem` should contain the following pyqtSignal's
101 self.assertTrue(hasattr(BibleMediaItem, 'bibles_go_live'))
102 self.assertTrue(hasattr(BibleMediaItem, 'bibles_add_to_service'))
103 self.assertTrue(isinstance(BibleMediaItem.bibles_go_live, QtCore.pyqtSignal))
104 self.assertTrue(isinstance(BibleMediaItem.bibles_add_to_service, QtCore.pyqtSignal))
30105
31106
32class TestMediaItem(TestCase, TestMixin):107class TestMediaItem(TestCase, TestMixin):
@@ -38,189 +113,1315 @@
38 """113 """
39 Set up the components need for all tests.114 Set up the components need for all tests.
40 """115 """
41 with patch('openlp.plugins.bibles.lib.mediaitem.MediaManagerItem._setup'),\116 log_patcher = patch('openlp.plugins.bibles.lib.mediaitem.log')
42 patch('openlp.plugins.bibles.lib.mediaitem.BibleMediaItem.setup_item'):117 self.addCleanup(log_patcher.stop)
43 self.media_item = BibleMediaItem(None, MagicMock())118 self.mocked_log = log_patcher.start()
44 self.setup_application()119
120 qtimer_patcher = patch('openlp.plugins.bibles.lib.mediaitem.QtCore.QTimer')
121 self.addCleanup(qtimer_patcher.stop)
122 self.mocked_qtimer = qtimer_patcher.start()
123
124 self.mocked_settings_instance = MagicMock()
125 self.mocked_settings_instance.value.side_effect = lambda key: self.setting_values[key]
126 settings_patcher = patch(
127 'openlp.plugins.bibles.lib.mediaitem.Settings', return_value=self.mocked_settings_instance)
128 self.addCleanup(settings_patcher.stop)
129 self.mocked_settings = settings_patcher.start()
130
131 Registry.create()
132
133 # self.setup_application()
134 self.mocked_application = MagicMock()
135 Registry().register('application', self.mocked_application)
45 self.mocked_main_window = MagicMock()136 self.mocked_main_window = MagicMock()
46 Registry.create()
47 Registry().register('main_window', self.mocked_main_window)137 Registry().register('main_window', self.mocked_main_window)
48138
49 def test_display_results_no_results(self):139 self.mocked_plugin = MagicMock()
50 """140 with patch('openlp.plugins.bibles.lib.mediaitem.build_icon'), \
51 Test the display_results method when called with a single bible, returning no results141 patch('openlp.plugins.bibles.lib.mediaitem.MediaManagerItem._setup'), \
52 """142 patch('openlp.plugins.bibles.lib.mediaitem.BibleMediaItem.setup_item'):
53143 self.media_item = BibleMediaItem(None, self.mocked_plugin)
54 # GIVEN: A mocked build_display_results which returns an empty list144
55 with patch('openlp.plugins.bibles.lib.BibleMediaItem.build_display_results', **{'return_value': []}) \145 self.media_item.settings_section = 'bibles'
56 as mocked_build_display_results:146
57 mocked_list_view = MagicMock()147 self.mocked_book_1 = MagicMock(**{'get_name.return_value': 'Book 1', 'book_reference_id': 1})
58 self.media_item.search_results = 'results'148 self.mocked_book_2 = MagicMock(**{'get_name.return_value': 'Book 2', 'book_reference_id': 2})
59 self.media_item.list_view = mocked_list_view149 self.mocked_book_3 = MagicMock(**{'get_name.return_value': 'Book 3', 'book_reference_id': 3})
60150 self.mocked_book_4 = MagicMock(**{'get_name.return_value': 'Book 4', 'book_reference_id': 4})
61 # WHEN: Calling display_results with a single bible version151
62 self.media_item.display_results('NIV')152 self.book_list_1 = [self.mocked_book_1, self.mocked_book_2, self.mocked_book_3]
63153 self.book_list_2 = [self.mocked_book_2, self.mocked_book_3, self.mocked_book_4]
64 # THEN: No items should be added to the list, and select all should have been called.154 self.mocked_bible_1 = MagicMock(**{'get_books.return_value': self.book_list_1})
65 mocked_build_display_results.assert_called_once_with('NIV', '', 'results')155 self.mocked_bible_1.name = 'Bible 1'
66 self.assertFalse(mocked_list_view.addItem.called)156 self.mocked_bible_2 = MagicMock(**{'get_books.return_value': self.book_list_2})
67 mocked_list_view.selectAll.assert_called_once_with()157 self.mocked_bible_2.name = 'Bible 2'
68 self.assertEqual(self.media_item.search_results, {})158
69 self.assertEqual(self.media_item.second_search_results, {})159 def test_media_item_instance(self):
70160 """
71 def test_display_results_two_bibles_no_results(self):161 When creating an instance of C test that it is also an instance of
72 """162 :class:`MediaManagerItem`
73 Test the display_results method when called with two bibles, returning no results163 """
74 """164 # GIVEN: An instance of :class:`BibleMediaItem`
75165 # WEHN: Checking its class
76 # GIVEN: A mocked build_display_results which returns an empty list166 # THEN: It should be a subclass of :class:`MediaManagerItem`
77 with patch('openlp.plugins.bibles.lib.BibleMediaItem.build_display_results', **{'return_value': []}) \167 self.assertTrue(isinstance(self.media_item, MediaManagerItem))
78 as mocked_build_display_results:168
79 mocked_list_view = MagicMock()169 def test_steup_item(self):
80 self.media_item.search_results = 'results'170 """
81 self.media_item.list_view = mocked_list_view171 Test the setup_item method
82172 """
83 # WHEN: Calling display_results with two single bible versions173 # Could have tested the connection of the custom signals, however they're class vairables, and I could not find
84 self.media_item.display_results('NIV', 'GNB')174 # a way to properly test them.
85175
86 # THEN: build_display_results should have been called with two bible versions.176 # GIVEN: A mocked Registry.register_function method and an instance of BibleMediaItem
87 # No items should be added to the list, and select all should have been called.177 with patch.object(Registry(), 'register_function') as mocked_register_function:
88 mocked_build_display_results.assert_called_once_with('NIV', 'GNB', 'results')178
89 self.assertFalse(mocked_list_view.addItem.called)179 # WHEN: Calling setup_itme
90 mocked_list_view.selectAll.assert_called_once_with()180 self.media_item.setup_item()
91 self.assertEqual(self.media_item.search_results, {})181
92 self.assertEqual(self.media_item.second_search_results, {})182 # THEN: Registry.register_function method should have been called with the reload_bibles method
93183 mocked_register_function.assert_called_once_with('bibles_load_list', self.media_item.reload_bibles)
94 def test_display_results_returns_lots_of_results(self):
95 """
96 Test the display_results method a large number of results (> 100) are returned
97 """
98
99 # GIVEN: A mocked build_display_results which returns a large list of results
100 long_list = list(range(100))
101 with patch('openlp.plugins.bibles.lib.BibleMediaItem.build_display_results', **{'return_value': long_list})\
102 as mocked_build_display_results:
103 mocked_list_view = MagicMock()
104 self.media_item.search_results = 'results'
105 self.media_item.list_view = mocked_list_view
106
107 # WHEN: Calling display_results
108 self.media_item.display_results('NIV', 'GNB')
109
110 # THEN: addItem should have been called 100 times, and the lsit items should not be selected.
111 mocked_build_display_results.assert_called_once_with('NIV', 'GNB', 'results')
112 self.assertEqual(mocked_list_view.addItem.call_count, 100)
113 mocked_list_view.selectAll.assert_called_once_with()
114 self.assertEqual(self.media_item.search_results, {})
115 self.assertEqual(self.media_item.second_search_results, {})
116184
117 def test_required_icons(self):185 def test_required_icons(self):
118 """186 """
119 Test that all the required icons are set properly.187 Test that all the required icons are set properly.
120 """188 """
121 # GIVEN: Mocked icons that need to be called.189 # GIVEN: An instance of :class:`MediaManagerItem`
122 self.media_item.has_import_icon = MagicMock()190 # WHEN: required_icons is called
123 self.media_item.has_new_icon = MagicMock()
124 self.media_item.has_edit_icon = MagicMock()
125 self.media_item.has_delete_icon = MagicMock()
126 self.media_item.add_to_service_item = MagicMock()
127
128 # WHEN: self.media_item.required_icons is called
129 self.media_item.required_icons()191 self.media_item.required_icons()
130192
131 # THEN: On windows it should return True, on other platforms False193 # THEN: The correct icons should be set
132 self.assertTrue(self.media_item.has_import_icon, 'Check that the icon is as True.')194 self.assertTrue(self.media_item.has_import_icon, 'Check that the icon is as True.')
133 self.assertFalse(self.media_item.has_new_icon, 'Check that the icon is called as False.')195 self.assertFalse(self.media_item.has_new_icon, 'Check that the icon is called as False.')
134 self.assertTrue(self.media_item.has_edit_icon, 'Check that the icon is called as True.')196 self.assertTrue(self.media_item.has_edit_icon, 'Check that the icon is called as True.')
135 self.assertTrue(self.media_item.has_delete_icon, 'Check that the icon is called as True.')197 self.assertTrue(self.media_item.has_delete_icon, 'Check that the icon is called as True.')
136 self.assertFalse(self.media_item.add_to_service_item, 'Check that the icon is called as False')198 self.assertFalse(self.media_item.add_to_service_item, 'Check that the icon is called as False')
137199
138 def test_on_quick_search_button_general(self):200 # TODO: Test add_end_header_bar
139 """201 # TODO: Test setupUi
140 Test that general things, which should be called on all Quick searches are called.202
141 """203 def test_on_focus_search_tab_visible(self):
142204 """
143 # GIVEN: self.application as self.app, all the required functions205 Test the correct widget gets focus when the BibleMediaItem receives focus
144 Registry.create()206 """
145 Registry().register('application', self.app)207 # GIVEN: An instance of :class:`MediaManagerItem` and a mocked out search_tab and search_edit
146 self.media_item.quickSearchButton = MagicMock()208 self.media_item.search_tab = MagicMock(**{'isVisible.return_value': True})
147 self.app.process_events = MagicMock()209 self.media_item.search_edit = MagicMock()
148 self.media_item.quickVersionComboBox = MagicMock()210
149 self.media_item.quickVersionComboBox.currentText = MagicMock()211 # WHEN: Calling on_focus
150 self.media_item.quickSecondComboBox = MagicMock()212 self.media_item.on_focus()
151 self.media_item.quickSecondComboBox.currentText = MagicMock()213
152 self.media_item.quick_search_edit = MagicMock()214 # THEN: setFocus and selectAll should have been called on search_edit
153 self.media_item.quick_search_edit.text = MagicMock()215 self.assertEqual(self.media_item.search_edit.mock_calls, [call.setFocus(), call.selectAll()])
154 self.media_item.quickLockButton = MagicMock()216
155 self.media_item.list_view = MagicMock()217 def test_on_focus_search_tab_not_visible(self):
156 self.media_item.search_results = MagicMock()218 """
157 self.media_item.display_results = MagicMock()219 Test the correct widget gets focus when the BibleMediaItem receives focus
158 self.app.set_normal_cursor = MagicMock()220 """
159221 # GIVEN: An instance of :class:`MediaManagerItem` and a mocked out search_tab and select_book_combo_box
160 # WHEN: on_quick_search_button is called222 self.media_item.search_tab = MagicMock(**{'isVisible.return_value': False})
161 self.media_item.on_quick_search_button()223 self.media_item.select_book_combo_box = MagicMock()
162224
163 # THEN: Search should had been started and finalized properly225 # WHEN: Calling on_focus
164 self.assertEqual(1, self.app.process_events.call_count, 'Normal cursor should had been called once')226 self.media_item.on_focus()
165 self.assertEqual(1, self.media_item.quickVersionComboBox.currentText.call_count, 'Should had been called once')227
166 self.assertEqual(1, self.media_item.quickSecondComboBox.currentText.call_count, 'Should had been called once')228 # THEN: setFocus should have been called on select_book_combo_box
167 self.assertEqual(1, self.media_item.quick_search_edit.text.call_count, 'Text edit Should had been called once')229 self.assertTrue(self.media_item.select_book_combo_box.setFocus.called)
168 self.assertEqual(1, self.media_item.quickLockButton.isChecked.call_count, 'Lock Should had been called once')230
169 self.assertEqual(1, self.media_item.display_results.call_count, 'Display results Should had been called once')231 def test_config_update_show_second_bible(self):
170 self.assertEqual(2, self.media_item.quickSearchButton.setEnabled.call_count, 'Disable and Enable the button')232 """
171 self.assertEqual(1, self.app.set_normal_cursor.call_count, 'Normal cursor should had been called once')233 Test the config update method
234 """
235 # GIVEN: An instance of :class:`MediaManagerItem` and mocked out settings class with known values
236 self.setting_values = {'bibles/second bibles': True}
237 self.media_item.general_bible_layout = MagicMock()
238 self.media_item.second_combo_box = MagicMock()
239
240 # WHEN: Calling config_update()
241 self.media_item.config_update()
242
243 # THEN: second_combo_box() should be set visible
244 self.media_item.second_combo_box.setVisible.assert_called_once_with(True)
245
246 def test_config_update_hide_second_bible(self):
247 """
248 Test the config update method
249 """
250 # GIVEN: An instance of :class:`MediaManagerItem` and mocked out settings class with known values
251 self.setting_values = {'bibles/second bibles': False}
252 self.media_item.general_bible_layout = MagicMock()
253 self.media_item.second_combo_box = MagicMock()
254
255 # WHEN: Calling config_update()
256 self.media_item.config_update()
257
258 # THEN: second_combo_box() should hidden
259 self.media_item.second_combo_box.setVisible.assert_called_once_with(False)
260
261 def test_initalise(self):
262 """
263 Test the initalise method
264 """
265 # GIVEN: An instance of :class:`MediaManagerItem` and mocked out settings class with known values
266 self.setting_values = {'bibles/reset to combined quick search': False}
267 with patch.object(self.media_item, 'populate_bible_combo_boxes'), \
268 patch.object(self.media_item, 'config_update'):
269 self.media_item.search_edit = MagicMock()
270
271 # WHEN: Calling initialise()
272 self.media_item.initialise()
273
274 # THEN: The search_edit search types should have been set.
275 self.assertTrue(self.media_item.search_edit.set_search_types.called)
276 self.assertFalse(self.media_item.search_edit.set_current_search_type.called)
277
278 def test_initalise_reset_search_type(self):
279 """
280 Test the initalise method
281 """
282 # GIVEN: An instance of :class:`MediaManagerItem` and mocked out settings class with known values
283 self.setting_values = {'bibles/reset to combined quick search': True}
284 with patch.object(self.media_item, 'populate_bible_combo_boxes'), \
285 patch.object(self.media_item, 'config_update'):
286 self.media_item.search_edit = MagicMock()
287
288 # WHEN: Calling initialise()
289 self.media_item.initialise()
290
291 # THEN: The search_edit search types should have been set and that the current search type should be set to
292 # 'Combined'
293 self.assertTrue(self.media_item.search_edit.set_search_types.called)
294 self.media_item.search_edit.set_current_search_type.assert_called_once_with(BibleSearch.Combined)
295
296 def test_populate_bible_combo_boxes(self):
297 """
298 Test populate_bible_combo_boxes method
299 """
300 # GIVEN: An instance of :class:`MediaManagerItem` and mocked out settings class with known values
301 bible_1 = MagicMock()
302 bible_2 = MagicMock()
303 bible_3 = MagicMock()
304 self.setting_values = {'bibles/primary bible': bible_2}
305 self.media_item.version_combo_box = MagicMock()
306 self.media_item.second_combo_box = MagicMock()
307 self.mocked_plugin.manager.get_bibles.return_value = \
308 {'Bible 2': bible_2, 'Bible 1': bible_1, 'Bible 3': bible_3}
309 with patch('openlp.plugins.bibles.lib.mediaitem.get_locale_key', side_effect=lambda x: x), \
310 patch('openlp.plugins.bibles.lib.mediaitem.find_and_set_in_combo_box'):
311
312 # WHEN: Calling populate_bible_combo_boxes
313 self.media_item.populate_bible_combo_boxes()
314
315 # THEN: The bible combo boxes should be filled with the bible names and data, in a sorted order.
316 self.media_item.version_combo_box.addItem.assert_has_calls(
317 [call('Bible 1', bible_1), call('Bible 2', bible_2), call('Bible 3', bible_3)])
318 self.media_item.second_combo_box.addItem.assert_has_calls(
319 [call('', None), call('Bible 1', bible_1), call('Bible 2', bible_2), call('Bible 3', bible_3)])
320
321 def test_reload_bibles(self):
322 """
323 Test reload_bibles
324 """
325 # GIVEN: An instance of :class:`MediaManagerItem` and mocked out settings class with known values
326 with patch.object(self.media_item, 'populate_bible_combo_boxes') as mocked_populate_bible_combo_boxes:
327 # WHEN: Calling reload_bibles()
328 self.media_item.reload_bibles()
329
330 # THEN: The manager reload_bibles method should have been called and the bible combo boxes updated
331 self.mocked_plugin.manager.reload_bibles.assert_called_once_with()
332 mocked_populate_bible_combo_boxes.assert_called_once_with()
333
334 def test_get_common_books_no_second_book(self):
335 """
336 Test get_common_books when called with out a second bible
337 """
338 # GIVEN: An instance of :class:`MediaManagerItem` and a mocked first bible
339 # WHEN: Calling get_common_books with only one bible
340 result = self.media_item.get_common_books(self.mocked_bible_1)
341
342 # THEN: The book of the bible should be returned
343 self.assertEqual(result, self.book_list_1)
344
345 def test_get_common_books_second_book(self):
346 """
347 Test get_common_books when called with a second bible
348 """
349 # GIVEN: An instance of :class:`MediaManagerItem` and two mocked bibles with differing books
350 # WHEN: Calling get_common_books with two bibles
351 result = self.media_item.get_common_books(self.mocked_bible_1, self.mocked_bible_2)
352
353 # THEN: Only the books contained in both bibles should be returned
354 self.assertEqual(result, [self.mocked_book_2, self.mocked_book_3])
355
356 def test_initialise_advanced_bible_no_bible(self):
357 """
358 Test initialise_advanced_bible when there is no main bible
359 """
360 # GIVEN: An instance of :class:`MediaManagerItem`
361 self.media_item.select_book_combo_box = MagicMock()
362 with patch.object(self.media_item, 'get_common_books') as mocked_get_common_books:
363
364 # WHEN: Calling initialise_advanced_bible() when there is no main bible
365 self.media_item.bible = None
366 result = self.media_item.initialise_advanced_bible()
367
368 # THEN: initialise_advanced_bible should return with put calling get_common_books
369 self.assertIsNone(result)
370 mocked_get_common_books.assert_not_called()
371
372 def test_initialise_advanced_bible_add_books_with_last_id_found(self):
373 """
374 Test initialise_advanced_bible when the last_id argument is supplied and it is found in the list
375 """
376 # GIVEN: An instance of :class:`MediaManagerItem` and a mocked_book_combo_box which simulates data being found
377 # in the list
378 self.media_item.select_book_combo_box = MagicMock(**{'findData.return_value': 2})
379 with patch.object(self.media_item, 'get_common_books', return_value=self.book_list_1), \
380 patch.object(self.media_item, 'on_advanced_book_combo_box'):
381
382 # WHEN: Calling initialise_advanced_bible() with the last_id argument set
383 self.media_item.bible = MagicMock()
384 self.media_item.initialise_advanced_bible(10)
385
386 # THEN: The books should be added to the combo box, and the chosen book should be reselected
387 self.media_item.select_book_combo_box.addItem.assert_has_calls(
388 [call('Book 1', 1), call('Book 2', 2), call('Book 3', 3)])
389 self.media_item.select_book_combo_box.setCurrentIndex.assert_called_once_with(2)
390
391 def test_initialise_advanced_bible_add_books_with_last_id_not_found(self):
392 """
393 Test initialise_advanced_bible when the last_id argument is supplied and it is not found in the list
394 """
395 # GIVEN: An instance of :class:`MediaManagerItem` and a mocked_book_combo_box which simulates data not being
396 # found in the list
397 self.media_item.select_book_combo_box = MagicMock(**{'findData.return_value': -1})
398 with patch.object(self.media_item, 'get_common_books', return_value=self.book_list_1), \
399 patch.object(self.media_item, 'on_advanced_book_combo_box'):
400
401 # WHEN: Calling initialise_advanced_bible() with the last_id argument set
402 self.media_item.bible = MagicMock()
403 self.media_item.initialise_advanced_bible(10)
404
405 # THEN: The books should be added to the combo box, and the first book should be selected
406 self.media_item.select_book_combo_box.addItem.assert_has_calls(
407 [call('Book 1', 1), call('Book 2', 2), call('Book 3', 3)])
408 self.media_item.select_book_combo_box.setCurrentIndex.assert_called_once_with(0)
409
410 def test_update_auto_completer_search_no_bible(self):
411 """
412 Test update_auto_completer when there is no main bible selected and the search_edit type is
413 'BibleSearch.Reference'
414 """
415 # GIVEN: An instance of :class:`MediaManagerItem` and a mocked search_edit
416 mocked_search_edit = MagicMock(**{'current_search_type.return_value': BibleSearch.Reference})
417 self.media_item.search_edit = mocked_search_edit
418 self.media_item.bible = None
419 with patch.object(self.media_item, 'get_common_books') as mocked_get_common_books, \
420 patch('openlp.plugins.bibles.lib.mediaitem.set_case_insensitive_completer') \
421 as mocked_set_case_insensitive_completer:
422
423 # WHEN: Calling update_auto_completer
424 self.media_item.update_auto_completer()
425
426 # THEN: get_common_books should not have been called. set_case_insensitive_completer should have been called
427 # with an empty list
428 mocked_get_common_books.assert_not_called()
429 mocked_set_case_insensitive_completer.assert_called_once_with([], mocked_search_edit)
430
431 def test_update_auto_completer_search_reference_type(self):
432 """
433 Test update_auto_completer when a main bible is selected and the search_edit type is 'BibleSearch.Reference'
434 """
435 # GIVEN: An instance of :class:`MediaManagerItem` and a mocked search_edit
436 mocked_search_edit = MagicMock(**{'current_search_type.return_value': BibleSearch.Reference})
437 self.media_item.search_edit = mocked_search_edit
438 self.media_item.bible = MagicMock()
439 with patch.object(self.media_item, 'get_common_books', return_value=self.book_list_1), \
440 patch('openlp.plugins.bibles.lib.mediaitem.get_locale_key', side_effect=lambda x: x), \
441 patch('openlp.plugins.bibles.lib.mediaitem.set_case_insensitive_completer') \
442 as mocked_set_case_insensitive_completer:
443
444 # WHEN: Calling update_auto_completer
445 self.media_item.update_auto_completer()
446
447 # THEN: set_case_insensitive_completer should have been called with the names of the books in order
448 mocked_set_case_insensitive_completer.assert_called_once_with(
449 ['Book 1', 'Book 2', 'Book 3'], mocked_search_edit)
450
451 def test_update_auto_completer_search_combined_type(self):
452 """
453 Test update_auto_completer when a main bible is selected and the search_edit type is 'BibleSearch.Combined'
454 """
455 # GIVEN: An instance of :class:`MediaManagerItem` and a mocked search_edit
456 mocked_search_edit = MagicMock(**{'current_search_type.return_value': BibleSearch.Combined})
457 self.media_item.search_edit = mocked_search_edit
458 self.media_item.bible = MagicMock()
459 with patch.object(self.media_item, 'get_common_books', return_value=self.book_list_1), \
460 patch('openlp.plugins.bibles.lib.mediaitem.get_locale_key', side_effect=lambda x: x), \
461 patch('openlp.plugins.bibles.lib.mediaitem.set_case_insensitive_completer') \
462 as mocked_set_case_insensitive_completer:
463
464 # WHEN: Calling update_auto_completer
465 self.media_item.update_auto_completer()
466
467 # THEN: set_case_insensitive_completer should have been called with the names of the books in order
468 mocked_set_case_insensitive_completer.assert_called_once_with(
469 ['Book 1', 'Book 2', 'Book 3'], mocked_search_edit)
470
471 def test_on_import_click_no_import_wizzard_attr(self):
472 """
473 Test on_import_click when media_item does not have the `import_wizard` attribute. And the wizard was canceled.
474 """
475 # GIVEN: An instance of :class:`MediaManagerItem` and a mocked BibleImportForm
476 mocked_bible_import_form_instance = MagicMock(**{'exec.return_value': False})
477 with patch('openlp.plugins.bibles.lib.mediaitem.BibleImportForm',
478 return_value=mocked_bible_import_form_instance) as mocked_bible_import_form, \
479 patch.object(self.media_item, 'reload_bibles') as mocked_reload_bibles:
480
481 # WHEN: Calling on_import_click
482 self.media_item.on_import_click()
483
484 # THEN: BibleImport wizard should have been instianted and reload_bibles should not have been called
485 self.assertTrue(mocked_bible_import_form.called)
486 self.assertFalse(mocked_reload_bibles.called)
487
488 def test_on_import_click_wizzard_not_canceled(self):
489 """
490 Test on_import_click when the media item has the import_wizzard attr set and wizard completes sucessfully.
491 """
492 # GIVEN: An instance of :class:`MediaManagerItem` and a mocked import_wizard
493 mocked_import_wizard = MagicMock(**{'exec.return_value': True})
494 self.media_item.import_wizard = mocked_import_wizard
495
496 with patch.object(self.media_item, 'reload_bibles') as mocked_reload_bibles:
497
498 # WHEN: Calling on_import_click
499 self.media_item.on_import_click()
500
501 # THEN: BibleImport wizard should have been instianted and reload_bibles should not have been called
502 self.assertFalse(mocked_import_wizard.called)
503 self.assertTrue(mocked_reload_bibles.called)
504
505 def test_on_edit_click_no_bible(self):
506 """
507 Test on_edit_click when there is no main bible selected
508 """
509 # GIVEN: An instance of :class:`MediaManagerItem`
510 with patch('openlp.plugins.bibles.lib.mediaitem.EditBibleForm') as mocked_edit_bible_form:
511
512 # WHEN: A main bible is not selected and on_edit_click is called
513 self.media_item.bible = None
514 self.media_item.on_edit_click()
515
516 # THEN: EditBibleForm should not have been instianted
517 self.assertFalse(mocked_edit_bible_form.called)
518
519 def test_on_edit_click_user_cancel_edit_form(self):
520 """
521 Test on_edit_click when the user cancels the EditBibleForm
522 """
523 # GIVEN: An instance of :class:`MediaManagerItem` and a mocked EditBibleForm which returns False when exec is
524 # called
525 self.media_item.bible = MagicMock()
526 mocked_edit_bible_form_instance = MagicMock(**{'exec.return_value': False})
527 with patch('openlp.plugins.bibles.lib.mediaitem.EditBibleForm', return_value=mocked_edit_bible_form_instance) \
528 as mocked_edit_bible_form, \
529 patch.object(self.media_item, 'reload_bibles') as mocked_reload_bibles:
530
531 # WHEN: on_edit_click is called, and the user cancels the EditBibleForm
532 self.media_item.on_edit_click()
533
534 # THEN: EditBibleForm should have been been instianted but reload_bibles should not have been called
535 self.assertTrue(mocked_edit_bible_form.called)
536 self.assertFalse(mocked_reload_bibles.called)
537
538 def test_on_edit_click_user_accepts_edit_form(self):
539 """
540 Test on_edit_click when the user accepts the EditBibleForm
541 """
542 # GIVEN: An instance of :class:`MediaManagerItem` and a mocked EditBibleForm which returns True when exec is
543 # called
544 self.media_item.bible = MagicMock()
545 mocked_edit_bible_form_instance = MagicMock(**{'exec.return_value': True})
546 with patch('openlp.plugins.bibles.lib.mediaitem.EditBibleForm',
547 return_value=mocked_edit_bible_form_instance) \
548 as mocked_edit_bible_form, \
549 patch.object(self.media_item, 'reload_bibles') as mocked_reload_bibles:
550
551 # WHEN: on_edit_click is called, and the user accpets the EditBibleForm
552 self.media_item.on_edit_click()
553
554 # THEN: EditBibleForm should have been been instianted and reload_bibles should have been called
555 self.assertTrue(mocked_edit_bible_form.called)
556 self.assertTrue(mocked_reload_bibles.called)
557
558 def test_on_delete_click_no_bible(self):
559 """
560 Test on_delete_click when there is no main bible selected
561 """
562 # GIVEN: An instance of :class:`MediaManagerItem`
563 with patch('openlp.plugins.bibles.lib.mediaitem.QtWidgets.QMessageBox') as mocked_qmessage_box:
564
565 # WHEN: A main bible is not selected and on_delete_click is called
566 self.media_item.bible = None
567 self.media_item.on_delete_click()
568
569 # THEN: QMessageBox.question should not have been called
570 self.assertFalse(mocked_qmessage_box.question.called)
571
572 def test_on_delete_click_response_no(self):
573 """
574 Test on_delete_click when the user selects no from the message box
575 """
576 # GIVEN: An instance of :class:`MediaManagerItem` and a QMessageBox which reutrns QtWidgets.QMessageBox.No
577 self.media_item.bible = MagicMock()
578 with patch('openlp.plugins.bibles.lib.mediaitem.QtWidgets.QMessageBox.question',
579 return_value=QtWidgets.QMessageBox.No) as mocked_qmessage_box:
580
581 # WHEN: on_delete_click is called
582 self.media_item.on_delete_click()
583
584 # THEN: QMessageBox.question should have been called, but the delete_bible should not have been called
585 self.assertTrue(mocked_qmessage_box.called)
586 self.assertFalse(self.mocked_plugin.manager.delete_bible.called)
587
588 def test_on_delete_click_response_yes(self):
589 """
590 Test on_delete_click when the user selects yes from the message box
591 """
592 # GIVEN: An instance of :class:`MediaManagerItem` and a QMessageBox which reutrns QtWidgets.QMessageBox.Yes
593 self.media_item.bible = MagicMock()
594 with patch('openlp.plugins.bibles.lib.mediaitem.QtWidgets.QMessageBox.question',
595 return_value=QtWidgets.QMessageBox.Yes) as mocked_qmessage_box, \
596 patch.object(self.media_item, 'reload_bibles'):
597
598 # WHEN: on_delete_click is called
599 self.media_item.on_delete_click()
600
601 # THEN: QMessageBox.question should and delete_bible should not have been called
602 self.assertTrue(mocked_qmessage_box.called)
603 self.assertTrue(self.mocked_plugin.manager.delete_bible.called)
604
605 def test_on_search_tab_bar_current_changed_search_tab_selected(self):
606 """
607 Test on_search_tab_bar_current_changed when the search_tab is selected
608 """
609 # GIVEN: An instance of :class:`MediaManagerItem` and mocked out search_tab and select_tab
610 self.media_item.search_tab = MagicMock()
611 self.media_item.select_tab = MagicMock()
612 with patch.object(self.media_item, 'on_focus'):
613
614 # WHEN: The search_tab has been selected
615 self.media_item.on_search_tab_bar_current_changed(0)
616
617 # THEN: search_tab should be setVisible and select_tab should be hidder
618 self.media_item.search_tab.setVisible.assert_called_once_with(True)
619 self.media_item.select_tab.setVisible.assert_called_once_with(False)
620
621 def test_on_search_tab_bar_current_changed_select_tab_selected(self):
622 """
623 Test on_search_tab_bar_current_changed when the select_tab is selected
624 """
625 # GIVEN: An instance of :class:`MediaManagerItem` and mocked out search_tab and select_tab
626 self.media_item.search_tab = MagicMock()
627 self.media_item.select_tab = MagicMock()
628 with patch.object(self.media_item, 'on_focus'):
629
630 # WHEN: The select_tab has been selected
631 self.media_item.on_search_tab_bar_current_changed(1)
632
633 # THEN: search_tab should be setVisible and select_tab should be hidder
634 self.media_item.search_tab.setVisible.assert_called_once_with(False)
635 self.media_item.select_tab.setVisible.assert_called_once_with(True)
636
637 def test_on_book_order_button_toggled_checked(self):
638 """
639 Test that 'on_book_order_button_toggled' changes the order of the book list
640 """
641 self.media_item.select_book_combo_box = MagicMock()
642
643 # WHEN: When the book_order_button is checked
644 self.media_item.on_book_order_button_toggled(True)
645
646 # THEN: The select_book_combo_box model should have been sorted
647 self.media_item.select_book_combo_box.model().sort.assert_called_once_with(0)
648
649 def test_on_book_order_button_toggled_un_checked(self):
650 """
651 Test that 'on_book_order_button_toggled' changes the order of the book list
652 """
653 self.media_item.select_book_combo_box = MagicMock()
654
655 # WHEN: When the book_order_button is un-checked
656 self.media_item.on_book_order_button_toggled(False)
657
658 # THEN: The select_book_combo_box model sort should have been reset
659 self.media_item.select_book_combo_box.model().sort.assert_called_once_with(-1)
172660
173 def test_on_clear_button_clicked(self):661 def test_on_clear_button_clicked(self):
174 """662 """
175 Test that the on_clear_button_clicked works properly. (Used by Bible search tab)663 Test on_clear_button_clicked
176 """664 """
177 # GIVEN: Mocked list_view, check_search_results & quick_search_edit.665 # GIVEN: An instance of :class:`MediaManagerItem` and mocked out search_tab and select_tab and a mocked out
666 # list_view and search_edit
178 self.media_item.list_view = MagicMock()667 self.media_item.list_view = MagicMock()
179 self.media_item.quick_search_edit = MagicMock()668 self.media_item.search_edit = MagicMock()
180669 with patch.object(self.media_item, 'on_focus'):
181 # WHEN: on_clear_button_clicked is called670
182 self.media_item.on_clear_button_clicked()671 # WHEN: Calling on_clear_button_clicked
183672 self.media_item.on_clear_button_clicked()
184 # THEN: Search result should be reset and search field should receive focus.673
185 self.media_item.list_view.clear.assert_called_once_with(),674 # THEN: The list_view and the search_edit should be cleared
186 self.media_item.quick_search_edit.clear.assert_called_once_with(),675 self.media_item.list_view.clear.assert_called_once_with()
187 self.media_item.quick_search_edit.setFocus.assert_called_once_with()676 self.media_item.search_edit.clear.assert_called_once_with()
188677
189 def test_on_lock_button_toggled_search_tab_lock_icon(self):678 def test_on_lock_button_toggled_search_tab_lock_icon(self):
190 """679 """
191 Test that "on_lock_button_toggled" gives focus to the right field and toggles the lock properly.680 Test that "on_lock_button_toggled" toggles the lock properly.
192 """681 """
193 # GIVEN: Mocked sender & Search edit, quickTab returning value = True on isVisible.682 # GIVEN: An instance of :class:`MediaManagerItem` a mocked sender and list_view
194 self.media_item.sender = MagicMock()683 self.media_item.list_view = MagicMock()
195 self.media_item.quick_search_edit = MagicMock()
196 self.media_item.quickTab = MagicMock(**{'isVisible.return_value': True})
197
198 self.media_item.lock_icon = 'lock icon'684 self.media_item.lock_icon = 'lock icon'
199 sender_instance_mock = MagicMock()685 mocked_sender_instance = MagicMock()
200 self.media_item.sender = MagicMock(return_value=sender_instance_mock)686 with patch.object(self.media_item, 'sender', return_value=mocked_sender_instance):
201687
202 # WHEN: on_lock_button_toggled is called and checked returns = True.688 # WHEN: When the lock_button is checked
203 self.media_item.on_lock_button_toggled(True)689 self.media_item.on_lock_button_toggled(True)
204690
205 # THEN: on_quick_search_edit should receive focus and Lock icon should be set.691 # THEN: list_view should be 'locked' and the lock icon set
206 self.media_item.quick_search_edit.setFocus.assert_called_once_with()692 self.assertTrue(self.media_item.list_view.locked)
207 sender_instance_mock.setIcon.assert_called_once_with('lock icon')693 mocked_sender_instance.setIcon.assert_called_once_with('lock icon')
208694
209 def test_on_lock_button_toggled_unlock_icon(self):695 def test_on_lock_button_toggled_unlock_icon(self):
210 """696 """
211 Test that lock button unlocks properly and lock toggles properly.697 Test that "on_lock_button_toggled" toggles the lock properly.
212 """698 """
213 # GIVEN: Mocked sender & Search edit, quickTab returning value = False on isVisible.699 # GIVEN: An instance of :class:`MediaManagerItem` a mocked sender and list_view
214 self.media_item.sender = MagicMock()700 self.media_item.list_view = MagicMock()
215 self.media_item.quick_search_edit = MagicMock()
216 self.media_item.quickTab = MagicMock()
217 self.media_item.quickTab.isVisible = MagicMock()
218 self.media_item.unlock_icon = 'unlock icon'701 self.media_item.unlock_icon = 'unlock icon'
219 sender_instance_mock = MagicMock()702 mocked_sender_instance = MagicMock()
220 self.media_item.sender = MagicMock(return_value=sender_instance_mock)703 with patch.object(self.media_item, 'sender', return_value=mocked_sender_instance):
221704
222 # WHEN: on_lock_button_toggled is called and checked returns = False.705 # WHEN: When the lock_button is unchecked
223 self.media_item.on_lock_button_toggled(False)706 self.media_item.on_lock_button_toggled(False)
224707
225 # THEN: Unlock icon should be set.708 # THEN: list_view should be 'unlocked' and the unlock icon set
226 sender_instance_mock.setIcon.assert_called_once_with('unlock icon')709 self.assertFalse(self.media_item.list_view.locked)
710 mocked_sender_instance.setIcon.assert_called_once_with('unlock icon')
711
712 def test_on_style_combo_box_changed(self):
713 """
714 Test on_style_combo_box_index_changed
715 """
716 # GIVEN: An instance of :class:`MediaManagerItem` a mocked media_item.settings
717 self.media_item.settings = MagicMock()
718
719 # WHEN: Calling on_style_combo_box_index_changed
720 self.media_item.on_style_combo_box_index_changed(2)
721
722 # THEN: The layput_style settimg should have been set
723 self.assertEqual(self.media_item.settings.layout_style, 2)
724 self.media_item.settings.layout_style_combo_box.setCurrentIndex.assert_called_once_with(2)
725 self.mocked_settings_instance.setValue.assert_called_once_with('bibles/verse layout style', 2)
726
727 def test_on_version_combo_box_index_changed_no_bible(self):
728 """
729 Test on_version_combo_box_index_changed when there is no main bible.
730 """
731 # GIVEN: An instance of :class:`MediaManagerItem` and mocked media_item.settings and select_book_combo_box
732 self.media_item.version_combo_box = MagicMock(**{'currentData.return_value': None})
733 self.media_item.select_book_combo_box = MagicMock()
734 with patch.object(self.media_item, 'initialise_advanced_bible') as mocked_initialise_advanced_bible:
735
736 # WHEN: Calling on_version_combo_box_index_changed
737 self.media_item.on_version_combo_box_index_changed()
738
739 # THEN: The vesion should be saved to settings and the 'select tab' should be initialised
740 self.assertFalse(self.mocked_settings_instance.setValue.called)
741 self.assertTrue(self.media_item.initialise_advanced_bible.called)
742
743 def test_on_version_combo_box_index_changed_bible_selected(self):
744 """
745 Test on_version_combo_box_index_changed when a bible has been selected.
746 """
747 # GIVEN: An instance of :class:`MediaManagerItem` and mocked media_item.settings and select_book_combo_box
748 mocked_bible_db = MagicMock()
749 mocked_bible_db.name = 'ABC'
750 self.media_item.version_combo_box = MagicMock(**{'currentData.return_value': mocked_bible_db})
751 self.media_item.select_book_combo_box = MagicMock()
752 with patch.object(self.media_item, 'initialise_advanced_bible') as mocked_initialise_advanced_bible:
753
754 # WHEN: Calling on_version_combo_box_index_changed
755 self.media_item.on_version_combo_box_index_changed()
756
757 # THEN: The vesion should be saved to settings and the 'select tab' should be initialised
758 self.mocked_settings_instance.setValue.assert_called_once_with('bibles/primary bible', 'ABC')
759 self.assertTrue(self.media_item.initialise_advanced_bible.called)
760
761 def test_on_second_combo_box_index_changed_mode_not_changed(self):
762 """
763 Test on_second_combo_box_index_changed when the user does not change from dual mode
764 results and the user chooses no to the message box
765 """
766 # GIVEN: An instance of :class:`MediaManagerItem`
767 self.media_item.list_view = MagicMock(**{'count.return_value': 5})
768 self.media_item.style_combo_box = MagicMock()
769 self.media_item.select_book_combo_box = MagicMock()
770 with patch.object(self.media_item, 'initialise_advanced_bible') as mocked_initialise_advanced_bible, \
771 patch('openlp.plugins.bibles.lib.mediaitem.critical_error_message_box') \
772 as mocked_critical_error_message_box:
773
774 # WHEN: The previously selected bible is one bible and the new selection is annother bible
775 self.media_item.second_bible = self.mocked_bible_1
776 self.media_item.second_combo_box = MagicMock(**{'currentData.return_value': self.mocked_bible_2})
777 self.media_item.on_second_combo_box_index_changed(5)
778
779 # THEN: The new bible should now be the current bible
780 self.assertFalse(mocked_critical_error_message_box.called)
781 self.media_item.style_combo_box.setEnabled.assert_called_once_with(False)
782 self.assertEqual(self.media_item.second_bible, self.mocked_bible_2)
783
784 def test_on_second_combo_box_index_changed_single_to_dual_user_abort(self):
785 """
786 Test on_second_combo_box_index_changed when the user changes from single to dual bible mode, there are search
787 results and the user chooses no to the message box
788 """
789 # GIVEN: An instance of :class:`MediaManagerItem`
790 self.media_item.list_view = MagicMock(**{'count.return_value': 5})
791 self.media_item.style_combo_box = MagicMock()
792 self.media_item.select_book_combo_box = MagicMock()
793 with patch.object(self.media_item, 'initialise_advanced_bible') as mocked_initialise_advanced_bible, \
794 patch('openlp.plugins.bibles.lib.mediaitem.critical_error_message_box',
795 return_value=QtWidgets.QMessageBox.No) as mocked_critical_error_message_box:
796
797 # WHEN: The previously selected bible is None and the new selection is a bible and the user selects yes
798 # to the dialog box
799 self.media_item.second_bible = None
800 self.media_item.second_combo_box = MagicMock(**{'currentData.return_value': self.mocked_bible_1})
801 self.media_item.on_second_combo_box_index_changed(5)
802
803 # THEN: The list_view should be cleared and the currently selected bible should not be channged
804 self.assertTrue(mocked_critical_error_message_box.called)
805 self.assertTrue(self.media_item.second_combo_box.setCurrentIndex.called)
806 self.assertFalse(self.media_item.style_combo_box.setEnabled.called)
807 self.assertEqual(self.media_item.second_bible, None)
808
809 def test_on_second_combo_box_index_changed_single_to_dual(self):
810 """
811 Test on_second_combo_box_index_changed when the user changes from single to dual bible mode, there are search
812 results and the user chooses yes to the message box
813 """
814 # GIVEN: An instance of :class:`MediaManagerItem`
815 self.media_item.list_view = MagicMock(**{'count.return_value': 5})
816 self.media_item.style_combo_box = MagicMock()
817 self.media_item.select_book_combo_box = MagicMock()
818 with patch.object(self.media_item, 'initialise_advanced_bible') as mocked_initialise_advanced_bible, \
819 patch('openlp.plugins.bibles.lib.mediaitem.critical_error_message_box',
820 return_value=QtWidgets.QMessageBox.Yes) as mocked_critical_error_message_box:
821
822 # WHEN: The previously selected bible is None and the new selection is a bible and the user selects yes
823 # to the dialog box
824 self.media_item.second_bible = None
825 self.media_item.second_combo_box = MagicMock(**{'currentData.return_value': self.mocked_bible_1})
826 self.media_item.on_second_combo_box_index_changed(5)
827
828 # THEN: The list_view should be cleared and the selected bible should be set as the current bible
829 self.assertTrue(mocked_critical_error_message_box.called)
830 self.media_item.list_view.clear.assert_called_once_with(override_lock=True)
831 self.media_item.style_combo_box.setEnabled.assert_called_once_with(False)
832 self.assertTrue(mocked_initialise_advanced_bible.called)
833 self.assertEqual(self.media_item.second_bible, self.mocked_bible_1)
834
835 def test_on_second_combo_box_index_changed_dual_to_single(self):
836 """
837 Test on_second_combo_box_index_changed when the user changes from dual to single bible mode, there are search
838 results and the user chooses yes to the message box
839 """
840 # GIVEN: An instance of :class:`MediaManagerItem`
841 self.media_item.list_view = MagicMock(**{'count.return_value': 5})
842 self.media_item.style_combo_box = MagicMock()
843 self.media_item.select_book_combo_box = MagicMock()
844 with patch.object(self.media_item, 'initialise_advanced_bible') as mocked_initialise_advanced_bible, \
845 patch('openlp.plugins.bibles.lib.mediaitem.critical_error_message_box',
846 return_value=QtWidgets.QMessageBox.Yes) as mocked_critical_error_message_box:
847 # WHEN: The previously is a bible new selection is None and the user selects yes
848 # to the dialog box
849 self.media_item.second_bible = self.mocked_bible_1
850 self.media_item.second_combo_box = MagicMock(**{'currentData.return_value': None})
851 self.media_item.on_second_combo_box_index_changed(0)
852
853 # THEN: The list_view should be cleared and the selected bible should be set as the current bible
854 self.assertTrue(mocked_critical_error_message_box.called)
855 self.media_item.list_view.clear.assert_called_once_with(override_lock=True)
856 self.media_item.style_combo_box.setEnabled.assert_called_once_with(True)
857 self.assertFalse(mocked_initialise_advanced_bible.called)
858 self.assertEqual(self.media_item.second_bible, None)
859
860 def test_on_advanced_book_combo_box(self):
861 """
862 Test on_advanced_book_combo_box when the book returns 0 for the verse count.
863 """
864 # GIVEN: An instance of :class:`MediaManagerItem` and a mocked get_verse_count_by_book_ref_id which returns 0
865 self.media_item.select_book_combo_box = MagicMock(**{'currentData.return_value': 2})
866 self.media_item.bible = self.mocked_bible_1
867 self.mocked_plugin.manager.get_verse_count_by_book_ref_id.return_value = 0
868 self.media_item.search_button = MagicMock()
869 with patch('openlp.plugins.bibles.lib.mediaitem.critical_error_message_box') \
870 as mocked_critical_error_message_box:
871
872 # WHEN: Calling on_advanced_book_combo_box
873 self.media_item.on_advanced_book_combo_box()
874
875 # THEN: The user should be informed that the bible cannot be used and the search button should be disabled
876 self.mocked_plugin.manager.get_book_by_id.assert_called_once_with('Bible 1', 2)
877 self.media_item.search_button.setEnabled.assert_called_once_with(False)
878 self.assertTrue(mocked_critical_error_message_box.called)
879
880 def test_on_advanced_book_combo_box_set_up_comboboxes(self):
881 """
882 Test on_advanced_book_combo_box when the book returns 6 for the verse count.
883 """
884 # GIVEN: An instance of :class:`MediaManagerItem` and a mocked get_verse_count_by_book_ref_id which returns 6
885 self.media_item.from_chapter = 0
886 self.media_item.to_chapter = 0
887 self.media_item.from_verse = 0
888 self.media_item.to_verse = 0
889 self.media_item.select_book_combo_box = MagicMock(**{'currentData.return_value': 2})
890 self.media_item.bible = self.mocked_bible_1
891 self.mocked_plugin.manager.get_verse_count_by_book_ref_id.return_value = 6
892 self.media_item.search_button = MagicMock()
893 with patch.object(self.media_item, 'adjust_combo_box') as mocked_adjust_combo_box:
894 # WHEN: Calling on_advanced_book_combo_box
895 self.media_item.on_advanced_book_combo_box()
896
897 # THEN: The verse selection combobox's should be set up
898 self.mocked_plugin.manager.get_book_by_id.assert_called_once_with('Bible 1', 2)
899 self.media_item.search_button.setEnabled.assert_called_once_with(True)
900 self.assertEqual(mocked_adjust_combo_box.call_count, 4)
901
902 def test_on_from_chapter_activated_invalid_to_chapter(self):
903 """
904 Test on_from_chapter_activated when the to_chapter is less than the from_chapter
905 """
906 # GIVEN: An instance of :class:`MediaManagerItem`, some mocked comboboxes with test data
907 self.media_item.chapter_count = 25
908 self.media_item.bible = self.mocked_bible_1
909 self.media_item.select_book_combo_box = MagicMock()
910 self.media_item.from_chapter = MagicMock(**{'currentData.return_value': 10})
911 self.media_item.to_chapter = MagicMock(**{'currentData.return_value': 5})
912 self.media_item.from_verse = MagicMock()
913 self.media_item.to_verse = MagicMock()
914 self.mocked_plugin.manager.get_verse_count_by_book_ref_id.return_value = 20
915 with patch.object(self.media_item, 'adjust_combo_box') as mocked_adjust_combo_box:
916
917 # WHEN: Calling on_from_chapter_activated
918 self.media_item.on_from_chapter_activated()
919
920 # THEN: The to_verse and to_chapter comboboxes should be updated appropriately
921 self.assertEqual(mocked_adjust_combo_box.call_args_list, [
922 call(1, 20, self.media_item.from_verse), call(1, 20, self.media_item.to_verse, False),
923 call(10, 25, self.media_item.to_chapter, False)])
924
925 def test_on_from_chapter_activated_same_chapter(self):
926 """
927 Test on_from_chapter_activated when the to_chapter is the same as from_chapter
928 """
929 # GIVEN: An instance of :class:`MediaManagerItem`, some mocked comboboxes with test data
930 self.media_item.chapter_count = 25
931 self.media_item.bible = self.mocked_bible_1
932 self.media_item.select_book_combo_box = MagicMock()
933 self.media_item.from_chapter = MagicMock(**{'currentData.return_value': 5})
934 self.media_item.to_chapter = MagicMock(**{'currentData.return_value': 5})
935 self.media_item.from_verse = MagicMock()
936 self.media_item.to_verse = MagicMock()
937 self.mocked_plugin.manager.get_verse_count_by_book_ref_id.return_value = 20
938 with patch.object(self.media_item, 'adjust_combo_box') as mocked_adjust_combo_box:
939
940 # WHEN: Calling on_from_chapter_activated
941 self.media_item.on_from_chapter_activated()
942
943 # THEN: The to_verse and to_chapter comboboxes should be updated appropriately
944 self.assertEqual(mocked_adjust_combo_box.call_args_list, [
945 call(1, 20, self.media_item.from_verse), call(1, 20, self.media_item.to_verse, True),
946 call(5, 25, self.media_item.to_chapter, False)])
947
948 def test_on_from_chapter_activated_lower_chapter(self):
949 """
950 Test on_from_chapter_activated when the to_chapter is greater than the from_chapter
951 """
952 # GIVEN: An instance of :class:`MediaManagerItem`, some mocked comboboxes with test data
953 self.media_item.chapter_count = 25
954 self.media_item.bible = self.mocked_bible_1
955 self.media_item.select_book_combo_box = MagicMock()
956 self.media_item.from_chapter = MagicMock(**{'currentData.return_value': 5})
957 self.media_item.to_chapter = MagicMock(**{'currentData.return_value': 7})
958 self.media_item.from_verse = MagicMock()
959 self.media_item.to_verse = MagicMock()
960 self.mocked_plugin.manager.get_verse_count_by_book_ref_id.return_value = 20
961 with patch.object(self.media_item, 'adjust_combo_box') as mocked_adjust_combo_box:
962 # WHEN: Calling on_from_chapter_activated
963 self.media_item.on_from_chapter_activated()
964
965 # THEN: The to_verse and to_chapter comboboxes should be updated appropriately
966 self.assertEqual(mocked_adjust_combo_box.call_args_list, [
967 call(1, 20, self.media_item.from_verse), call(5, 25, self.media_item.to_chapter, True)])
968
969 def test_on_from_verse(self):
970 """
971 Test on_from_verse when the to_chapter is not equal to the from_chapter
972 """
973 # GIVEN: An instance of :class:`MediaManagerItem`, some mocked comboboxes with test data
974 self.media_item.select_book_combo_box = MagicMock()
975 self.media_item.from_chapter = MagicMock(**{'currentData.return_value': 2})
976 self.media_item.to_chapter = MagicMock(**{'currentData.return_value': 5})
977
978 # WHEN: Calling on_from_verse
979 self.media_item.on_from_verse()
980
981 # THEN: select_book_combo_box.currentData should nto be called
982 self.assertFalse(self.media_item.select_book_combo_box.currentData.called)
983
984 def test_on_from_verse_equal(self):
985 """
986 Test on_from_verse when the to_chapter is equal to the from_chapter
987 """
988 # GIVEN: An instance of :class:`MediaManagerItem`, some mocked comboboxes with test data
989 self.media_item.bible = self.mocked_bible_1
990 self.media_item.select_book_combo_box = MagicMock()
991 self.media_item.from_chapter = MagicMock(**{'currentData.return_value': 5})
992 self.media_item.to_chapter = MagicMock(**{'currentData.return_value': 5})
993 self.media_item.from_verse = MagicMock(**{'currentData.return_value': 7})
994 self.media_item.to_verse = MagicMock()
995 self.mocked_plugin.manager.get_verse_count_by_book_ref_id.return_value = 20
996 with patch.object(self.media_item, 'adjust_combo_box') as mocked_adjust_combo_box:
997
998 # WHEN: Calling on_from_verse
999 self.media_item.on_from_verse()
1000
1001 # THEN: The to_verse should have been updated
1002 mocked_adjust_combo_box.assert_called_once_with(7, 20, self.media_item.to_verse, True)
1003
1004 def test_on_to_chapter_same_chapter_from_greater_than(self):
1005 """
1006 Test on_to_chapter when the to_chapter is equal to the from_chapter and the from_verse is greater than the
1007 to_verse
1008 """
1009 # GIVEN: An instance of :class:`MediaManagerItem`, some mocked comboboxes with test data
1010 self.media_item.bible = self.mocked_bible_1
1011 self.media_item.select_book_combo_box = MagicMock()
1012 self.media_item.from_chapter = MagicMock(**{'currentData.return_value': 5})
1013 self.media_item.to_chapter = MagicMock(**{'currentData.return_value': 5})
1014 self.media_item.from_verse = MagicMock(**{'currentData.return_value': 10})
1015 self.media_item.to_verse = MagicMock(**{'currentData.return_value': 7})
1016 self.mocked_plugin.manager.get_verse_count_by_book_ref_id.return_value = 20
1017 with patch.object(self.media_item, 'adjust_combo_box') as mocked_adjust_combo_box:
1018
1019 # WHEN: Calling on_tp_chapter
1020 self.media_item.on_to_chapter()
1021
1022 # THEN: The to_verse should have been updated
1023 mocked_adjust_combo_box.assert_called_once_with(10, 20, self.media_item.to_verse)
1024
1025 def test_on_from_verse_chapters_not_equal(self):
1026 """
1027 Test on_from_verse when the to_chapter is not equal to the from_chapter
1028 """
1029 # GIVEN: An instance of :class:`MediaManagerItem`, some mocked comboboxes with test data
1030 self.media_item.bible = self.mocked_bible_1
1031 self.media_item.select_book_combo_box = MagicMock()
1032 self.media_item.from_chapter = MagicMock(**{'currentData.return_value': 7})
1033 self.media_item.to_chapter = MagicMock(**{'currentData.return_value': 5})
1034 self.media_item.from_verse = MagicMock(**{'currentData.return_value': 10})
1035 self.media_item.to_verse = MagicMock(**{'currentData.return_value': 7})
1036 self.mocked_plugin.manager.get_verse_count_by_book_ref_id.return_value = 20
1037 with patch.object(self.media_item, 'adjust_combo_box') as mocked_adjust_combo_box:
1038
1039 # WHEN: Calling on_from_chapter_activated
1040 self.media_item.on_to_chapter()
1041
1042 # THEN: The to_verse should have been updated
1043 mocked_adjust_combo_box.assert_called_once_with(1, 20, self.media_item.to_verse)
1044
1045 def test_on_from_verse_from_verse_less_than(self):
1046 """
1047 Test on_from_verse when the to_chapter is equal to the from_chapter and from_verse is less than to_verse
1048 """
1049 # GIVEN: An instance of :class:`MediaManagerItem`, some mocked comboboxes with test data
1050 self.media_item.bible = self.mocked_bible_1
1051 self.media_item.select_book_combo_box = MagicMock()
1052 self.media_item.from_chapter = MagicMock(**{'currentData.return_value': 5})
1053 self.media_item.to_chapter = MagicMock(**{'currentData.return_value': 5})
1054 self.media_item.from_verse = MagicMock(**{'currentData.return_value': 6})
1055 self.media_item.to_verse = MagicMock(**{'currentData.return_value': 7})
1056 self.mocked_plugin.manager.get_verse_count_by_book_ref_id.return_value = 20
1057 with patch.object(self.media_item, 'adjust_combo_box') as mocked_adjust_combo_box:
1058
1059 # WHEN: Calling on_from_chapter_activated
1060 self.media_item.on_to_chapter()
1061
1062 # THEN: The to_verse should have been updated
1063 mocked_adjust_combo_box.assert_called_once_with(1, 20, self.media_item.to_verse)
1064
1065 def test_adjust_combo_box_no_restore(self):
1066 """
1067 Test adjust_combo_box when being used with out the restore function
1068 """
1069 # GIVEN: An instance of :class:`MediaManagerItem`
1070 mocked_combo_box = MagicMock()
1071
1072 # WHEN: Calling adjust_combo_box with out setting the kwarg `restore`
1073 self.media_item.adjust_combo_box(10, 13, mocked_combo_box)
1074
1075 # THEN: The combo_box should be cleared, and new items added
1076 mocked_combo_box.clear.assert_called_once_with()
1077 self.assertEqual(mocked_combo_box.addItem.call_args_list,
1078 [call('10', 10), call('11', 11), call('12', 12), call('13', 13)])
1079
1080 def test_adjust_combo_box_restore_found(self):
1081 """
1082 Test adjust_combo_box when being used with out the restore function
1083 """
1084 # GIVEN: An instance of :class:`MediaManagerItem`, with the 2nd item '12' selected
1085 mocked_combo_box = MagicMock(**{'currentData.return_value': 12, 'findData.return_value': 2})
1086
1087 # WHEN: Calling adjust_combo_box with the kwarg `restore` set to True
1088 self.media_item.adjust_combo_box(10, 13, mocked_combo_box, True)
1089
1090 # THEN: The combo_box should be cleared, and new items added. Finally the previously selected item should be
1091 # reselected
1092 mocked_combo_box.clear.assert_called_once_with()
1093 self.assertEqual(mocked_combo_box.addItem.call_args_list,
1094 [call('10', 10), call('11', 11), call('12', 12), call('13', 13)])
1095 mocked_combo_box.setCurrentIndex.assert_called_once_with(2)
1096
1097 def test_adjust_combo_box_restore_not_found(self):
1098 """
1099 Test adjust_combo_box when being used with out the restore function when the selected item is not available
1100 after the combobox has been updated
1101 """
1102 # GIVEN: An instance of :class:`MediaManagerItem`, with the 2nd item '12' selected
1103 mocked_combo_box = MagicMock(**{'currentData.return_value': 9, 'findData.return_value': -1})
1104
1105 # WHEN: Calling adjust_combo_box with the kwarg `restore` set to True
1106 self.media_item.adjust_combo_box(10, 13, mocked_combo_box, True)
1107
1108 # THEN: The combo_box should be cleared, and new items added. Finally the first item should be selected
1109 mocked_combo_box.clear.assert_called_once_with()
1110 self.assertEqual(mocked_combo_box.addItem.call_args_list,
1111 [call('10', 10), call('11', 11), call('12', 12), call('13', 13)])
1112 mocked_combo_box.setCurrentIndex.assert_called_once_with(0)
1113
1114 def test_on_search_button_no_bible(self):
1115 """
1116 Test on_search_button_clicked when there is no bible selected
1117 """
1118 # GIVEN: An instance of :class:`MediaManagerItem`
1119 # WHEN calling on_search_button_clicked and there is no selected bible
1120 self.media_item.bible = None
1121 self.media_item.on_search_button_clicked()
1122
1123 # THEN: The user should be informed that there are no bibles selected
1124 self.assertEqual(self.mocked_main_window.information_message.call_count, 1)
1125
1126 def test_on_search_button_search_tab(self):
1127 """
1128 Test on_search_button_clicked when the `Search` tab is selected
1129 """
1130 # GIVEN: An instance of :class:`MediaManagerItem`, and a mocked text_search method
1131 self.media_item.bible = self.mocked_bible_1
1132 self.media_item.search_button = MagicMock()
1133 self.media_item.search_tab = MagicMock(**{'isVisible.return_value': True})
1134 with patch.object(self.media_item, 'text_search') as mocked_text_search:
1135
1136 # WHEN: Calling on_search_button_clicked and the 'Search' tab is selected
1137 self.media_item.on_search_button_clicked()
1138
1139 # THEN: The text_search method should have been called
1140 mocked_text_search.assert_called_once_with()
1141
1142 def test_on_search_button_select_tab(self):
1143 """
1144 Test on_search_button_clicked when the `Select` tab is selected
1145 """
1146 # GIVEN: An instance of :class:`MediaManagerItem`, and a mocked select_search method
1147 self.media_item.bible = self.mocked_bible_1
1148 self.media_item.search_button = MagicMock()
1149 self.media_item.search_tab = MagicMock(**{'isVisible.return_value': False})
1150 self.media_item.select_tab = MagicMock(**{'isVisible.return_value': True})
1151 with patch.object(self.media_item, 'select_search') as mocked_select_search:
1152
1153 # WHEN: Calling on_search_button_clicked and the 'Select' tab is selected
1154 self.media_item.on_search_button_clicked()
1155
1156 # THEN: The text_search method should have been called
1157 mocked_select_search.assert_called_once_with()
1158
1159 def test_select_search_single_bible(self):
1160 """
1161 Test select_search when only one bible is selected
1162 """
1163 # GIVEN: An instance of :class:`MediaManagerItem` and mocked plugin.manager.get_verses
1164 self.media_item.select_book_combo_box = MagicMock()
1165 self.media_item.from_chapter = MagicMock()
1166 self.media_item.from_verse = MagicMock()
1167 self.media_item.to_chapter = MagicMock()
1168 self.media_item.to_verse = MagicMock()
1169 with patch.object(self.media_item, 'display_results') as mocked_display_results:
1170
1171 # WHEN: Calling select_search and there is only one bible selected
1172 self.media_item.bible = self.mocked_bible_1
1173 self.media_item.second_bible = None
1174 self.media_item.select_search()
1175
1176 # THEN: reference_search should only be called once
1177 self.assertEqual(self.mocked_plugin.manager.get_verses.call_count, 1)
1178 mocked_display_results.assert_called_once_with()
1179
1180 def test_select_search_dual_bibles(self):
1181 """
1182 Test select_search when two bibles are selected
1183 """
1184 # GIVEN: An instance of :class:`MediaManagerItem` and mocked_reference_search
1185 self.media_item.select_book_combo_box = MagicMock()
1186 self.media_item.from_chapter = MagicMock()
1187 self.media_item.from_verse = MagicMock()
1188 self.media_item.to_chapter = MagicMock()
1189 self.media_item.to_verse = MagicMock()
1190 with patch.object(self.media_item, 'display_results') as mocked_display_results:
1191
1192 # WHEN: Calling select_search and there are two bibles selected
1193 self.media_item.bible = self.mocked_bible_1
1194 self.media_item.second_bible = self.mocked_bible_2
1195 self.media_item.select_search()
1196
1197 # THEN: reference_search should be called twice
1198 self.assertEqual(self.mocked_plugin.manager.get_verses.call_count, 2)
1199 mocked_display_results.assert_called_once_with()
1200
1201 def test_text_reference_search_single_bible(self):
1202 """
1203 Test text_reference_search when only one bible is selected
1204 """
1205 # GIVEN: An instance of :class:`MediaManagerItem` and mocked plugin.manager.get_verses
1206 with patch.object(self.media_item, 'display_results') as mocked_display_results:
1207
1208 # WHEN: Calling text_reference_search with only one bible selected
1209 self.media_item.bible = self.mocked_bible_1
1210 self.media_item.second_bible = None
1211 self.media_item.text_reference_search('Search Text')
1212
1213 # THEN: reference_search should only be called once
1214 self.assertEqual(self.mocked_plugin.manager.get_verses.call_count, 1)
1215 mocked_display_results.assert_called_once_with()
1216
1217 def text_reference_search(self, search_text, search_while_type=False):
1218 """
1219 We are doing a 'Reference Search'.
1220 This search is called on def text_search by Reference and Combined Searches.
1221 """
1222 verse_refs = self.plugin.manager.parse_ref(self.bible.name, search_text)
1223 self.search_results = self.reference_search(verse_refs, self.bible)
1224 if self.second_bible and self.search_results:
1225 self.second_search_results = self.reference_search(verse_refs, self.second_bible)
1226 self.display_results()
1227
1228 def test_text_reference_search_dual_bible_no_results(self):
1229 """
1230 Test text_reference_search when two bible are selected, but the search of the first bible does not return any
1231 results
1232 """
1233 # GIVEN: An instance of :class:`MediaManagerItem` and mocked plugin.manager.get_verses
1234 # WHEN: Calling text_reference_search with two bibles selected, but no results are found in the first bible
1235 with patch.object(self.media_item, 'display_results') as mocked_display_results:
1236 self.mocked_plugin.manager.get_verses.return_value = []
1237 self.media_item.bible = self.mocked_bible_1
1238 self.media_item.second_bible = self.mocked_bible_2
1239 self.media_item.text_reference_search('Search Text')
1240
1241 # THEN: reference_search should only be called once
1242 self.assertEqual(self.mocked_plugin.manager.get_verses.call_count, 1)
1243 mocked_display_results.assert_called_once_with()
1244
1245 def test_text_reference_search_dual_bible(self):
1246 """
1247 Test text_reference_search when two bible are selected
1248 """
1249 # GIVEN: An instance of :class:`MediaManagerItem` and mocked plugin.manager.get_verses
1250 with patch.object(self.media_item, 'display_results') as mocked_display_results:
1251 self.media_item.bible = self.mocked_bible_1
1252 self.media_item.second_bible = self.mocked_bible_2
1253
1254 # WHEN: Calling text_reference_search with two bibles selected
1255 self.media_item.text_reference_search('Search Text')
1256
1257 # THEN: reference_search should be called twice
1258 self.assertEqual(self.mocked_plugin.manager.get_verses.call_count, 2)
1259 mocked_display_results.assert_called_once_with()
1260
1261 def test_on_text_search_single_bible(self):
1262 """
1263 Test on_text_search when only one bible is selected
1264 """
1265 # GIVEN: An instance of :class:`MediaManagerItem`
1266 self.media_item.bible = self.mocked_bible_1
1267 self.media_item.second_bible = None
1268
1269 # WHEN: Calling on_text_search and plugin.manager.verse_search returns a list of results
1270 self.mocked_plugin.manager.verse_search.return_value = ['results', 'list']
1271 with patch.object(self.media_item, 'display_results') as mocked_display_results:
1272 self.media_item.on_text_search('Search Text')
1273
1274 # THEN: The search results should be the same as those returned by plugin.manager.verse_search
1275 self.assertEqual(self.media_item.search_results, ['results', 'list'])
1276 mocked_display_results.assert_called_once_with()
1277
1278 def test_on_text_search_no_results(self):
1279 """
1280 Test on_text_search when the search of the first bible does not return any results
1281 """
1282 # GIVEN: An instance of :class:`MediaManagerItem`
1283 self.media_item.bible = self.mocked_bible_1
1284 self.media_item.second_bible = self.mocked_bible_2
1285
1286 # WHEN: Calling on_text_search and plugin.manager.verse_search returns an empty list
1287 self.mocked_plugin.manager.verse_search.return_value = []
1288 with patch.object(self.media_item, 'display_results') as mocked_display_results:
1289 self.media_item.on_text_search('Search Text')
1290
1291 # THEN: The search results should be an empty list
1292 self.assertEqual(self.media_item.search_results, [])
1293 mocked_display_results.assert_called_once_with()
1294
1295 def test_on_text_search_all_results_in_both_books(self):
1296 """
1297 Test on_text_search when all of the results from the first bible are found in the second
1298 """
1299 # GIVEN: An instance of :class:`MediaManagerItem` and some test data
1300 mocked_verse_1 = MagicMock(**{'book.book_reference_id': 1, 'chapter': 2, 'verse': 3})
1301 mocked_verse_1a = MagicMock(**{'book.book_reference_id': 1, 'chapter': 2, 'verse': 3})
1302 mocked_verse_2 = MagicMock(**{'book.book_reference_id': 4, 'chapter': 5, 'verse': 6})
1303 mocked_verse_2a = MagicMock(**{'book.book_reference_id': 4, 'chapter': 5, 'verse': 6})
1304 self.media_item.bible = self.mocked_bible_1
1305 self.media_item.second_bible = self.mocked_bible_2
1306 self.media_item.second_search_results = []
1307
1308 # WHEN: Calling on_text_search and plugin.manager.verse_search returns a list of search results
1309 self.mocked_plugin.manager.verse_search.return_value = [mocked_verse_1, mocked_verse_2]
1310 self.media_item.second_bible.get_verses.side_effect = [[mocked_verse_1a], [mocked_verse_2a]]
1311 with patch.object(self.media_item, 'display_results') as mocked_display_results:
1312 self.media_item.on_text_search('Search Text')
1313
1314 # THEN: The search results for both bibles should be returned
1315 self.assertEqual(self.media_item.search_results, [mocked_verse_1, mocked_verse_2])
1316 self.assertEqual(self.media_item.second_search_results, [mocked_verse_1a, mocked_verse_2a])
1317 self.assertFalse(self.mocked_log.debug.called)
1318 self.assertFalse(self.mocked_main_window.information_message.called)
1319 mocked_display_results.assert_called_once_with()
1320
1321 def test_on_text_search_not_all_results_in_both_books(self):
1322 """
1323 Test on_text_search when not all of the results from the first bible are found in the second
1324 """
1325 # GIVEN: An instance of :class:`MediaManagerItem` and some test data
1326 mocked_verse_1 = MagicMock(**{'book.book_reference_id': 1, 'chapter': 2, 'verse': 3})
1327 mocked_verse_1a = MagicMock(**{'book.book_reference_id': 1, 'chapter': 2, 'verse': 3})
1328 mocked_verse_2 = MagicMock(**{'book.book_reference_id': 4, 'chapter': 5, 'verse': 6})
1329 mocked_verse_3 = MagicMock(**{'book.book_reference_id': 7, 'chapter': 8, 'verse': 9})
1330 self.media_item.bible = self.mocked_bible_1
1331 self.media_item.second_bible = self.mocked_bible_2
1332 self.media_item.second_search_results = []
1333
1334 # WHEN: Calling on_text_search and not all results are found in the second bible
1335 self.mocked_plugin.manager.verse_search.return_value = [mocked_verse_1, mocked_verse_2, mocked_verse_3]
1336 self.media_item.second_bible.get_verses.side_effect = [[mocked_verse_1a], [], []]
1337 with patch.object(self.media_item, 'display_results') as mocked_display_results:
1338 self.media_item.on_text_search('Search Text')
1339
1340 # THEN: The search results included in both bibles should be returned and the user should be notified of
1341 # the missing verses
1342 self.assertEqual(self.media_item.search_results, [mocked_verse_1])
1343 self.assertEqual(self.media_item.second_search_results, [mocked_verse_1a])
1344 self.assertEqual(self.mocked_log.debug.call_count, 2)
1345 self.assertTrue(self.mocked_main_window.information_message.called)
1346 mocked_display_results.assert_called_once_with()
1347
1348 # TODO: Test text_search
1349
1350 def test_on_search_edit_text_changed_search_while_typing_disabled(self):
1351 """
1352 Test on_search_edit_text_changed when 'search while typing' is disabled
1353 """
1354 # GIVEN: An instance of BibleMediaItem and mocked Settings which returns False when the value
1355 # 'bibles/is search while typing enabled' is requested
1356 self.setting_values = {'bibles/is search while typing enabled': False}
1357 self.mocked_qtimer.isActive.return_value = False
1358
1359 # WHEN: Calling on_search_edit_text_changed
1360 self.media_item.on_search_edit_text_changed()
1361
1362 # THEN: The method should not have checked if the timer is active
1363 self.assertFalse(self.media_item.search_timer.isActive.called)
1364
1365 def test_on_search_edit_text_changed_search_while_typing_enabled(self):
1366 """
1367 Test on_search_edit_text_changed when 'search while typing' is enabled
1368 """
1369 # GIVEN: An instance of BibleMediaItem and mocked Settings which returns True when the value
1370 # 'bibles/is search while typing enabled' is requested
1371 self.setting_values = {'bibles/is search while typing enabled': True}
1372 self.media_item.search_timer.isActive.return_value = False
1373
1374 # WHEN: Calling on_search_edit_text_changed
1375 self.media_item.on_search_edit_text_changed()
1376
1377 # THEN: The method should start the search_timer
1378 self.media_item.search_timer.isActive.assert_called_once_with()
1379 self.media_item.search_timer.start.assert_called_once_with()
1380
1381 def test_on_search_timer_timeout(self):
1382 """
1383 Test on_search_timer_timeout
1384 """
1385 # GIVEN: An instance of BibleMediaItem
1386 with patch.object(self.media_item, 'text_search') as mocked_text_search:
1387
1388 # WHEN: Calling on_search_timer_timeout
1389 self.media_item.on_search_timer_timeout()
1390
1391 # THEN: The text_search method should have been called with True
1392 mocked_text_search.assert_called_once_with(True)
1393
1394 def test_display_results_no_results(self):
1395 """
1396 Test the display_results method when there are no items to display
1397 """
1398 # GIVEN: An instance of BibleMediaItem and a mocked build_display_results which returns an empty list
1399 self.media_item.list_view = MagicMock()
1400 self.media_item.bible = self.mocked_bible_1
1401 self.media_item.second_bible = self.mocked_bible_2
1402 self.media_item.search_results = []
1403
1404 with patch.object(self.media_item, 'build_display_results', return_value=[]):
1405
1406 # WHEN: Calling display_results with True
1407 self.media_item.display_results()
1408
1409 # THEN: No items should be added to the list
1410 self.media_item.list_view.clear.assert_called_once_with()
1411 self.assertFalse(self.media_item.list_view.addItem.called)
1412
1413 def test_display_results_results(self):
1414 """
1415 Test the display_results method when there are items to display
1416 """
1417 # GIVEN: An instance of BibleMediaItem and a mocked build_display_results which returns a list of results
1418 with patch.object(self.media_item, 'build_display_results', return_value=['list', 'items']):
1419 self.media_item.search_results = ['results']
1420 self.media_item.list_view = MagicMock()
1421
1422 # WHEN: Calling display_results
1423 self.media_item.display_results()
1424
1425 # THEN: addItem should have been with the display items
1426 self.media_item.list_view.clear.assert_called_once_with()
1427 self.assertEqual(self.media_item.list_view.addItem.call_args_list, [call('list'), call('items')])
2271428
=== modified file 'tests/resources/themes/Moss_on_tree.otz' (properties changed: +x to -x)