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

Proposed by Phill
Status: Superseded
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
Tim Bentley Needs Fixing
Azaziah Pending
Review via email: mp+317701@code.launchpad.net

This proposal supersedes a proposal from 2016-12-18.

This proposal has been superseded by 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 :

Fixed the above issues!

Revision history for this message
Phill (phill-ridout) wrote :
Revision history for this message
Tim Bentley (trb143) wrote :

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 :

> 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.

2732. By Phill

Added tool tip

Revision history for this message
Phill (phill-ridout) wrote :

> 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?

2733. By Phill

Regened resources

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'openlp/core/common/settings.py'
2--- openlp/core/common/settings.py 2016-12-31 11:01:36 +0000
3+++ openlp/core/common/settings.py 2017-02-18 20:23:59 +0000
4@@ -217,7 +217,9 @@
5 ('advanced/default image', 'core/logo file', []), # Default image renamed + moved to general after 2.4.
6 ('shortcuts/escapeItem', 'shortcuts/desktopScreenEnable', []), # Escape item was removed in 2.6.
7 ('shortcuts/offlineHelpItem', 'shortcuts/userManualItem', []), # Online and Offline help were combined in 2.6.
8- ('shortcuts/onlineHelpItem', 'shortcuts/userManualItem', []) # Online and Offline help were combined in 2.6.
9+ ('shortcuts/onlineHelpItem', 'shortcuts/userManualItem', []), # Online and Offline help were combined in 2.6.
10+ ('bibles/advanced bible', '', []), # Common bible search widgets combined in 2.6
11+ ('bibles/quick bible', 'bibles/primary bible', []) # Common bible search widgets combined in 2.6
12 ]
13
14 @staticmethod
15
16=== modified file 'openlp/core/lib/mediamanageritem.py'
17--- openlp/core/lib/mediamanageritem.py 2016-12-31 11:01:36 +0000
18+++ openlp/core/lib/mediamanageritem.py 2017-02-18 20:23:59 +0000
19@@ -197,14 +197,9 @@
20 """
21 # Add the List widget
22 self.list_view = ListWidgetWithDnD(self, self.plugin.name)
23- self.list_view.setSpacing(1)
24- self.list_view.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
25- self.list_view.setAlternatingRowColors(True)
26 self.list_view.setObjectName('{name}ListView'.format(name=self.plugin.name))
27 # Add to page_layout
28 self.page_layout.addWidget(self.list_view)
29- # define and add the context menu
30- self.list_view.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
31 if self.has_edit_icon:
32 create_widget_action(self.list_view,
33 text=self.plugin.get_string(StringContent.Edit)['title'],
34
35=== modified file 'openlp/core/ui/lib/listwidgetwithdnd.py'
36--- openlp/core/ui/lib/listwidgetwithdnd.py 2016-12-31 11:01:36 +0000
37+++ openlp/core/ui/lib/listwidgetwithdnd.py 2017-02-18 20:23:59 +0000
38@@ -40,6 +40,11 @@
39 super().__init__(parent)
40 self.mime_data_text = name
41 self.no_results_text = UiStrings().NoResults
42+ self.setSpacing(1)
43+ self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
44+ self.setAlternatingRowColors(True)
45+ self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
46+ self.locked = False
47
48 def activateDnD(self):
49 """
50@@ -49,13 +54,15 @@
51 self.setDragDropMode(QtWidgets.QAbstractItemView.DragDrop)
52 Registry().register_function(('%s_dnd' % self.mime_data_text), self.parent().load_file)
53
54- def clear(self, search_while_typing=False):
55+ def clear(self, search_while_typing=False, override_lock=False):
56 """
57 Re-implement clear, so that we can customise feedback when using 'Search as you type'
58
59 :param search_while_typing: True if we want to display the customised message
60 :return: None
61 """
62+ if self.locked and not override_lock:
63+ return
64 if search_while_typing:
65 self.no_results_text = UiStrings().ShortResults
66 else:
67
68=== modified file 'openlp/plugins/bibles/bibleplugin.py'
69--- openlp/plugins/bibles/bibleplugin.py 2016-12-31 11:01:36 +0000
70+++ openlp/plugins/bibles/bibleplugin.py 2017-02-18 20:23:59 +0000
71@@ -46,8 +46,7 @@
72 'bibles/is verse number visible': True,
73 'bibles/display new chapter': False,
74 'bibles/second bibles': True,
75- 'bibles/advanced bible': '',
76- 'bibles/quick bible': '',
77+ 'bibles/primary bible': '',
78 'bibles/proxy name': '',
79 'bibles/proxy address': '',
80 'bibles/proxy username': '',
81
82=== modified file 'openlp/plugins/bibles/lib/__init__.py'
83--- openlp/plugins/bibles/lib/__init__.py 2016-12-31 11:01:36 +0000
84+++ openlp/plugins/bibles/lib/__init__.py 2017-02-18 20:23:59 +0000
85@@ -230,7 +230,7 @@
86 REFERENCE_MATCHES['range_separator'] = re.compile(REFERENCE_SEPARATORS['sep_l'], re.UNICODE)
87 # full reference match: <book>(<range>(,(?!$)|(?=$)))+
88 REFERENCE_MATCHES['full'] = \
89- re.compile('^\s*(?!\s)(?P<book>[\d]*[^\d]+)(?<!\s)\s*'
90+ re.compile('^\s*(?!\s)(?P<book>[\d]*[^\d\.]+)\.*(?<!\s)\s*'
91 '(?P<ranges>(?:%(range_regex)s(?:%(sep_l)s(?!\s*$)|(?=\s*$)))+)\s*$'
92 % dict(list(REFERENCE_SEPARATORS.items()) + [('range_regex', range_regex)]), re.UNICODE)
93
94@@ -326,7 +326,7 @@
95
96 ``^\s*(?!\s)(?P<book>[\d]*[^\d]+)(?<!\s)\s*``
97 The ``book`` group starts with the first non-whitespace character. There are optional leading digits followed by
98- non-digits. The group ends before the whitspace in front of the next digit.
99+ non-digits. The group ends before the whitspace, or a full stop in front of the next digit.
100
101 ``(?P<ranges>(?:%(range_regex)s(?:%(sep_l)s(?!\s*$)|(?=\s*$)))+)\s*$``
102 The second group contains all ``ranges``. This can be multiple declarations of range_regex separated by a list
103
104=== modified file 'openlp/plugins/bibles/lib/db.py'
105--- openlp/plugins/bibles/lib/db.py 2016-12-31 11:01:36 +0000
106+++ openlp/plugins/bibles/lib/db.py 2017-02-18 20:23:59 +0000
107@@ -36,7 +36,7 @@
108 from openlp.core.common import AppLocation, translate, clean_filename
109 from openlp.core.lib.db import BaseModel, init_db, Manager
110 from openlp.core.lib.ui import critical_error_message_box
111-from openlp.plugins.bibles.lib import upgrade
112+from openlp.plugins.bibles.lib import BibleStrings, LanguageSelection, upgrade
113
114 log = logging.getLogger(__name__)
115
116@@ -52,9 +52,15 @@
117
118 class Book(BaseModel):
119 """
120- Song model
121+ Bible Book model
122 """
123- pass
124+ def get_name(self, language_selection=LanguageSelection.Bible):
125+ if language_selection == LanguageSelection.Bible:
126+ return self.name
127+ elif language_selection == LanguageSelection.Application:
128+ return BibleStrings().BookNames[BiblesResourcesDB.get_book_by_id(self.book_reference_id)['abbreviation']]
129+ elif language_selection == LanguageSelection.English:
130+ return BiblesResourcesDB.get_book_by_id(self.book_reference_id)['name']
131
132
133 class Verse(BaseModel):
134@@ -380,13 +386,12 @@
135 """
136 log.debug('BibleDB.verse_search("{text}")'.format(text=text))
137 verses = self.session.query(Verse)
138- # TODO: Find out what this is doing before converting to format()
139 if text.find(',') > -1:
140- keywords = ['%%%s%%' % keyword.strip() for keyword in text.split(',')]
141+ keywords = ['%{keyword}%'.format(keyword=keyword.strip()) for keyword in text.split(',') if keyword.strip()]
142 or_clause = [Verse.text.like(keyword) for keyword in keywords]
143 verses = verses.filter(or_(*or_clause))
144 else:
145- keywords = ['%%%s%%' % keyword.strip() for keyword in text.split(' ')]
146+ keywords = ['%{keyword}%'.format(keyword=keyword.strip()) for keyword in text.split(' ') if keyword.strip()]
147 for keyword in keywords:
148 verses = verses.filter(Verse.text.like(keyword))
149 verses = verses.all()
150
151=== modified file 'openlp/plugins/bibles/lib/manager.py'
152--- openlp/plugins/bibles/lib/manager.py 2017-01-08 19:12:12 +0000
153+++ openlp/plugins/bibles/lib/manager.py 2017-02-18 20:23:59 +0000
154@@ -250,14 +250,20 @@
155 '"{book}", "{chapter}")'.format(bible=bible, book=book_ref_id, chapter=chapter))
156 return self.db_cache[bible].get_verse_count(book_ref_id, chapter)
157
158- def get_verses(self, bible, verse_text, book_ref_id=False, show_error=True):
159+ def parse_ref(self, bible, reference_text, book_ref_id=False):
160+ if not bible:
161+ return
162+ language_selection = self.get_language_selection(bible)
163+ return parse_reference(reference_text, self.db_cache[bible], language_selection, book_ref_id)
164+
165+ def get_verses(self, bible, ref_list, show_error=True):
166 """
167 Parses a scripture reference, fetches the verses from the Bible
168 specified, and returns a list of ``Verse`` objects.
169
170 :param bible: Unicode. The Bible to use.
171 :param verse_text:
172- Unicode. The scripture reference. Valid scripture references are:
173+ String. The scripture reference. Valid scripture references are:
174
175 - Genesis 1
176 - Genesis 1-2
177@@ -271,22 +277,9 @@
178 For second bible this is necessary.
179 :param show_error:
180 """
181- # If no bibles are installed, message is given.
182- log.debug('BibleManager.get_verses("{bible}", "{verse}")'.format(bible=bible, verse=verse_text))
183- if not bible:
184- if show_error:
185- self.main_window.information_message(
186- UiStrings().BibleNoBiblesTitle,
187- UiStrings().BibleNoBibles)
188- return None
189- # Get the language for books.
190- language_selection = self.get_language_selection(bible)
191- ref_list = parse_reference(verse_text, self.db_cache[bible], language_selection, book_ref_id)
192- if ref_list:
193- return self.db_cache[bible].get_verses(ref_list, show_error)
194- # If nothing is found. Message is given if this is not combined search. (defined in mediaitem.py)
195- else:
196- return None
197+ if not bible or not ref_list:
198+ return None
199+ return self.db_cache[bible].get_verses(ref_list, show_error)
200
201 def get_language_selection(self, bible):
202 """
203@@ -308,7 +301,7 @@
204 language_selection = LanguageSelection.Application
205 return language_selection
206
207- def verse_search(self, bible, second_bible, text):
208+ def verse_search(self, bible, text):
209 """
210 Does a verse search for the given bible and text.
211
212@@ -325,20 +318,14 @@
213 return None
214 # Check if the bible or second_bible is a web bible.
215 web_bible = self.db_cache[bible].get_object(BibleMeta, 'download_source')
216- second_web_bible = ''
217- if second_bible:
218- second_web_bible = self.db_cache[second_bible].get_object(BibleMeta, 'download_source')
219- if web_bible or second_web_bible:
220+ if web_bible:
221 # If either Bible is Web, cursor is reset to normal and message is given.
222 self.application.set_normal_cursor()
223 self.main_window.information_message(
224 translate('BiblesPlugin.BibleManager', 'Web Bible cannot be used in Text Search'),
225 translate('BiblesPlugin.BibleManager', 'Text Search is not available with Web Bibles.\n'
226 'Please use the Scripture Reference Search instead.\n\n'
227- 'This means that the currently used Bible\nor Second Bible '
228- 'is installed as Web Bible.\n\n'
229- 'If you were trying to perform a Reference search\nin Combined '
230- 'Search, your reference is invalid.')
231+ 'This means that the currently selected Bible is a Web Bible.')
232 )
233 return None
234 # Shorter than 3 char searches break OpenLP with very long search times, thus they are blocked.
235@@ -380,6 +367,20 @@
236 else:
237 return None
238
239+ def process_verse_range(self, book_ref_id, chapter_from, verse_from, chapter_to, verse_to):
240+ verse_ranges = []
241+ for chapter in range(chapter_from, chapter_to + 1):
242+ if chapter == chapter_from:
243+ start_verse = verse_from
244+ else:
245+ start_verse = 1
246+ if chapter == chapter_to:
247+ end_verse = verse_to
248+ else:
249+ end_verse = -1
250+ verse_ranges.append((book_ref_id, chapter, start_verse, end_verse))
251+ return verse_ranges
252+
253 def save_meta_data(self, bible, version, copyright, permissions, full_license, book_name_language=None):
254 """
255 Saves the bibles meta data.
256
257=== modified file 'openlp/plugins/bibles/lib/mediaitem.py'
258--- openlp/plugins/bibles/lib/mediaitem.py 2017-01-08 19:12:12 +0000
259+++ openlp/plugins/bibles/lib/mediaitem.py 2017-02-18 20:23:59 +0000
260@@ -21,28 +21,36 @@
261 ###############################################################################
262
263 import logging
264+import re
265
266 from PyQt5 import QtCore, QtWidgets
267
268 from openlp.core.common import Registry, Settings, UiStrings, translate
269-from openlp.core.lib import MediaManagerItem, ItemCapabilities, ServiceItemContext, create_separated_list
270+from openlp.core.lib import MediaManagerItem, ItemCapabilities, ServiceItemContext
271 from openlp.core.lib.searchedit import SearchEdit
272 from openlp.core.lib.ui import set_case_insensitive_completer, create_horizontal_adjusting_combo_box, \
273 critical_error_message_box, find_and_set_in_combo_box, build_icon
274 from openlp.core.common.languagemanager import get_locale_key
275 from openlp.plugins.bibles.forms.bibleimportform import BibleImportForm
276 from openlp.plugins.bibles.forms.editbibleform import EditBibleForm
277-from openlp.plugins.bibles.lib import LayoutStyle, DisplayStyle, VerseReferenceList, get_reference_separator, \
278- LanguageSelection, BibleStrings
279-from openlp.plugins.bibles.lib.db import BiblesResourcesDB
280-import re
281+from openlp.plugins.bibles.lib import DisplayStyle, LayoutStyle, VerseReferenceList, \
282+ get_reference_match, get_reference_separator
283
284 log = logging.getLogger(__name__)
285
286
287+VALID_TEXT_SEARCH = re.compile('\w\w\w')
288+
289+
290+def get_reference_separators():
291+ return {'verse': get_reference_separator('sep_v_display'),
292+ 'range': get_reference_separator('sep_r_display'),
293+ 'list': get_reference_separator('sep_l_display')}
294+
295+
296 class BibleSearch(object):
297 """
298- Enumeration class for the different search methods for the "quick search".
299+ Enumeration class for the different search methods for the "Search" tab.
300 """
301 Reference = 1
302 Text = 2
303@@ -57,15 +65,31 @@
304 bibles_add_to_service = QtCore.pyqtSignal(list)
305 log.info('Bible Media Item loaded')
306
307- def __init__(self, parent, plugin):
308+ def __init__(self, *args, **kwargs):
309+ """
310+ Constructor
311+
312+ :param args: Positional arguments to pass to the super method. (tuple)
313+ :param kwargs: Keyword arguments to pass to the super method. (dict)
314+ """
315 self.clear_icon = build_icon(':/bibles/bibles_search_clear.png')
316 self.lock_icon = build_icon(':/bibles/bibles_search_lock.png')
317 self.unlock_icon = build_icon(':/bibles/bibles_search_unlock.png')
318- MediaManagerItem.__init__(self, parent, plugin)
319+ self.sort_icon = build_icon(':/bibles/bibles_book_sort.png')
320+ self.bible = None
321+ self.second_bible = None
322+ # TODO: Make more central and clean up after!
323+ self.search_timer = QtCore.QTimer()
324+ self.search_timer.setInterval(200)
325+ self.search_timer.setSingleShot(True)
326+ self.search_timer.timeout.connect(self.on_search_timer_timeout)
327+ super().__init__(*args, **kwargs)
328
329 def setup_item(self):
330 """
331 Do some additional setup.
332+
333+ :return: None
334 """
335 self.bibles_go_live.connect(self.go_live_remote)
336 self.bibles_add_to_service.connect(self.add_to_service_remote)
337@@ -73,251 +97,174 @@
338 self.settings = self.plugin.settings_tab
339 self.quick_preview_allowed = True
340 self.has_search = True
341- self.search_results = {}
342- self.second_search_results = {}
343+ self.search_results = []
344+ self.second_search_results = []
345 Registry().register_function('bibles_load_list', self.reload_bibles)
346
347- def __check_second_bible(self, bible, second_bible):
348- """
349- Check if the first item is a second bible item or not.
350- """
351- if not self.list_view.count():
352- self.display_results(bible, second_bible)
353- return
354- item_second_bible = self._decode_qt_object(self.list_view.item(0), 'second_bible')
355- if item_second_bible and second_bible or not item_second_bible and not second_bible:
356- self.display_results(bible, second_bible)
357- elif critical_error_message_box(
358- message=translate('BiblesPlugin.MediaItem',
359- 'You cannot combine single and dual Bible verse search results. '
360- 'Do you want to delete your search results and start a new search?'),
361- parent=self, question=True) == QtWidgets.QMessageBox.Yes:
362- self.list_view.clear()
363- self.display_results(bible, second_bible)
364-
365- def _decode_qt_object(self, bitem, key):
366- reference = bitem.data(QtCore.Qt.UserRole)
367- obj = reference[str(key)]
368- return str(obj).strip()
369-
370 def required_icons(self):
371 """
372 Set which icons the media manager tab should show
373+
374+ :return: None
375 """
376- MediaManagerItem.required_icons(self)
377+ super().required_icons()
378 self.has_import_icon = True
379 self.has_new_icon = False
380 self.has_edit_icon = True
381 self.has_delete_icon = True
382 self.add_to_service_item = False
383
384- def add_search_tab(self, prefix, name):
385- self.search_tab_bar.addTab(name)
386- tab = QtWidgets.QWidget()
387- tab.setObjectName(prefix + 'Tab')
388- tab.setSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum)
389- layout = QtWidgets.QGridLayout(tab)
390- layout.setObjectName(prefix + 'Layout')
391- setattr(self, prefix + 'Tab', tab)
392- setattr(self, prefix + 'Layout', layout)
393-
394- def add_search_fields(self, prefix, name):
395- """
396- Creates and adds generic search tab.
397-
398- :param prefix: The prefix of the tab, this is either ``quick`` or ``advanced``.
399- :param name: The translated string to display.
400- """
401- if prefix == 'quick':
402- idx = 2
403- else:
404- idx = 5
405- tab = getattr(self, prefix + 'Tab')
406- layout = getattr(self, prefix + 'Layout')
407- version_label = QtWidgets.QLabel(tab)
408- version_label.setObjectName(prefix + 'VersionLabel')
409- layout.addWidget(version_label, idx, 0, QtCore.Qt.AlignRight)
410- version_combo_box = create_horizontal_adjusting_combo_box(tab, prefix + 'VersionComboBox')
411- version_label.setBuddy(version_combo_box)
412- layout.addWidget(version_combo_box, idx, 1, 1, 2)
413- second_label = QtWidgets.QLabel(tab)
414- second_label.setObjectName(prefix + 'SecondLabel')
415- layout.addWidget(second_label, idx + 1, 0, QtCore.Qt.AlignRight)
416- second_combo_box = create_horizontal_adjusting_combo_box(tab, prefix + 'SecondComboBox')
417- version_label.setBuddy(second_combo_box)
418- layout.addWidget(second_combo_box, idx + 1, 1, 1, 2)
419- style_label = QtWidgets.QLabel(tab)
420- style_label.setObjectName(prefix + 'StyleLabel')
421- layout.addWidget(style_label, idx + 2, 0, QtCore.Qt.AlignRight)
422- style_combo_box = create_horizontal_adjusting_combo_box(tab, prefix + 'StyleComboBox')
423- style_combo_box.addItems(['', '', ''])
424- layout.addWidget(style_combo_box, idx + 2, 1, 1, 2)
425- search_button_layout = QtWidgets.QHBoxLayout()
426- search_button_layout.setObjectName(prefix + 'search_button_layout')
427- search_button_layout.addStretch()
428- # Note: If we use QPushButton instead of the QToolButton, the icon will be larger than the Lock icon.
429- clear_button = QtWidgets.QToolButton(tab)
430- clear_button.setIcon(self.clear_icon)
431- clear_button.setObjectName(prefix + 'ClearButton')
432- lock_button = QtWidgets.QToolButton(tab)
433- lock_button.setIcon(self.unlock_icon)
434- lock_button.setCheckable(True)
435- lock_button.setObjectName(prefix + 'LockButton')
436- search_button_layout.addWidget(clear_button)
437- search_button_layout.addWidget(lock_button)
438- search_button = QtWidgets.QPushButton(tab)
439- search_button.setObjectName(prefix + 'SearchButton')
440- search_button_layout.addWidget(search_button)
441- layout.addLayout(search_button_layout, idx + 3, 1, 1, 2)
442- self.page_layout.addWidget(tab)
443- tab.setVisible(False)
444- lock_button.toggled.connect(self.on_lock_button_toggled)
445- second_combo_box.currentIndexChanged.connect(self.on_second_bible_combobox_index_changed)
446- setattr(self, prefix + 'VersionLabel', version_label)
447- setattr(self, prefix + 'VersionComboBox', version_combo_box)
448- setattr(self, prefix + 'SecondLabel', second_label)
449- setattr(self, prefix + 'SecondComboBox', second_combo_box)
450- setattr(self, prefix + 'StyleLabel', style_label)
451- setattr(self, prefix + 'StyleComboBox', style_combo_box)
452- setattr(self, prefix + 'ClearButton', clear_button)
453- setattr(self, prefix + 'LockButton', lock_button)
454- setattr(self, prefix + 'SearchButtonLayout', search_button_layout)
455- setattr(self, prefix + 'SearchButton', search_button)
456-
457 def add_end_header_bar(self):
458 self.search_tab_bar = QtWidgets.QTabBar(self)
459 self.search_tab_bar.setExpanding(False)
460- self.search_tab_bar.setObjectName('search_tab_bar')
461 self.page_layout.addWidget(self.search_tab_bar)
462- # Add the Quick Search tab.
463- self.add_search_tab('quick', translate('BiblesPlugin.MediaItem', 'Search'))
464- self.quick_search_label = QtWidgets.QLabel(self.quickTab)
465- self.quick_search_label.setObjectName('quick_search_label')
466- self.quickLayout.addWidget(self.quick_search_label, 0, 0, QtCore.Qt.AlignRight)
467- self.quick_search_edit = SearchEdit(self.quickTab, self.settings_section)
468- self.quick_search_edit.setSizePolicy(QtWidgets.QSizePolicy.Ignored, QtWidgets.QSizePolicy.Fixed)
469- self.quick_search_edit.setObjectName('quick_search_edit')
470- self.quick_search_label.setBuddy(self.quick_search_edit)
471- self.quickLayout.addWidget(self.quick_search_edit, 0, 1, 1, 2)
472- self.add_search_fields('quick', translate('BiblesPlugin.MediaItem', 'Search'))
473- self.quickTab.setVisible(True)
474- # Add the Advanced Search tab.
475- self.add_search_tab('advanced', translate('BiblesPlugin.MediaItem', 'Select'))
476- self.advanced_book_label = QtWidgets.QLabel(self.advancedTab)
477- self.advanced_book_label.setObjectName('advanced_book_label')
478- self.advancedLayout.addWidget(self.advanced_book_label, 0, 0, QtCore.Qt.AlignRight)
479- self.advanced_book_combo_box = create_horizontal_adjusting_combo_box(self.advancedTab,
480- 'advanced_book_combo_box')
481- self.advanced_book_label.setBuddy(self.advanced_book_combo_box)
482- self.advancedLayout.addWidget(self.advanced_book_combo_box, 0, 1, 1, 2)
483- self.advanced_chapter_label = QtWidgets.QLabel(self.advancedTab)
484- self.advanced_chapter_label.setObjectName('advanced_chapter_label')
485- self.advancedLayout.addWidget(self.advanced_chapter_label, 1, 1, 1, 2)
486- self.advanced_verse_label = QtWidgets.QLabel(self.advancedTab)
487- self.advanced_verse_label.setObjectName('advanced_verse_label')
488- self.advancedLayout.addWidget(self.advanced_verse_label, 1, 2)
489- self.advanced_from_label = QtWidgets.QLabel(self.advancedTab)
490- self.advanced_from_label.setObjectName('advanced_from_label')
491- self.advancedLayout.addWidget(self.advanced_from_label, 3, 0, QtCore.Qt.AlignRight)
492- self.advanced_from_chapter = QtWidgets.QComboBox(self.advancedTab)
493- self.advanced_from_chapter.setObjectName('advanced_from_chapter')
494- self.advancedLayout.addWidget(self.advanced_from_chapter, 3, 1)
495- self.advanced_from_verse = QtWidgets.QComboBox(self.advancedTab)
496- self.advanced_from_verse.setObjectName('advanced_from_verse')
497- self.advancedLayout.addWidget(self.advanced_from_verse, 3, 2)
498- self.advanced_to_label = QtWidgets.QLabel(self.advancedTab)
499- self.advanced_to_label.setObjectName('advanced_to_label')
500- self.advancedLayout.addWidget(self.advanced_to_label, 4, 0, QtCore.Qt.AlignRight)
501- self.advanced_to_chapter = QtWidgets.QComboBox(self.advancedTab)
502- self.advanced_to_chapter.setObjectName('advanced_to_chapter')
503- self.advancedLayout.addWidget(self.advanced_to_chapter, 4, 1)
504- self.advanced_to_verse = QtWidgets.QComboBox(self.advancedTab)
505- self.advanced_to_verse.setObjectName('advanced_to_verse')
506- self.advancedLayout.addWidget(self.advanced_to_verse, 4, 2)
507- self.add_search_fields('advanced', UiStrings().Advanced)
508+ # Add the Search tab.
509+ self.search_tab = QtWidgets.QWidget()
510+ self.search_tab.setSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum)
511+ self.search_tab_bar.addTab(translate('BiblesPlugin.MediaItem', 'Find'))
512+ self.search_layout = QtWidgets.QFormLayout(self.search_tab)
513+ self.search_edit = SearchEdit(self.search_tab, self.settings_section)
514+ self.search_layout.addRow(translate('BiblesPlugin.MediaItem', 'Find:'), self.search_edit)
515+ self.search_tab.setVisible(True)
516+ self.page_layout.addWidget(self.search_tab)
517+ # Add the Select tab.
518+ self.select_tab = QtWidgets.QWidget()
519+ self.select_tab.setSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum)
520+ self.search_tab_bar.addTab(translate('BiblesPlugin.MediaItem', 'Select'))
521+ self.select_layout = QtWidgets.QFormLayout(self.select_tab)
522+ self.book_layout = QtWidgets.QHBoxLayout()
523+ self.select_book_combo_box = create_horizontal_adjusting_combo_box(self.select_tab, 'select_book_combo_box')
524+ self.book_layout.addWidget(self.select_book_combo_box)
525+ self.book_order_button = QtWidgets.QToolButton()
526+ self.book_order_button.setIcon(self.sort_icon)
527+ self.book_order_button.setCheckable(True)
528+ self.book_order_button.setToolTip(translate('BiblesPlugin.MediaItem', 'Sort bible books alphabetically.'))
529+ self.book_layout.addWidget(self.book_order_button)
530+ self.select_layout.addRow(translate('BiblesPlugin.MediaItem', 'Book:'), self.book_layout)
531+ self.verse_title_layout = QtWidgets.QHBoxLayout()
532+ self.chapter_label = QtWidgets.QLabel(self.select_tab)
533+ self.verse_title_layout.addWidget(self.chapter_label)
534+ self.verse_label = QtWidgets.QLabel(self.select_tab)
535+ self.verse_title_layout.addWidget(self.verse_label)
536+ self.select_layout.addRow('', self.verse_title_layout)
537+ self.from_layout = QtWidgets.QHBoxLayout()
538+ self.from_chapter = QtWidgets.QComboBox(self.select_tab)
539+ self.from_layout.addWidget(self.from_chapter)
540+ self.from_verse = QtWidgets.QComboBox(self.select_tab)
541+ self.from_layout.addWidget(self.from_verse)
542+ self.select_layout.addRow(translate('BiblesPlugin.MediaItem', 'From:'), self.from_layout)
543+ self.to_layout = QtWidgets.QHBoxLayout()
544+ self.to_chapter = QtWidgets.QComboBox(self.select_tab)
545+ self.to_layout.addWidget(self.to_chapter)
546+ self.to_verse = QtWidgets.QComboBox(self.select_tab)
547+ self.to_layout.addWidget(self.to_verse)
548+ self.select_layout.addRow(translate('BiblesPlugin.MediaItem', 'To:'), self.to_layout)
549+ self.select_tab.setVisible(False)
550+ self.page_layout.addWidget(self.select_tab)
551+ # General Search Opions
552+ self.options_widget = QtWidgets.QGroupBox(translate('BiblesPlugin.MediaItem', 'Options'), self)
553+ self.general_bible_layout = QtWidgets.QFormLayout(self.options_widget)
554+ self.version_combo_box = create_horizontal_adjusting_combo_box(self, 'version_combo_box')
555+ self.general_bible_layout.addRow('{version}:'.format(version=UiStrings().Version), self.version_combo_box)
556+ self.second_combo_box = create_horizontal_adjusting_combo_box(self, 'second_combo_box')
557+ self.general_bible_layout.addRow(translate('BiblesPlugin.MediaItem', 'Second:'), self.second_combo_box)
558+ self.style_combo_box = create_horizontal_adjusting_combo_box(self, 'style_combo_box')
559+ self.style_combo_box.addItems(['', '', ''])
560+ self.general_bible_layout.addRow(UiStrings().LayoutStyle, self.style_combo_box)
561+ self.search_button_layout = QtWidgets.QHBoxLayout()
562+ self.search_button_layout.addStretch()
563+ # Note: If we use QPushButton instead of the QToolButton, the icon will be larger than the Lock icon.
564+ self.clear_button = QtWidgets.QToolButton(self)
565+ self.clear_button.setIcon(self.clear_icon)
566+ self.lock_button = QtWidgets.QToolButton(self)
567+ self.lock_button.setIcon(self.unlock_icon)
568+ self.lock_button.setCheckable(True)
569+ self.search_button_layout.addWidget(self.clear_button)
570+ self.search_button_layout.addWidget(self.lock_button)
571+ self.search_button = QtWidgets.QPushButton(self)
572+ self.search_button_layout.addWidget(self.search_button)
573+ self.general_bible_layout.addRow(self.search_button_layout)
574+ self.page_layout.addWidget(self.options_widget)
575+
576+ def setupUi(self):
577+ super().setupUi()
578+ sort_model = QtCore.QSortFilterProxyModel(self.select_book_combo_box)
579+ model = self.select_book_combo_box.model()
580+ # Reparent the combo box model to the sort proxy, otherwise it will be deleted when we change the comobox's
581+ # model
582+ model.setParent(sort_model)
583+ sort_model.setSourceModel(model)
584+ self.select_book_combo_box.setModel(sort_model)
585+
586+ # Signals & Slots
587 # Combo Boxes
588- self.quickVersionComboBox.activated.connect(self.update_auto_completer)
589- self.quickSecondComboBox.activated.connect(self.update_auto_completer)
590- self.advancedVersionComboBox.activated.connect(self.on_advanced_version_combo_box)
591- self.advancedSecondComboBox.activated.connect(self.on_advanced_second_combo_box)
592- self.advanced_book_combo_box.activated.connect(self.on_advanced_book_combo_box)
593- self.advanced_from_chapter.activated.connect(self.on_advanced_from_chapter)
594- self.advanced_from_verse.activated.connect(self.on_advanced_from_verse)
595- self.advanced_to_chapter.activated.connect(self.on_advanced_to_chapter)
596- self.quick_search_edit.searchTypeChanged.connect(self.update_auto_completer)
597- self.quickVersionComboBox.activated.connect(self.update_auto_completer)
598- self.quickStyleComboBox.activated.connect(self.on_quick_style_combo_box_changed)
599- self.advancedStyleComboBox.activated.connect(self.on_advanced_style_combo_box_changed)
600+ self.select_book_combo_box.activated.connect(self.on_advanced_book_combo_box)
601+ self.from_chapter.activated.connect(self.on_from_chapter_activated)
602+ self.from_verse.activated.connect(self.on_from_verse)
603+ self.to_chapter.activated.connect(self.on_to_chapter)
604+ self.version_combo_box.currentIndexChanged.connect(self.on_version_combo_box_index_changed)
605+ self.version_combo_box.currentIndexChanged.connect(self.update_auto_completer)
606+ self.second_combo_box.currentIndexChanged.connect(self.on_second_combo_box_index_changed)
607+ self.second_combo_box.currentIndexChanged.connect(self.update_auto_completer)
608+ self.style_combo_box.currentIndexChanged.connect(self.on_style_combo_box_index_changed)
609+ self.search_edit.searchTypeChanged.connect(self.update_auto_completer)
610 # Buttons
611- self.advancedClearButton.clicked.connect(self.on_advanced_clear_button_clicked)
612- self.quickClearButton.clicked.connect(self.on_clear_button_clicked)
613- self.advancedSearchButton.clicked.connect(self.on_advanced_search_button)
614- self.quickSearchButton.clicked.connect(self.on_quick_search_button)
615+ self.book_order_button.toggled.connect(self.on_book_order_button_toggled)
616+ self.clear_button.clicked.connect(self.on_clear_button_clicked)
617+ self.lock_button.toggled.connect(self.on_lock_button_toggled)
618+ self.search_button.clicked.connect(self.on_search_button_clicked)
619 # Other stuff
620- self.quick_search_edit.returnPressed.connect(self.on_quick_search_button)
621+ self.search_edit.returnPressed.connect(self.on_search_button_clicked)
622 self.search_tab_bar.currentChanged.connect(self.on_search_tab_bar_current_changed)
623- self.quick_search_edit.textChanged.connect(self.on_search_text_edit_changed)
624+ self.search_edit.textChanged.connect(self.on_search_edit_text_changed)
625+
626+ def retranslateUi(self):
627+ log.debug('retranslateUi')
628+ self.chapter_label.setText(translate('BiblesPlugin.MediaItem', 'Chapter:'))
629+ self.verse_label.setText(translate('BiblesPlugin.MediaItem', 'Verse:'))
630+ self.style_combo_box.setItemText(LayoutStyle.VersePerSlide, UiStrings().VersePerSlide)
631+ self.style_combo_box.setItemText(LayoutStyle.VersePerLine, UiStrings().VersePerLine)
632+ self.style_combo_box.setItemText(LayoutStyle.Continuous, UiStrings().Continuous)
633+ self.clear_button.setToolTip(translate('BiblesPlugin.MediaItem', 'Clear the search results.'))
634+ self.lock_button.setToolTip(
635+ translate('BiblesPlugin.MediaItem', 'Toggle to keep or clear the previous results.'))
636+ self.search_button.setText(UiStrings().Search)
637
638 def on_focus(self):
639- if self.quickTab.isVisible():
640- self.quick_search_edit.setFocus()
641- self.quick_search_edit.selectAll()
642+ """
643+ Set focus on the appropriate widget when BibleMediaItem receives focus
644+
645+ Reimplements MediaManagerItem.on_focus()
646+
647+ :return: None
648+ """
649+ if self.search_tab.isVisible():
650+ self.search_edit.setFocus()
651+ self.search_edit.selectAll()
652 else:
653- self.advanced_book_combo_box.setFocus()
654+ self.select_book_combo_box.setFocus()
655
656 def config_update(self):
657+ """
658+ Change the visible widgets when the config changes
659+
660+ :return: None
661+ """
662 log.debug('config_update')
663- if Settings().value(self.settings_section + '/second bibles'):
664- self.quickSecondLabel.setVisible(True)
665- self.quickSecondComboBox.setVisible(True)
666- self.advancedSecondLabel.setVisible(True)
667- self.advancedSecondComboBox.setVisible(True)
668- self.quickSecondLabel.setVisible(True)
669- self.quickSecondComboBox.setVisible(True)
670- else:
671- self.quickSecondLabel.setVisible(False)
672- self.quickSecondComboBox.setVisible(False)
673- self.advancedSecondLabel.setVisible(False)
674- self.advancedSecondComboBox.setVisible(False)
675- self.quickSecondLabel.setVisible(False)
676- self.quickSecondComboBox.setVisible(False)
677- self.quickStyleComboBox.setCurrentIndex(self.settings.layout_style)
678- self.advancedStyleComboBox.setCurrentIndex(self.settings.layout_style)
679-
680- def retranslateUi(self):
681- log.debug('retranslateUi')
682- self.quick_search_label.setText(translate('BiblesPlugin.MediaItem', 'Find:'))
683- self.quickVersionLabel.setText('{version}:'.format(version=UiStrings().Version))
684- self.quickSecondLabel.setText(translate('BiblesPlugin.MediaItem', 'Second:'))
685- self.quickStyleLabel.setText(UiStrings().LayoutStyle)
686- self.quickStyleComboBox.setItemText(LayoutStyle.VersePerSlide, UiStrings().VersePerSlide)
687- self.quickStyleComboBox.setItemText(LayoutStyle.VersePerLine, UiStrings().VersePerLine)
688- self.quickStyleComboBox.setItemText(LayoutStyle.Continuous, UiStrings().Continuous)
689- self.quickClearButton.setToolTip(translate('BiblesPlugin.MediaItem', 'Clear the search results.'))
690- self.quickLockButton.setToolTip(translate('BiblesPlugin.MediaItem',
691- 'Toggle to keep or clear the previous results.'))
692- self.quickSearchButton.setText(UiStrings().Search)
693- self.advanced_book_label.setText(translate('BiblesPlugin.MediaItem', 'Book:'))
694- self.advanced_chapter_label.setText(translate('BiblesPlugin.MediaItem', 'Chapter:'))
695- self.advanced_verse_label.setText(translate('BiblesPlugin.MediaItem', 'Verse:'))
696- self.advanced_from_label.setText(translate('BiblesPlugin.MediaItem', 'From:'))
697- self.advanced_to_label.setText(translate('BiblesPlugin.MediaItem', 'To:'))
698- self.advancedVersionLabel.setText('{version}:'.format(version=UiStrings().Version))
699- self.advancedSecondLabel.setText(translate('BiblesPlugin.MediaItem', 'Second:'))
700- self.advancedStyleLabel.setText(UiStrings().LayoutStyle)
701- self.advancedStyleComboBox.setItemText(LayoutStyle.VersePerSlide, UiStrings().VersePerSlide)
702- self.advancedStyleComboBox.setItemText(LayoutStyle.VersePerLine, UiStrings().VersePerLine)
703- self.advancedStyleComboBox.setItemText(LayoutStyle.Continuous, UiStrings().Continuous)
704- self.advancedClearButton.setToolTip(translate('BiblesPlugin.MediaItem', 'Clear the search results.'))
705- self.advancedLockButton.setToolTip(translate('BiblesPlugin.MediaItem',
706- 'Toggle to keep or clear the previous results.'))
707- self.advancedSearchButton.setText(UiStrings().Search)
708+ visible = Settings().value('{settings_section}/second bibles'.format(settings_section=self.settings_section))
709+ self.general_bible_layout.labelForField(self.second_combo_box).setVisible(visible)
710+ self.second_combo_box.setVisible(visible)
711
712 def initialise(self):
713+ """
714+ Called to complete initialisation that could not be completed in the constructor.
715+
716+ :return: None
717+ """
718 log.debug('bible manager initialise')
719 self.plugin.manager.media = self
720- self.load_bibles()
721- self.quick_search_edit.set_search_types([
722+ self.populate_bible_combo_boxes()
723+ self.search_edit.set_search_types([
724 (BibleSearch.Combined, ':/bibles/bibles_search_combined.png',
725 translate('BiblesPlugin.MediaItem', 'Text or Reference'),
726 translate('BiblesPlugin.MediaItem', 'Text or Reference...')),
727@@ -328,164 +275,107 @@
728 translate('BiblesPlugin.MediaItem', 'Text Search'),
729 translate('BiblesPlugin.MediaItem', 'Search Text...'))
730 ])
731- if Settings().value(self.settings_section + '/reset to combined quick search'):
732- self.quick_search_edit.set_current_search_type(BibleSearch.Combined)
733+ if Settings().value(
734+ '{settings_section}/reset to combined quick search'.format(settings_section=self.settings_section)):
735+ self.search_edit.set_current_search_type(BibleSearch.Combined)
736 self.config_update()
737 log.debug('bible manager initialise complete')
738
739- def load_bibles(self):
740+ def populate_bible_combo_boxes(self):
741+ """
742+ Populate the bible combo boxes with the list of bibles that have been loaded
743+
744+ :return: None
745+ """
746 log.debug('Loading Bibles')
747- self.quickVersionComboBox.clear()
748- self.quickSecondComboBox.clear()
749- self.advancedVersionComboBox.clear()
750- self.advancedSecondComboBox.clear()
751- self.quickSecondComboBox.addItem('')
752- self.advancedSecondComboBox.addItem('')
753+ self.version_combo_box.clear()
754+ self.second_combo_box.clear()
755+ self.second_combo_box.addItem('', None)
756 # Get all bibles and sort the list.
757- bibles = list(self.plugin.manager.get_bibles().keys())
758- bibles = [_f for _f in bibles if _f]
759- bibles.sort(key=get_locale_key)
760- # Load the bibles into the combo boxes.
761- self.quickVersionComboBox.addItems(bibles)
762- self.quickSecondComboBox.addItems(bibles)
763- self.advancedVersionComboBox.addItems(bibles)
764- self.advancedSecondComboBox.addItems(bibles)
765+ bibles = self.plugin.manager.get_bibles()
766+ bibles = [(_f, bibles[_f]) for _f in bibles if _f]
767+ bibles.sort(key=lambda k: get_locale_key(k[0]))
768+ for bible in bibles:
769+ self.version_combo_box.addItem(bible[0], bible[1])
770+ self.second_combo_box.addItem(bible[0], bible[1])
771 # set the default value
772- bible = Settings().value(self.settings_section + '/advanced bible')
773- if bible in bibles:
774- find_and_set_in_combo_box(self.advancedVersionComboBox, bible)
775- self.initialise_advanced_bible(str(bible))
776- elif bibles:
777- self.initialise_advanced_bible(bibles[0])
778- bible = Settings().value(self.settings_section + '/quick bible')
779- find_and_set_in_combo_box(self.quickVersionComboBox, bible)
780-
781- def reload_bibles(self, process=False):
782+ bible = Settings().value('{settings_section}/primary bible'.format(settings_section=self.settings_section))
783+ find_and_set_in_combo_box(self.version_combo_box, bible)
784+
785+ def reload_bibles(self):
786+ """
787+ Reload the bibles and update the combo boxes
788+
789+ :return: None
790+ """
791 log.debug('Reloading Bibles')
792 self.plugin.manager.reload_bibles()
793- self.load_bibles()
794- # If called from first time wizard re-run, process any new bibles.
795- if process:
796- self.plugin.app_startup()
797- self.update_auto_completer()
798-
799- def initialise_advanced_bible(self, bible, last_book_id=None):
800+ self.populate_bible_combo_boxes()
801+
802+ def get_common_books(self, first_bible, second_bible=None):
803+ """
804+ Return a list of common books between two bibles.
805+
806+ :param first_bible: The first bible (BibleDB)
807+ :param second_bible: The second bible. (Optional, BibleDB
808+ :return: A list of common books between the two bibles. Or if only one bible is supplied a list of that bibles
809+ books (list of Book objects)
810+ """
811+ if not second_bible:
812+ return first_bible.get_books()
813+ book_data = []
814+ for book in first_bible.get_books():
815+ for second_book in second_bible.get_books():
816+ if book.book_reference_id == second_book.book_reference_id:
817+ book_data.append(book)
818+ return book_data
819+
820+ def initialise_advanced_bible(self, last_book=None):
821 """
822 This initialises the given bible, which means that its book names and their chapter numbers is added to the
823- combo boxes on the 'Advanced Search' Tab. This is not of any importance of the 'Quick Search' Tab.
824+ combo boxes on the 'Select' Tab. This is not of any importance of the 'Search' Tab.
825
826- :param bible: The bible to initialise (unicode).
827 :param last_book_id: The "book reference id" of the book which is chosen at the moment. (int)
828+ :return: None
829 """
830- log.debug('initialise_advanced_bible {bible}, {ref}'.format(bible=bible, ref=last_book_id))
831- book_data = self.plugin.manager.get_books(bible)
832- second_bible = self.advancedSecondComboBox.currentText()
833- if second_bible != '':
834- second_book_data = self.plugin.manager.get_books(second_bible)
835- book_data_temp = []
836- for book in book_data:
837- for second_book in second_book_data:
838- if book['book_reference_id'] == second_book['book_reference_id']:
839- book_data_temp.append(book)
840- book_data = book_data_temp
841- self.advanced_book_combo_box.clear()
842- first = True
843- initialise_chapter_verse = False
844- language_selection = self.plugin.manager.get_language_selection(bible)
845- book_names = BibleStrings().BookNames
846+ log.debug('initialise_advanced_bible {bible}, {ref}'.format(bible=self.bible, ref=last_book))
847+ self.select_book_combo_box.clear()
848+ if self.bible is None:
849+ return
850+ book_data = self.get_common_books(self.bible, self.second_bible)
851+ language_selection = self.plugin.manager.get_language_selection(self.bible.name)
852+ self.select_book_combo_box.model().setDynamicSortFilter(False)
853 for book in book_data:
854- row = self.advanced_book_combo_box.count()
855- if language_selection == LanguageSelection.Bible:
856- self.advanced_book_combo_box.addItem(book['name'])
857- elif language_selection == LanguageSelection.Application:
858- data = BiblesResourcesDB.get_book_by_id(book['book_reference_id'])
859- self.advanced_book_combo_box.addItem(book_names[data['abbreviation']])
860- elif language_selection == LanguageSelection.English:
861- data = BiblesResourcesDB.get_book_by_id(book['book_reference_id'])
862- self.advanced_book_combo_box.addItem(data['name'])
863- self.advanced_book_combo_box.setItemData(row, book['book_reference_id'])
864- if first:
865- first = False
866- first_book = book
867- initialise_chapter_verse = True
868- if last_book_id and last_book_id == int(book['book_reference_id']):
869- index = self.advanced_book_combo_box.findData(book['book_reference_id'])
870- if index == -1:
871- # Not Found.
872- index = 0
873- self.advanced_book_combo_box.setCurrentIndex(index)
874- initialise_chapter_verse = False
875- if initialise_chapter_verse:
876- self.initialise_chapter_verse(bible, first_book['name'], first_book['book_reference_id'])
877-
878- def initialise_chapter_verse(self, bible, book, book_ref_id):
879- log.debug('initialise_chapter_verse {bible}, {book}, {ref}'.format(bible=bible, book=book, ref=book_ref_id))
880- book = self.plugin.manager.get_book_by_id(bible, book_ref_id)
881- self.chapter_count = self.plugin.manager.get_chapter_count(bible, book)
882- verse_count = self.plugin.manager.get_verse_count_by_book_ref_id(bible, book_ref_id, 1)
883- if verse_count == 0:
884- self.advancedSearchButton.setEnabled(False)
885- log.warning('Not enough chapters in %s', book_ref_id)
886- critical_error_message_box(message=translate('BiblesPlugin.MediaItem', 'Bible not fully loaded.'))
887- else:
888- self.advancedSearchButton.setEnabled(True)
889- self.adjust_combo_box(1, self.chapter_count, self.advanced_from_chapter)
890- self.adjust_combo_box(1, self.chapter_count, self.advanced_to_chapter)
891- self.adjust_combo_box(1, verse_count, self.advanced_from_verse)
892- self.adjust_combo_box(1, verse_count, self.advanced_to_verse)
893+ self.select_book_combo_box.addItem(book.get_name(language_selection), book.book_reference_id)
894+ self.select_book_combo_box.model().setDynamicSortFilter(True)
895+ if last_book:
896+ index = self.select_book_combo_box.findData(last_book)
897+ self.select_book_combo_box.setCurrentIndex(index if index != -1 else 0)
898+ self.on_advanced_book_combo_box()
899
900 def update_auto_completer(self):
901 """
902 This updates the bible book completion list for the search field. The completion depends on the bible. It is
903 only updated when we are doing reference or combined search, in text search the completion list is removed.
904+
905+ :return: None
906 """
907- log.debug('update_auto_completer')
908- # Save the current bible to the configuration.
909- Settings().setValue('{section}/quick bible'.format(section=self.settings_section),
910- self.quickVersionComboBox.currentText())
911 books = []
912 # We have to do a 'Reference Search' (Or as part of Combined Search).
913- if self.quick_search_edit.current_search_type() is not BibleSearch.Text:
914- bibles = self.plugin.manager.get_bibles()
915- bible = self.quickVersionComboBox.currentText()
916- if bible:
917- book_data = bibles[bible].get_books()
918- second_bible = self.quickSecondComboBox.currentText()
919- if second_bible != '':
920- second_book_data = bibles[second_bible].get_books()
921- book_data_temp = []
922- for book in book_data:
923- for second_book in second_book_data:
924- if book.book_reference_id == second_book.book_reference_id:
925- book_data_temp.append(book)
926- book_data = book_data_temp
927- language_selection = self.plugin.manager.get_language_selection(bible)
928- if language_selection == LanguageSelection.Bible:
929- books = [book.name + ' ' for book in book_data]
930- elif language_selection == LanguageSelection.Application:
931- book_names = BibleStrings().BookNames
932- for book in book_data:
933- data = BiblesResourcesDB.get_book_by_id(book.book_reference_id)
934- books.append(str(book_names[data['abbreviation']]) + ' ')
935- elif language_selection == LanguageSelection.English:
936- for book in book_data:
937- data = BiblesResourcesDB.get_book_by_id(book.book_reference_id)
938- books.append(data['name'] + ' ')
939+ if self.search_edit.current_search_type() is not BibleSearch.Text:
940+ if self.bible:
941+ book_data = self.get_common_books(self.bible, self.second_bible)
942+ language_selection = self.plugin.manager.get_language_selection(self.bible.name)
943+ books = [book.get_name(language_selection) for book in book_data]
944 books.sort(key=get_locale_key)
945- set_case_insensitive_completer(books, self.quick_search_edit)
946-
947- def on_second_bible_combobox_index_changed(self, selection):
948- """
949- Activate the style combobox only when no second bible is selected
950- """
951- if selection == 0:
952- self.quickStyleComboBox.setEnabled(True)
953- self.advancedStyleComboBox.setEnabled(True)
954- else:
955- self.quickStyleComboBox.setEnabled(False)
956- self.advancedStyleComboBox.setEnabled(False)
957+ set_case_insensitive_completer(books, self.search_edit)
958
959 def on_import_click(self):
960+ """
961+ Create, if not already, the `BibleImportForm` and execute it
962+
963+ :return: None
964+ """
965 if not hasattr(self, 'import_wizard'):
966 self.import_wizard = BibleImportForm(self, self.plugin.manager, self.plugin)
967 # If the import was not cancelled then reload.
968@@ -493,141 +383,199 @@
969 self.reload_bibles()
970
971 def on_edit_click(self):
972- if self.quickTab.isVisible():
973- bible = self.quickVersionComboBox.currentText()
974- elif self.advancedTab.isVisible():
975- bible = self.advancedVersionComboBox.currentText()
976- if bible:
977+ """
978+ Load the EditBibleForm and reload the bibles if the user accepts it
979+
980+ :return: None
981+ """
982+ if self.bible:
983 self.edit_bible_form = EditBibleForm(self, self.main_window, self.plugin.manager)
984- self.edit_bible_form.load_bible(bible)
985+ self.edit_bible_form.load_bible(self.bible.name)
986 if self.edit_bible_form.exec():
987 self.reload_bibles()
988
989 def on_delete_click(self):
990 """
991- When the delete button is pressed
992+ Confirm that the user wants to delete the main bible
993+
994+ :return: None
995 """
996- bible = None
997- if self.quickTab.isVisible():
998- bible = self.quickVersionComboBox.currentText()
999- elif self.advancedTab.isVisible():
1000- bible = self.advancedVersionComboBox.currentText()
1001- if bible:
1002+ if self.bible:
1003 if QtWidgets.QMessageBox.question(
1004- self, UiStrings().ConfirmDelete,
1005- translate('BiblesPlugin.MediaItem',
1006- 'Are you sure you want to completely delete "{bible}" Bible '
1007- 'from OpenLP?\n\nYou will need to re-import this Bible to use it '
1008- 'again.').format(bible=bible),
1009- QtWidgets.QMessageBox.StandardButtons(QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No),
1010- QtWidgets.QMessageBox.Yes) == QtWidgets.QMessageBox.No:
1011+ self, UiStrings().ConfirmDelete,
1012+ translate('BiblesPlugin.MediaItem',
1013+ 'Are you sure you want to completely delete "{bible}" Bible from OpenLP?\n\n'
1014+ 'You will need to re-import this Bible to use it again.').format(bible=self.bible.name),
1015+ defaultButton=QtWidgets.QMessageBox.No) == QtWidgets.QMessageBox.No:
1016 return
1017- self.plugin.manager.delete_bible(bible)
1018+ self.plugin.manager.delete_bible(self.bible.name)
1019 self.reload_bibles()
1020
1021 def on_search_tab_bar_current_changed(self, index):
1022- if index == 0:
1023- self.advancedTab.setVisible(False)
1024- self.quickTab.setVisible(True)
1025- self.quick_search_edit.setFocus()
1026+ """
1027+ Show the selected tab and set focus to it
1028+
1029+ :param index: The tab selected (int)
1030+ :return: None
1031+ """
1032+ search_tab = index == 0
1033+ self.search_tab.setVisible(search_tab)
1034+ self.select_tab.setVisible(not search_tab)
1035+ self.on_focus()
1036+
1037+ def on_book_order_button_toggled(self, checked):
1038+ """
1039+ Change the sort order of the book names
1040+
1041+ :param checked: Indicates if the button is checked or not (Bool)
1042+ :return: None
1043+ """
1044+ if checked:
1045+ self.select_book_combo_box.model().sort(0)
1046 else:
1047- self.quickTab.setVisible(False)
1048- self.advancedTab.setVisible(True)
1049- self.advanced_book_combo_box.setFocus()
1050+ # -1 Removes the sorting, and returns the items to the order they were added in
1051+ self.select_book_combo_box.model().sort(-1)
1052
1053 def on_clear_button_clicked(self):
1054- # Clear the list, then set the "No search Results" message, then clear the text field and give it focus.
1055- self.list_view.clear()
1056- self.quick_search_edit.clear()
1057- self.quick_search_edit.setFocus()
1058+ """
1059+ Clear the list_view and the search_edit
1060
1061- def on_advanced_clear_button_clicked(self):
1062- # The same as the on_clear_button_clicked, but gives focus to Book name field in "Select" (advanced).
1063+ :return: None
1064+ """
1065 self.list_view.clear()
1066- self.advanced_book_combo_box.setFocus()
1067+ self.search_edit.clear()
1068+ self.on_focus()
1069
1070 def on_lock_button_toggled(self, checked):
1071 """
1072 Toggle the lock button, if Search tab is used, set focus to search field.
1073- :param checked: The state of the toggle button. bool
1074+
1075+ :param checked: The state of the toggle button. (bool)
1076 :return: None
1077 """
1078+ self.list_view.locked = checked
1079 if checked:
1080 self.sender().setIcon(self.lock_icon)
1081 else:
1082 self.sender().setIcon(self.unlock_icon)
1083- if self.quickTab.isVisible():
1084- self.quick_search_edit.setFocus()
1085-
1086- def on_quick_style_combo_box_changed(self):
1087- self.settings.layout_style = self.quickStyleComboBox.currentIndex()
1088- self.advancedStyleComboBox.setCurrentIndex(self.settings.layout_style)
1089- self.settings.layout_style_combo_box.setCurrentIndex(self.settings.layout_style)
1090- Settings().setValue(self.settings_section + '/verse layout style', self.settings.layout_style)
1091-
1092- def on_advanced_style_combo_box_changed(self):
1093- self.settings.layout_style = self.advancedStyleComboBox.currentIndex()
1094- self.quickStyleComboBox.setCurrentIndex(self.settings.layout_style)
1095- self.settings.layout_style_combo_box.setCurrentIndex(self.settings.layout_style)
1096- Settings().setValue(self.settings_section + '/verse layout style', self.settings.layout_style)
1097-
1098- def on_advanced_version_combo_box(self):
1099- Settings().setValue(self.settings_section + '/advanced bible', self.advancedVersionComboBox.currentText())
1100- self.initialise_advanced_bible(
1101- self.advancedVersionComboBox.currentText(),
1102- self.advanced_book_combo_box.itemData(int(self.advanced_book_combo_box.currentIndex())))
1103-
1104- def on_advanced_second_combo_box(self):
1105- self.initialise_advanced_bible(
1106- self.advancedVersionComboBox.currentText(),
1107- self.advanced_book_combo_box.itemData(int(self.advanced_book_combo_box.currentIndex())))
1108+
1109+ def on_style_combo_box_index_changed(self, index):
1110+ """
1111+ Change the layout style and save the setting
1112+
1113+ :param index: The index of the current item in the combobox (int)
1114+ :return: None
1115+ """
1116+ # TODO: Change layout_style to a property
1117+ self.settings.layout_style = index
1118+ self.settings.layout_style_combo_box.setCurrentIndex(index)
1119+ Settings().setValue('{section}/verse layout style'.format(section=self.settings_section), index)
1120+
1121+ def on_version_combo_box_index_changed(self):
1122+ """
1123+ Update the main bible and save it to settings
1124+
1125+ :return: None
1126+ """
1127+ self.bible = self.version_combo_box.currentData()
1128+ if self.bible is not None:
1129+ Settings().setValue('{section}/primary bible'.format(section=self.settings_section), self.bible.name)
1130+ self.initialise_advanced_bible(self.select_book_combo_box.currentData())
1131+
1132+ def on_second_combo_box_index_changed(self, selection):
1133+ """
1134+ Update the second bible. If changing from single to dual bible modes as if the user wants to clear the search
1135+ results, if not revert to the previously selected bible
1136+
1137+ :return: None
1138+ """
1139+ new_selection = self.second_combo_box.currentData()
1140+ if self.list_view.count():
1141+ # Exclusive or (^) the new and previous selections to detect if the user has switched between single and
1142+ # dual bible mode
1143+ if (new_selection is None) ^ (self.second_bible is None):
1144+ if critical_error_message_box(
1145+ message=translate('BiblesPlugin.MediaItem',
1146+ 'OpenLP cannot combine single and dual Bible verse search results. '
1147+ 'Do you want to clear your search results and start a new search?'),
1148+ parent=self, question=True) == QtWidgets.QMessageBox.Yes:
1149+ self.list_view.clear(override_lock=True)
1150+ else:
1151+ self.second_combo_box.setCurrentIndex(self.second_combo_box.findData(self.second_bible))
1152+ return
1153+ self.second_bible = new_selection
1154+ if new_selection is None:
1155+ self.style_combo_box.setEnabled(True)
1156+ else:
1157+ self.style_combo_box.setEnabled(False)
1158+ self.initialise_advanced_bible(self.select_book_combo_box.currentData())
1159
1160 def on_advanced_book_combo_box(self):
1161- item = int(self.advanced_book_combo_box.currentIndex())
1162- self.initialise_chapter_verse(
1163- self.advancedVersionComboBox.currentText(),
1164- self.advanced_book_combo_box.currentText(),
1165- self.advanced_book_combo_box.itemData(item))
1166-
1167- def on_advanced_from_verse(self):
1168- chapter_from = int(self.advanced_from_chapter.currentText())
1169- chapter_to = int(self.advanced_to_chapter.currentText())
1170+ """
1171+ Update the verse selection boxes
1172+
1173+ :return: None
1174+ """
1175+ book_ref_id = self.select_book_combo_box.currentData()
1176+ book = self.plugin.manager.get_book_by_id(self.bible.name, book_ref_id)
1177+ self.chapter_count = self.plugin.manager.get_chapter_count(self.bible.name, book)
1178+ verse_count = self.plugin.manager.get_verse_count_by_book_ref_id(self.bible.name, book_ref_id, 1)
1179+ if verse_count == 0:
1180+ self.search_button.setEnabled(False)
1181+ log.warning('Not enough chapters in %s', book_ref_id)
1182+ critical_error_message_box(message=translate('BiblesPlugin.MediaItem', 'Bible not fully loaded.'))
1183+ else:
1184+ self.search_button.setEnabled(True)
1185+ self.adjust_combo_box(1, self.chapter_count, self.from_chapter)
1186+ self.adjust_combo_box(1, self.chapter_count, self.to_chapter)
1187+ self.adjust_combo_box(1, verse_count, self.from_verse)
1188+ self.adjust_combo_box(1, verse_count, self.to_verse)
1189+
1190+ def on_from_chapter_activated(self):
1191+ """
1192+ Update the verse selection boxes
1193+
1194+ :return: None
1195+ """
1196+ book_ref_id = self.select_book_combo_box.currentData()
1197+ chapter_from = self.from_chapter.currentData()
1198+ chapter_to = self.to_chapter.currentData()
1199+ verse_count = self.plugin.manager.get_verse_count_by_book_ref_id(self.bible.name, book_ref_id, chapter_from)
1200+ self.adjust_combo_box(1, verse_count, self.from_verse)
1201+ if chapter_from >= chapter_to:
1202+ self.adjust_combo_box(1, verse_count, self.to_verse, chapter_from == chapter_to)
1203+ self.adjust_combo_box(chapter_from, self.chapter_count, self.to_chapter, chapter_from < chapter_to)
1204+
1205+ def on_from_verse(self):
1206+ """
1207+ Update the verse selection boxes
1208+
1209+ :return: None
1210+ """
1211+ chapter_from = self.from_chapter.currentData()
1212+ chapter_to = self.to_chapter.currentData()
1213 if chapter_from == chapter_to:
1214- bible = self.advancedVersionComboBox.currentText()
1215- book_ref_id = self.advanced_book_combo_box.itemData(int(self.advanced_book_combo_box.currentIndex()))
1216- verse_from = int(self.advanced_from_verse.currentText())
1217- verse_count = self.plugin.manager.get_verse_count_by_book_ref_id(bible, book_ref_id, chapter_to)
1218- self.adjust_combo_box(verse_from, verse_count, self.advanced_to_verse, True)
1219-
1220- def on_advanced_to_chapter(self):
1221- bible = self.advancedVersionComboBox.currentText()
1222- book_ref_id = self.advanced_book_combo_box.itemData(int(self.advanced_book_combo_box.currentIndex()))
1223- chapter_from = int(self.advanced_from_chapter.currentText())
1224- chapter_to = int(self.advanced_to_chapter.currentText())
1225- verse_from = int(self.advanced_from_verse.currentText())
1226- verse_to = int(self.advanced_to_verse.currentText())
1227- verse_count = self.plugin.manager.get_verse_count_by_book_ref_id(bible, book_ref_id, chapter_to)
1228+ book_ref_id = self.select_book_combo_box.currentData()
1229+ verse_from = self.from_verse.currentData()
1230+ verse_count = self.plugin.manager.get_verse_count_by_book_ref_id(self.bible.name, book_ref_id, chapter_to)
1231+ self.adjust_combo_box(verse_from, verse_count, self.to_verse, True)
1232+
1233+ def on_to_chapter(self):
1234+ """
1235+ Update the verse selection boxes
1236+
1237+ :return: None
1238+ """
1239+ book_ref_id = self.select_book_combo_box.currentData()
1240+ chapter_from = self.from_chapter.currentData()
1241+ chapter_to = self.to_chapter.currentData()
1242+ verse_from = self.from_verse.currentData()
1243+ verse_to = self.to_verse.currentData()
1244+ verse_count = self.plugin.manager.get_verse_count_by_book_ref_id(self.bible.name, book_ref_id, chapter_to)
1245 if chapter_from == chapter_to and verse_from > verse_to:
1246- self.adjust_combo_box(verse_from, verse_count, self.advanced_to_verse)
1247- else:
1248- self.adjust_combo_box(1, verse_count, self.advanced_to_verse)
1249-
1250- def on_advanced_from_chapter(self):
1251- bible = self.advancedVersionComboBox.currentText()
1252- book_ref_id = self.advanced_book_combo_box.itemData(
1253- int(self.advanced_book_combo_box.currentIndex()))
1254- chapter_from = int(self.advanced_from_chapter.currentText())
1255- chapter_to = int(self.advanced_to_chapter.currentText())
1256- verse_count = self.plugin.manager.get_verse_count_by_book_ref_id(bible, book_ref_id, chapter_from)
1257- self.adjust_combo_box(1, verse_count, self.advanced_from_verse)
1258- if chapter_from > chapter_to:
1259- self.adjust_combo_box(1, verse_count, self.advanced_to_verse)
1260- self.adjust_combo_box(chapter_from, self.chapter_count, self.advanced_to_chapter)
1261- elif chapter_from == chapter_to:
1262- self.adjust_combo_box(chapter_from, self.chapter_count, self.advanced_to_chapter)
1263- self.adjust_combo_box(1, verse_count, self.advanced_to_verse, True)
1264- else:
1265- self.adjust_combo_box(chapter_from, self.chapter_count, self.advanced_to_chapter, True)
1266+ self.adjust_combo_box(verse_from, verse_count, self.to_verse)
1267+ else:
1268+ self.adjust_combo_box(1, verse_count, self.to_verse)
1269
1270 def adjust_combo_box(self, range_from, range_to, combo, restore=False):
1271 """
1272@@ -640,380 +588,195 @@
1273 """
1274 log.debug('adjust_combo_box {box}, {start}, {end}'.format(box=combo, start=range_from, end=range_to))
1275 if restore:
1276- old_text = combo.currentText()
1277+ old_selection = combo.currentData()
1278 combo.clear()
1279- combo.addItems(list(map(str, list(range(range_from, range_to + 1)))))
1280- if restore and combo.findText(old_text) != -1:
1281- combo.setCurrentIndex(combo.findText(old_text))
1282-
1283- def on_advanced_search_button(self):
1284- """
1285- Does an advanced search and saves the search results.
1286- """
1287- log.debug('Advanced Search Button clicked')
1288- self.advancedSearchButton.setEnabled(False)
1289+ for item in range(range_from, range_to + 1):
1290+ combo.addItem(str(item), item)
1291+ if restore:
1292+ index = combo.findData(old_selection)
1293+ combo.setCurrentIndex(index if index != -1 else 0)
1294+
1295+ def on_search_button_clicked(self):
1296+ """
1297+ Call the correct search function depending on which tab the user is using
1298+
1299+ :return: None
1300+ """
1301+ if not self.bible:
1302+ self.main_window.information_message(UiStrings().BibleNoBiblesTitle, UiStrings().BibleNoBibles)
1303+ return
1304+ self.search_button.setEnabled(False)
1305+ self.application.set_busy_cursor()
1306 self.application.process_events()
1307- bible = self.advancedVersionComboBox.currentText()
1308- second_bible = self.advancedSecondComboBox.currentText()
1309- book = self.advanced_book_combo_box.currentText()
1310- book_ref_id = self.advanced_book_combo_box.itemData(int(self.advanced_book_combo_box.currentIndex()))
1311- chapter_from = self.advanced_from_chapter.currentText()
1312- chapter_to = self.advanced_to_chapter.currentText()
1313- verse_from = self.advanced_from_verse.currentText()
1314- verse_to = self.advanced_to_verse.currentText()
1315- verse_separator = get_reference_separator('sep_v_display')
1316- range_separator = get_reference_separator('sep_r_display')
1317- verse_range = chapter_from + verse_separator + verse_from + range_separator + chapter_to + \
1318- verse_separator + verse_to
1319- verse_text = '{book} {verse}'.format(book=book, verse=verse_range)
1320- self.application.set_busy_cursor()
1321- self.search_results = self.plugin.manager.get_verses(bible, verse_text, book_ref_id)
1322- if second_bible:
1323- self.second_search_results = self.plugin.manager.get_verses(second_bible, verse_text, book_ref_id)
1324- if not self.advancedLockButton.isChecked():
1325- self.list_view.clear()
1326- if self.list_view.count() != 0:
1327- self.__check_second_bible(bible, second_bible)
1328- elif self.search_results:
1329- self.display_results(bible, second_bible)
1330- self.advancedSearchButton.setEnabled(True)
1331+ if self.search_tab.isVisible():
1332+ self.text_search()
1333+ elif self.select_tab.isVisible():
1334+ self.select_search()
1335+ self.search_button.setEnabled(True)
1336 self.application.set_normal_cursor()
1337
1338- def on_quick_reference_search(self):
1339+ def select_search(self):
1340+ """
1341+ Preform a search using the passage selected on the `Select` tab
1342+
1343+ :return: None
1344+ """
1345+ verse_range = self.plugin.manager.process_verse_range(
1346+ self.select_book_combo_box.currentData(), self.from_chapter.currentData(), self.from_verse.currentData(),
1347+ self.to_chapter.currentData(), self.to_verse.currentData())
1348+ self.search_results = self.plugin.manager.get_verses(self.bible.name, verse_range, False)
1349+ if self.second_bible:
1350+ self.second_search_results = self.plugin.manager.get_verses(self.second_bible.name, verse_range, False)
1351+ self.display_results()
1352+
1353+ def text_reference_search(self, search_text):
1354 """
1355 We are doing a 'Reference Search'.
1356- This search is called on def on_quick_search_button by Quick Reference and Combined Searches.
1357- """
1358- # Set Bibles to use the text input from Quick search field.
1359- bible = self.quickVersionComboBox.currentText()
1360- second_bible = self.quickSecondComboBox.currentText()
1361- """
1362- Get input from field and replace 'A-Z + . ' with ''
1363- This will check if field has any '.' after A-Z and removes them. Eg. Gen. 1 = Ge 1 = Genesis 1
1364- If Book name has '.' after number. eg. 1. Genesis, the search fails without the dot, and vice versa.
1365- A better solution would be to make '.' optional in the search results. Current solution was easier to code.
1366- """
1367- text = self.quick_search_edit.text()
1368- text = re.sub('\D[.]\s', ' ', text)
1369- # This is triggered on reference search, use the search from manager.py
1370- if self.quick_search_edit.current_search_type() != BibleSearch.Text:
1371- self.search_results = self.plugin.manager.get_verses(bible, text)
1372- if second_bible and self.search_results:
1373- self.second_search_results = \
1374- self.plugin.manager.get_verses(second_bible, text, self.search_results[0].book.book_reference_id)
1375-
1376- def on_quick_text_search(self):
1377+ This search is called on def text_search by Reference and Combined Searches.
1378+
1379+ :return: None
1380+ """
1381+ verse_refs = self.plugin.manager.parse_ref(self.bible.name, search_text)
1382+ self.search_results = self.plugin.manager.get_verses(self.bible.name, verse_refs, True)
1383+ if self.second_bible and self.search_results:
1384+ self.search_results = self.plugin.manager.get_verses(self.second_bible.name, verse_refs, True)
1385+ self.display_results()
1386+
1387+ def on_text_search(self, text, search_while_type=False):
1388 """
1389 We are doing a 'Text Search'.
1390- This search is called on def on_quick_search_button by Quick Text and Combined Searches.
1391+ This search is called on def text_search by 'Search' Text and Combined Searches.
1392 """
1393- # Set Bibles to use the text input from Quick search field.
1394- bible = self.quickVersionComboBox.currentText()
1395- second_bible = self.quickSecondComboBox.currentText()
1396- text = self.quick_search_edit.text()
1397- # If Text search ends with "," OpenLP will crash, prevent this from happening by removing all ","s.
1398- text = re.sub('[,]', '', text)
1399- self.application.set_busy_cursor()
1400- # Get Bibles list
1401- bibles = self.plugin.manager.get_bibles()
1402- # Add results to "search_results"
1403- self.search_results = self.plugin.manager.verse_search(bible, second_bible, text)
1404- if second_bible and self.search_results:
1405- # new_search_results is needed to make sure 2nd bible contains all verses. (And counting them on error)
1406- text = []
1407- new_search_results = []
1408- count = 0
1409- passage_not_found = False
1410- # Search second bible for results of search_results to make sure everythigns there.
1411- # Count all the unfound passages.
1412+ self.search_results = self.plugin.manager.verse_search(self.bible.name, text)
1413+ if self.second_bible and self.search_results:
1414+ filtered_search_results = []
1415+ not_found_count = 0
1416 for verse in self.search_results:
1417- db_book = bibles[second_bible].get_book_by_book_ref_id(verse.book.book_reference_id)
1418- if not db_book:
1419- log.debug('Passage "{name} {chapter:d}:{verse:d}" not found in '
1420- 'Second Bible'.format(name=verse.book.name, chapter=verse.chapter, verse=verse.verse))
1421- passage_not_found = True
1422- count += 1
1423- continue
1424- new_search_results.append(verse)
1425- text.append((verse.book.book_reference_id, verse.chapter, verse.verse, verse.verse))
1426- if passage_not_found:
1427- # This is for the 2nd Bible.
1428+ second_verse = self.second_bible.get_verses(
1429+ [(verse.book.book_reference_id, verse.chapter, verse.verse, verse.verse)], False)
1430+ if second_verse:
1431+ filtered_search_results.append(verse)
1432+ self.second_search_results += second_verse
1433+ else:
1434+ log.debug('Verse "{name} {chapter:d}:{verse:d}" not found in Second Bible "{bible_name}"'.format(
1435+ name=verse.book.name, chapter=verse.chapter,
1436+ verse=verse.verse, bible_name=self.second_bible.name))
1437+ not_found_count += 1
1438+ self.search_results = filtered_search_results
1439+ if not_found_count != 0 and not search_while_type:
1440 self.main_window.information_message(
1441- translate('BiblesPlugin.MediaItem', 'Information'),
1442- translate('BiblesPlugin.MediaItem', 'The second Bible does not contain all the verses '
1443- 'that are in the main Bible.\nOnly verses found in both Bibles'
1444- ' will be shown.\n\n{count:d} verses have not been included '
1445- 'in the results.').format(count=count))
1446- # Join the searches so only verses that are found on both Bibles are shown.
1447- self.search_results = new_search_results
1448- self.second_search_results = bibles[second_bible].get_verses(text)
1449-
1450- def on_quick_text_search_while_typing(self):
1451- """
1452- We are doing a 'Text Search' while typing
1453- Call the verse_search_while_typing from manager.py
1454- It does not show web bible errors while typing.
1455- (It would result in the error popping every time a char is entered or removed)
1456- """
1457- # Set Bibles to use the text input from Quick search field.
1458- bible = self.quickVersionComboBox.currentText()
1459- second_bible = self.quickSecondComboBox.currentText()
1460- text = self.quick_search_edit.text()
1461- # If Text search ends with "," OpenLP will crash, prevent this from happening by removing all ","s.
1462- text = re.sub('[,]', '', text)
1463- self.application.set_busy_cursor()
1464- # Get Bibles list
1465- bibles = self.plugin.manager.get_bibles()
1466- # Add results to "search_results"
1467- self.search_results = self.plugin.manager.verse_search_while_typing(bible, second_bible, text)
1468- if second_bible and self.search_results:
1469- # new_search_results is needed to make sure 2nd bible contains all verses. (And counting them on error)
1470- text = []
1471- new_search_results = []
1472- count = 0
1473- passage_not_found = False
1474- # Search second bible for results of search_results to make sure everythigns there.
1475- # Count all the unfound passages. Even thou no error is shown, this needs to be done or
1476- # the code breaks later on.
1477- for verse in self.search_results:
1478- db_book = bibles[second_bible].get_book_by_book_ref_id(verse.book.book_reference_id)
1479- if not db_book:
1480- log.debug('Passage ("{versebookname}","{versechapter}","{verseverse}") not found in Second Bible'
1481- .format(versebookname=verse.book.name, versechapter='verse.chapter',
1482- verseverse=verse.verse))
1483- count += 1
1484- continue
1485- new_search_results.append(verse)
1486- text.append((verse.book.book_reference_id, verse.chapter, verse.verse, verse.verse))
1487- # Join the searches so only verses that are found on both Bibles are shown.
1488- self.search_results = new_search_results
1489- self.second_search_results = bibles[second_bible].get_verses(text)
1490-
1491- def on_quick_search_button(self):
1492- """
1493- This triggers the proper Quick search based on which search type is used.
1494+ translate('BiblesPlugin.MediaItem', 'Verses not found'),
1495+ translate('BiblesPlugin.MediaItem',
1496+ 'The second Bible "{second_name}" does not contain all the verses that are in the main '
1497+ 'Bible "{name}".\nOnly verses found in both Bibles will be shown.\n\n'
1498+ '{count:d} verses have not been included in the results.'
1499+ ).format(second_name=self.second_bible.name, name=self.bible.name, count=not_found_count))
1500+ self.display_results()
1501+
1502+ def text_search(self, search_while_type=False):
1503+ """
1504+ This triggers the proper 'Search' search based on which search type is used.
1505 "Eg. "Reference Search", "Text Search" or "Combined search".
1506 """
1507- log.debug('Quick Search Button clicked')
1508- self.quickSearchButton.setEnabled(False)
1509- self.application.process_events()
1510- bible = self.quickVersionComboBox.currentText()
1511- second_bible = self.quickSecondComboBox.currentText()
1512- text = self.quick_search_edit.text()
1513- if self.quick_search_edit.current_search_type() == BibleSearch.Reference:
1514- # We are doing a 'Reference Search'. (Get script from def on_quick_reference_search)
1515- self.on_quick_reference_search()
1516- # Get reference separators from settings.
1517- if not self.search_results:
1518- reference_separators = {
1519- 'verse': get_reference_separator('sep_v_display'),
1520- 'range': get_reference_separator('sep_r_display'),
1521- 'list': get_reference_separator('sep_l_display')}
1522+ log.debug('text_search called')
1523+ text = self.search_edit.text()
1524+ if text == '':
1525+ self.list_view.clear()
1526+ return
1527+ self.list_view.clear(search_while_typing=search_while_type)
1528+ if self.search_edit.current_search_type() == BibleSearch.Reference:
1529+ if get_reference_match('full').match(text):
1530+ # Valid reference found. Do reference search.
1531+ self.text_reference_search(text)
1532+ elif not search_while_type:
1533 self.main_window.information_message(
1534 translate('BiblesPlugin.BibleManager', 'Scripture Reference Error'),
1535- translate('BiblesPlugin.BibleManager', '<strong>OpenLP couldn’t find anything '
1536- 'with your search.<br><br>'
1537- 'Please make sure that your reference follows '
1538- 'one of these patterns:</strong><br><br>%s'
1539- % UiStrings().BibleScriptureError % reference_separators))
1540- elif self.quick_search_edit.current_search_type() == BibleSearch.Text:
1541- # We are doing a 'Text Search'. (Get script from def on_quick_text_search)
1542- self.on_quick_text_search()
1543- if not self.search_results and len(text) - text.count(' ') < 3 and bible:
1544- self.main_window.information_message(
1545- UiStrings().BibleShortSearchTitle,
1546- UiStrings().BibleShortSearch)
1547- elif self.quick_search_edit.current_search_type() == BibleSearch.Combined:
1548- # We are doing a 'Combined search'. Starting with reference search.
1549- # Perform only if text contains any numbers
1550- if (char.isdigit() for char in text):
1551- self.on_quick_reference_search()
1552- """
1553- If results are found, search will be finalized.
1554- This check needs to be here in order to avoid duplicate errors.
1555- If keyword is shorter than 3 (not including spaces), message is given. It's actually possible to find
1556- verses with less than 3 chars (Eg. G1 = Genesis 1) thus this error is not shown if any results are found.
1557- if no Bibles are installed, this message is not shown - "No bibles" message is shown instead.
1558- """
1559- if not self.search_results and len(text) - text.count(' ') < 3 and bible:
1560- self.main_window.information_message(
1561- UiStrings().BibleShortSearchTitle,
1562- UiStrings().BibleShortSearch)
1563- if not self.search_results and len(text) - text.count(' ') > 2 and bible:
1564- # Text search starts here if no reference was found and keyword is longer than 2.
1565- # > 2 check is required in order to avoid duplicate error messages for short keywords.
1566- self.on_quick_text_search()
1567- if not self.search_results and not \
1568- Settings().value(self.settings_section + '/hide combined quick error'):
1569- self.application.set_normal_cursor()
1570- # Reference separators need to be defined both, in here and on reference search,
1571- # error won't work if they are left out from one.
1572- reference_separators = {
1573- 'verse': get_reference_separator('sep_v_display'),
1574- 'range': get_reference_separator('sep_r_display'),
1575- 'list': get_reference_separator('sep_l_display')}
1576- self.main_window.information_message(translate('BiblesPlugin.BibleManager', 'Nothing found'),
1577- translate('BiblesPlugin.BibleManager',
1578- '<strong>OpenLP couldn’t find anything with your'
1579- ' search.</strong><br><br>If you tried to search'
1580- ' with Scripture Reference, please make<br> sure'
1581- ' that your reference follows one of these'
1582- ' patterns: <br><br>%s'
1583- % UiStrings().BibleScriptureError %
1584- reference_separators))
1585- # Finalizing the search
1586- # List is cleared if not locked, results are listed, button is set available, cursor is set to normal.
1587- if not self.quickLockButton.isChecked():
1588- self.list_view.clear()
1589- if self.list_view.count() != 0 and self.search_results:
1590- self.__check_second_bible(bible, second_bible)
1591- elif self.search_results:
1592- self.display_results(bible, second_bible)
1593- self.quickSearchButton.setEnabled(True)
1594- self.application.set_normal_cursor()
1595-
1596- def on_quick_search_while_typing(self):
1597- """
1598- This function is called when "Search as you type" is enabled for Bibles.
1599- It is basically the same thing as "on_quick_search_search" but all the error messages are removed.
1600- This also has increased min len for text search for performance reasons.
1601- For commented version, please visit def on_quick_search_button.
1602- """
1603- bible = self.quickVersionComboBox.currentText()
1604- second_bible = self.quickSecondComboBox.currentText()
1605- text = self.quick_search_edit.text()
1606- if self.quick_search_edit.current_search_type() == BibleSearch.Combined:
1607- # If text has no numbers, auto search limit is min 8 characters for performance reasons.
1608- # If you change this value, also change it in biblestab.py (Count) in enabling search while typing.
1609- if (char.isdigit() for char in text) and len(text) > 2:
1610- self.on_quick_reference_search()
1611- if not self.search_results and len(text) > 7:
1612- self.on_quick_text_search_while_typing()
1613- elif self.quick_search_edit.current_search_type() == BibleSearch.Reference:
1614- self.on_quick_reference_search()
1615- elif self.quick_search_edit.current_search_type() == BibleSearch.Text:
1616- if len(text) > 7:
1617- self.on_quick_text_search_while_typing()
1618- if not self.quickLockButton.isChecked():
1619- self.list_view.clear()
1620- if self.list_view.count() != 0 and self.search_results:
1621- self.__check_second_bible(bible, second_bible)
1622- elif self.search_results:
1623- self.display_results(bible, second_bible)
1624- self.application.set_normal_cursor()
1625-
1626- def on_search_text_edit_changed(self):
1627- """
1628- If search automatically while typing is enabled, perform the search and list results when conditions are met.
1629+ translate('BiblesPlugin.BibleManager',
1630+ '<strong>The reference you typed is invalid!<br><br>'
1631+ 'Please make sure that your reference follows one of these patterns:</strong><br><br>%s')
1632+ % UiStrings().BibleScriptureError % get_reference_separators())
1633+ elif self.search_edit.current_search_type() == BibleSearch.Combined and get_reference_match('full').match(text):
1634+ # Valid reference found. Do reference search.
1635+ self.text_reference_search(text)
1636+ else:
1637+ # It can only be a 'Combined' search without a valid reference, or a 'Text' search
1638+ if search_while_type:
1639+ if len(text) > 8 and VALID_TEXT_SEARCH.search(text):
1640+ self.on_text_search(text, True)
1641+ elif VALID_TEXT_SEARCH.search(text):
1642+ self.on_text_search(text)
1643+
1644+ def on_search_edit_text_changed(self):
1645+ """
1646+ If 'search_as_you_type' is enabled, start a timer when the search_edit emits a textChanged signal. This is to
1647+ prevent overloading the system by submitting too many search requests in a short space of time.
1648+
1649+ :return: None
1650 """
1651 if Settings().value('bibles/is search while typing enabled'):
1652- text = self.quick_search_edit.text()
1653- """
1654- Use Regex for finding space + number in reference search and space + 2 characters in text search.
1655- Also search for two characters (Searches require at least two sets of two characters)
1656- These are used to prevent bad search queries from starting. (Long/crashing queries)
1657- """
1658- space_and_digit_reference = re.compile(' \d')
1659- two_chars_text = re.compile('\S\S')
1660- space_and_two_chars_text = re.compile(' \S\S')
1661- # Turn this into a format that may be used in if statement.
1662- count_space_digit_reference = space_and_digit_reference.findall(text)
1663- count_two_chars_text = two_chars_text.findall(text)
1664- count_spaces_two_chars_text = space_and_two_chars_text.findall(text)
1665- """
1666- The Limit is required for setting the proper "No items found" message.
1667- "Limit" is also hard coded to on_quick_search_while_typing, it must be there to avoid bad search
1668- performance. Limit 8 = Text search, 3 = Reference search.
1669- """
1670- limit = 8
1671- if self.quick_search_edit.current_search_type() == BibleSearch.Combined:
1672- if len(count_space_digit_reference) != 0:
1673- limit = 3
1674- elif self.quick_search_edit.current_search_type() == BibleSearch.Reference:
1675- limit = 3
1676- """
1677- If text is empty, clear the list.
1678- else: Start by checking if the search is suitable for "Search while typing"
1679- """
1680- if len(text) == 0:
1681- if not self.quickLockButton.isChecked():
1682- self.list_view.clear()
1683- else:
1684- if limit == 3 and (len(text) < limit or len(count_space_digit_reference) == 0):
1685- if not self.quickLockButton.isChecked():
1686- self.list_view.clear()
1687- elif limit == 8 and (len(text) < limit or len(count_two_chars_text) < 2):
1688- if not self.quickLockButton.isChecked():
1689- self.list_view.clear(search_while_typing=True)
1690- else:
1691- """
1692- Start search if no chars are entered or deleted for 0.2 s
1693- If no Timer is set, Text search will break the search by sending repeative search Quaries on
1694- all chars. Use the self.on_quick_search_while_typing, this does not contain any error messages.
1695- """
1696- self.search_timer = ()
1697- if self.search_timer:
1698- self.search_timer.stop()
1699- self.search_timer.deleteLater()
1700- self.search_timer = QtCore.QTimer()
1701- self.search_timer.timeout.connect(self.on_quick_search_while_typing)
1702- self.search_timer.setSingleShot(True)
1703- self.search_timer.start(200)
1704-
1705- def display_results(self, bible, second_bible=''):
1706- """
1707- Displays the search results in the media manager. All data needed for further action is saved for/in each row.
1708- """
1709- items = self.build_display_results(bible, second_bible, self.search_results)
1710- for bible_verse in items:
1711- self.list_view.addItem(bible_verse)
1712- self.list_view.selectAll()
1713- self.search_results = {}
1714- self.second_search_results = {}
1715+ if not self.search_timer.isActive():
1716+ self.search_timer.start()
1717+
1718+ def on_search_timer_timeout(self):
1719+ """
1720+ Perform a search when the search timer timeouts. The search timer is used for 'search_as_you_type' so that we
1721+ don't overload the system buy submitting too many search requests in a short space of time.
1722+
1723+ :return: None
1724+ """
1725+ self.text_search(True)
1726+
1727+ def display_results(self):
1728+ """
1729+ Add the search results to the media manager list.
1730+
1731+ :return: None
1732+ """
1733+ self.list_view.clear()
1734+ if self.search_results:
1735+ items = self.build_display_results(self.bible, self.second_bible, self.search_results)
1736+ for item in items:
1737+ self.list_view.addItem(item)
1738+ self.list_view.selectAll()
1739+ self.search_results = []
1740+ self.second_search_results = []
1741
1742 def build_display_results(self, bible, second_bible, search_results):
1743 """
1744 Displays the search results in the media manager. All data needed for further action is saved for/in each row.
1745 """
1746- verse_separator = get_reference_separator('sep_v_display')
1747- version = self.plugin.manager.get_meta_data(bible, 'name').value
1748- copyright = self.plugin.manager.get_meta_data(bible, 'copyright').value
1749- permissions = self.plugin.manager.get_meta_data(bible, 'permissions').value
1750+ verse_separator = get_reference_separators()['verse']
1751+ version = self.plugin.manager.get_meta_data(self.bible.name, 'name').value
1752+ copyright = self.plugin.manager.get_meta_data(self.bible.name, 'copyright').value
1753+ permissions = self.plugin.manager.get_meta_data(self.bible.name, 'permissions').value
1754+ second_name = ''
1755 second_version = ''
1756 second_copyright = ''
1757 second_permissions = ''
1758 if second_bible:
1759- second_version = self.plugin.manager.get_meta_data(second_bible, 'name').value
1760- second_copyright = self.plugin.manager.get_meta_data(second_bible, 'copyright').value
1761- second_permissions = self.plugin.manager.get_meta_data(second_bible, 'permissions').value
1762+ second_name = second_bible.name
1763+ second_version = self.plugin.manager.get_meta_data(self.second_bible.name, 'name').value
1764+ second_copyright = self.plugin.manager.get_meta_data(self.second_bible.name, 'copyright').value
1765+ second_permissions = self.plugin.manager.get_meta_data(self.second_bible.name, 'permissions').value
1766 items = []
1767- language_selection = self.plugin.manager.get_language_selection(bible)
1768+ language_selection = self.plugin.manager.get_language_selection(self.bible.name)
1769 for count, verse in enumerate(search_results):
1770- book = None
1771- if language_selection == LanguageSelection.Bible:
1772- book = verse.book.name
1773- elif language_selection == LanguageSelection.Application:
1774- book_names = BibleStrings().BookNames
1775- data = BiblesResourcesDB.get_book_by_id(verse.book.book_reference_id)
1776- book = str(book_names[data['abbreviation']])
1777- elif language_selection == LanguageSelection.English:
1778- data = BiblesResourcesDB.get_book_by_id(verse.book.book_reference_id)
1779- book = data['name']
1780 data = {
1781- 'book': book,
1782+ 'book': verse.book.get_name(language_selection),
1783 'chapter': verse.chapter,
1784 'verse': verse.verse,
1785- 'bible': bible,
1786+ 'bible': self.bible.name,
1787 'version': version,
1788 'copyright': copyright,
1789 'permissions': permissions,
1790 'text': verse.text,
1791- 'second_bible': second_bible,
1792+ 'second_bible': second_name,
1793 'second_version': second_version,
1794 'second_copyright': second_copyright,
1795 'second_permissions': second_permissions,
1796 'second_text': ''
1797 }
1798+
1799 if second_bible:
1800 try:
1801 data['second_text'] = self.second_search_results[count].text
1802@@ -1023,20 +786,10 @@
1803 except TypeError:
1804 log.exception('The second_search_results does not have this book.')
1805 break
1806- bible_text = ('{book} {chapter:d}{sep}{verse:d} '
1807- '({version1}, {version2})').format(book=book,
1808- chapter=verse.chapter,
1809- sep=verse_separator,
1810- verse=verse.verse,
1811- version1=version,
1812- version2=second_version)
1813+ bible_text = '{book} {chapter:d}{sep}{verse:d} ({version}, {second_version})'
1814 else:
1815- bible_text = '{book} {chapter:d}{sep}{verse:d} ({version})'.format(book=book,
1816- chapter=verse.chapter,
1817- sep=verse_separator,
1818- verse=verse.verse,
1819- version=version)
1820- bible_verse = QtWidgets.QListWidgetItem(bible_text)
1821+ bible_text = '{book} {chapter:d}{sep}{verse:d} ({version})'
1822+ bible_verse = QtWidgets.QListWidgetItem(bible_text.format(sep=verse_separator, **data))
1823 bible_verse.setData(QtCore.Qt.UserRole, data)
1824 items.append(bible_verse)
1825 return items
1826@@ -1060,63 +813,44 @@
1827 if not items:
1828 return False
1829 bible_text = ''
1830- old_item = None
1831 old_chapter = -1
1832 raw_slides = []
1833- raw_title = []
1834 verses = VerseReferenceList()
1835 for bitem in items:
1836- book = self._decode_qt_object(bitem, 'book')
1837- chapter = int(self._decode_qt_object(bitem, 'chapter'))
1838- verse = int(self._decode_qt_object(bitem, 'verse'))
1839- bible = self._decode_qt_object(bitem, 'bible')
1840- version = self._decode_qt_object(bitem, 'version')
1841- copyright = self._decode_qt_object(bitem, 'copyright')
1842- permissions = self._decode_qt_object(bitem, 'permissions')
1843- text = self._decode_qt_object(bitem, 'text')
1844- second_bible = self._decode_qt_object(bitem, 'second_bible')
1845- second_version = self._decode_qt_object(bitem, 'second_version')
1846- second_copyright = self._decode_qt_object(bitem, 'second_copyright')
1847- second_permissions = self._decode_qt_object(bitem, 'second_permissions')
1848- second_text = self._decode_qt_object(bitem, 'second_text')
1849- verses.add(book, chapter, verse, version, copyright, permissions)
1850- verse_text = self.format_verse(old_chapter, chapter, verse)
1851- if second_bible:
1852- bible_text = '{verse}{text1}\n\n{verse}&nbsp;{text2}'.format(verse=verse_text,
1853- text1=text,
1854- text2=second_text)
1855+ data = bitem.data(QtCore.Qt.UserRole)
1856+ verses.add(
1857+ data['book'], data['chapter'], data['verse'], data['version'], data['copyright'], data['permissions'])
1858+ verse_text = self.format_verse(old_chapter, data['chapter'], data['verse'])
1859+ # We only support 'Verse Per Slide' when using a scond bible
1860+ if data['second_bible']:
1861+ second_text = self.format_verse(old_chapter, data['chapter'], data['verse'])
1862+ bible_text = '{first_version}{data[text]}\n\n{second_version}{data[second_text]}'\
1863+ .format(first_version=verse_text, second_version=second_text, data=data)
1864 raw_slides.append(bible_text.rstrip())
1865 bible_text = ''
1866 # If we are 'Verse Per Slide' then create a new slide.
1867 elif self.settings.layout_style == LayoutStyle.VersePerSlide:
1868- bible_text = '{verse}{text}'.format(verse=verse_text, text=text)
1869+ bible_text = '{first_version}{data[text]}'.format(first_version=verse_text, data=data)
1870 raw_slides.append(bible_text.rstrip())
1871 bible_text = ''
1872 # If we are 'Verse Per Line' then force a new line.
1873 elif self.settings.layout_style == LayoutStyle.VersePerLine:
1874- bible_text = '{bible}{verse}{text}\n'.format(bible=bible_text, verse=verse_text, text=text)
1875+ bible_text = '{bible} {verse}{data[text]}\n'.format(bible=bible_text, verse=verse_text, data=data)
1876 # We have to be 'Continuous'.
1877 else:
1878- bible_text = '{bible} {verse}{text}\n'.format(bible=bible_text, verse=verse_text, text=text)
1879+ bible_text = '{bible} {verse}{data[text]}'.format(bible=bible_text, verse=verse_text, data=data)
1880 bible_text = bible_text.strip(' ')
1881- if not old_item:
1882- start_item = bitem
1883- elif self.check_title(bitem, old_item):
1884- raw_title.append(self.format_title(start_item, old_item))
1885- start_item = bitem
1886- old_item = bitem
1887- old_chapter = chapter
1888+ old_chapter = data['chapter']
1889 # Add footer
1890 service_item.raw_footer.append(verses.format_verses())
1891- if second_bible:
1892- verses.add_version(second_version, second_copyright, second_permissions)
1893+ if data['second_bible']:
1894+ verses.add_version(data['second_version'], data['second_copyright'], data['second_permissions'])
1895 service_item.raw_footer.append(verses.format_versions())
1896- raw_title.append(self.format_title(start_item, bitem))
1897 # If there are no more items we check whether we have to add bible_text.
1898 if bible_text:
1899 raw_slides.append(bible_text.lstrip())
1900 # Service Item: Capabilities
1901- if self.settings.layout_style == LayoutStyle.Continuous and not second_bible:
1902+ if self.settings.layout_style == LayoutStyle.Continuous and not data['second_bible']:
1903 # Split the line but do not replace line breaks in renderer.
1904 service_item.add_capability(ItemCapabilities.NoLineBreaks)
1905 service_item.add_capability(ItemCapabilities.CanPreview)
1906@@ -1126,77 +860,12 @@
1907 # Service Item: Title
1908 service_item.title = '{verse} {version}'.format(verse=verses.format_verses(), version=verses.format_versions())
1909 # Service Item: Theme
1910- if not self.settings.bible_theme:
1911- service_item.theme = None
1912- else:
1913+ if self.settings.bible_theme:
1914 service_item.theme = self.settings.bible_theme
1915 for slide in raw_slides:
1916 service_item.add_from_text(slide)
1917 return True
1918
1919- def format_title(self, start_bitem, old_bitem):
1920- """
1921- 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
1922- want to add Genesis 1:1-6 as well as Daniel 2:14.
1923-
1924- :param start_bitem: The first item of a range.
1925- :param old_bitem: The last item of a range.
1926- """
1927- verse_separator = get_reference_separator('sep_v_display')
1928- range_separator = get_reference_separator('sep_r_display')
1929- old_chapter = self._decode_qt_object(old_bitem, 'chapter')
1930- old_verse = self._decode_qt_object(old_bitem, 'verse')
1931- start_book = self._decode_qt_object(start_bitem, 'book')
1932- start_chapter = self._decode_qt_object(start_bitem, 'chapter')
1933- start_verse = self._decode_qt_object(start_bitem, 'verse')
1934- start_bible = self._decode_qt_object(start_bitem, 'bible')
1935- start_second_bible = self._decode_qt_object(start_bitem, 'second_bible')
1936- if start_second_bible:
1937- bibles = '{bible1}, {bible2}'.format(bible1=start_bible, bible2=start_second_bible)
1938- else:
1939- bibles = start_bible
1940- if start_chapter == old_chapter:
1941- if start_verse == old_verse:
1942- verse_range = start_chapter + verse_separator + start_verse
1943- else:
1944- verse_range = start_chapter + verse_separator + start_verse + range_separator + old_verse
1945- else:
1946- verse_range = start_chapter + verse_separator + start_verse + \
1947- range_separator + old_chapter + verse_separator + old_verse
1948- return '{book} {verse} ({bible})'.format(book=start_book, verse=verse_range, bible=bibles)
1949-
1950- def check_title(self, bitem, old_bitem):
1951- """
1952- This method checks if we are at the end of an verse range. If that is the case, we return True, otherwise False.
1953- E. g. if we added Genesis 1:1-6, but the next verse is Daniel 2:14, we return True.
1954-
1955- :param bitem: The item we are dealing with at the moment.
1956- :param old_bitem: The item we were previously dealing with.
1957- """
1958- # Get all the necessary meta data.
1959- book = self._decode_qt_object(bitem, 'book')
1960- chapter = int(self._decode_qt_object(bitem, 'chapter'))
1961- verse = int(self._decode_qt_object(bitem, 'verse'))
1962- bible = self._decode_qt_object(bitem, 'bible')
1963- second_bible = self._decode_qt_object(bitem, 'second_bible')
1964- old_book = self._decode_qt_object(old_bitem, 'book')
1965- old_chapter = int(self._decode_qt_object(old_bitem, 'chapter'))
1966- old_verse = int(self._decode_qt_object(old_bitem, 'verse'))
1967- old_bible = self._decode_qt_object(old_bitem, 'bible')
1968- old_second_bible = self._decode_qt_object(old_bitem, 'second_bible')
1969- if old_bible != bible or old_second_bible != second_bible or old_book != book:
1970- # The bible, second bible or book has changed.
1971- return True
1972- elif old_verse + 1 != verse and old_chapter == chapter:
1973- # We are still in the same chapter, but a verse has been skipped.
1974- return True
1975- elif old_chapter + 1 == chapter and (verse != 1 or old_verse !=
1976- self.plugin.manager.get_verse_count(old_bible, old_book, old_chapter)):
1977- # We are in the following chapter, but the last verse was not the last verse of the chapter or the current
1978- # verse is not the first one of the chapter.
1979- return True
1980- return False
1981-
1982 def format_verse(self, old_chapter, chapter, verse):
1983 """
1984 Formats and returns the text, each verse starts with, for the given chapter and verse. The text is either
1985@@ -1207,28 +876,29 @@
1986 :param old_chapter: The previous verse's chapter number (int).
1987 :param chapter: The chapter number (int).
1988 :param verse: The verse number (int).
1989+ :return: An empty or formatted string
1990 """
1991- verse_separator = get_reference_separator('sep_v_display')
1992 if not self.settings.is_verse_number_visible:
1993 return ''
1994+ verse_separator = get_reference_separators()['verse']
1995 if not self.settings.show_new_chapters or old_chapter != chapter:
1996- verse_text = str(chapter) + verse_separator + str(verse)
1997+ verse_text = '{chapter}{sep}{verse}'.format(chapter=chapter, sep=verse_separator, verse=verse)
1998 else:
1999- verse_text = str(verse)
2000- if self.settings.display_style == DisplayStyle.Round:
2001- return '{{su}}({verse}){{/su}}&nbsp;'.format(verse=verse_text)
2002- if self.settings.display_style == DisplayStyle.Curly:
2003- return '{{su}}{{{verse}}}{{/su}}&nbsp;'.format(verse=verse_text)
2004- if self.settings.display_style == DisplayStyle.Square:
2005- return '{{su}}[{verse}]{{/su}}&nbsp;'.format(verse=verse_text)
2006- return '{{su}}{verse}{{/su}}&nbsp;'.format(verse=verse_text)
2007+ verse_text = verse
2008+ bracket = {
2009+ DisplayStyle.NoBrackets: ('', ''),
2010+ DisplayStyle.Round: ('(', ')'),
2011+ DisplayStyle.Curly: ('{', '}'),
2012+ DisplayStyle.Square: ('[', ']')
2013+ }[self.settings.display_style]
2014+ return '{{su}}{bracket[0]}{verse_text}{bracket[1]}{{/su}}&nbsp;'.format(verse_text=verse_text, bracket=bracket)
2015
2016 def search(self, string, showError):
2017 """
2018 Search for some Bible verses (by reference).
2019 """
2020- bible = self.quickVersionComboBox.currentText()
2021- search_results = self.plugin.manager.get_verses(bible, string, False, showError)
2022+ reference = self.plugin.manager.parse_ref(self.bible.name, string)
2023+ search_results = self.plugin.manager.get_verses(self.bible.name, reference, showError)
2024 if search_results:
2025 verse_text = ' '.join([verse.text for verse in search_results])
2026 return [[string, verse_text]]
2027@@ -1238,7 +908,6 @@
2028 """
2029 Create a media item from an item id.
2030 """
2031- bible = self.quickVersionComboBox.currentText()
2032- search_results = self.plugin.manager.get_verses(bible, item_id, False)
2033- items = self.build_display_results(bible, '', search_results)
2034- return items
2035+ reference = self.plugin.manager.parse_ref(self.bible.name, item_id)
2036+ search_results = self.plugin.manager.get_verses(self.bible.name, reference, False)
2037+ return self.build_display_results(self.bible, None, search_results)
2038
2039=== modified file 'openlp/plugins/songs/forms/songselectform.py' (properties changed: +x to -x)
2040=== modified file 'resources/images/openlp-2.qrc'
2041--- resources/images/openlp-2.qrc 2016-10-27 20:37:33 +0000
2042+++ resources/images/openlp-2.qrc 2017-02-18 20:23:59 +0000
2043@@ -29,6 +29,7 @@
2044 <file>image_new_group.png</file>
2045 </qresource>
2046 <qresource prefix="bibles">
2047+ <file>bibles_book_sort.png</file>
2048 <file>bibles_search_combined.png</file>
2049 <file>bibles_search_text.png</file>
2050 <file>bibles_search_reference.png</file>
2051
2052=== modified file 'tests/functional/openlp_core_ui_lib/test_listwidgetwithdnd.py'
2053--- tests/functional/openlp_core_ui_lib/test_listwidgetwithdnd.py 2016-12-31 11:01:36 +0000
2054+++ tests/functional/openlp_core_ui_lib/test_listwidgetwithdnd.py 2017-02-18 20:23:59 +0000
2055@@ -33,6 +33,37 @@
2056 """
2057 Test the :class:`~openlp.core.lib.listwidgetwithdnd.ListWidgetWithDnD` class
2058 """
2059+ def test_clear_locked(self):
2060+ """
2061+ Test the clear method the list is 'locked'
2062+ """
2063+ with patch('openlp.core.ui.lib.listwidgetwithdnd.QtWidgets.QListWidget.clear') as mocked_clear_super_method:
2064+ # GIVEN: An instance of ListWidgetWithDnD
2065+ widget = ListWidgetWithDnD()
2066+
2067+ # WHEN: The list is 'locked' and clear has been called
2068+ widget.locked = True
2069+ widget.clear()
2070+
2071+ # THEN: The super method should not have been called (i.e. The list not cleared)
2072+ self.assertFalse(mocked_clear_super_method.called)
2073+
2074+ def test_clear_overide_locked(self):
2075+ """
2076+ Test the clear method the list is 'locked', but clear is called with 'override_lock' set to True
2077+ """
2078+ with patch('openlp.core.ui.lib.listwidgetwithdnd.QtWidgets.QListWidget.clear') as mocked_clear_super_method:
2079+ # GIVEN: An instance of ListWidgetWithDnD
2080+ widget = ListWidgetWithDnD()
2081+
2082+ # WHEN: The list is 'locked' and clear has been called with override_lock se to True
2083+ widget.locked = True
2084+ widget.clear(override_lock=True)
2085+
2086+ # THEN: The super method should have been called (i.e. The list is cleared regardless whether it is locked
2087+ # or not)
2088+ mocked_clear_super_method.assert_called_once_with()
2089+
2090 def test_clear(self):
2091 """
2092 Test the clear method when called without any arguments.
2093
2094=== modified file 'tests/functional/openlp_plugins/bibles/test_lib.py'
2095--- tests/functional/openlp_plugins/bibles/test_lib.py 2016-12-31 11:01:36 +0000
2096+++ tests/functional/openlp_plugins/bibles/test_lib.py 2017-02-18 20:23:59 +0000
2097@@ -25,11 +25,12 @@
2098 from unittest import TestCase
2099
2100 from openlp.plugins.bibles import lib
2101-from openlp.plugins.bibles.lib import SearchResults
2102-from tests.functional import patch
2103-
2104-
2105-class TestLib(TestCase):
2106+from openlp.plugins.bibles.lib import SearchResults, get_reference_match
2107+from tests.functional import MagicMock, patch
2108+from tests.helpers.testmixin import TestMixin
2109+
2110+
2111+class TestLib(TestCase, TestMixin):
2112 """
2113 Test the functions in the :mod:`lib` module.
2114 """
2115@@ -60,6 +61,142 @@
2116 self.assertEqual(separators[key], value)
2117 mocked_update_reference_separators.assert_called_once_with()
2118
2119+ def test_reference_matched_full(self):
2120+ """
2121+ Test that the 'full' regex parses bible verse references correctly.
2122+ """
2123+ # GIVEN: Some test data which contains different references to parse, with the expected results.
2124+ with patch('openlp.plugins.bibles.lib.Settings', return_value=MagicMock(**{'value.return_value': ''})):
2125+ # The following test data tests with 222 variants when using the default 'separators'
2126+ test_data = [
2127+ # Input reference, book name, chapter + verse reference
2128+ ('Psalm 23', 'Psalm', '23'),
2129+ ('Psalm. 23', 'Psalm', '23'),
2130+ ('Psalm 23{to}24', 'Psalm', '23-24'),
2131+ ('Psalm 23{verse}1{to}2', 'Psalm', '23:1-2'),
2132+ ('Psalm 23{verse}1{to}{end}', 'Psalm', '23:1-end'),
2133+ ('Psalm 23{verse}1{to}2{_and}5{to}6', 'Psalm', '23:1-2,5-6'),
2134+ ('Psalm 23{verse}1{to}2{_and}5{to}{end}', 'Psalm', '23:1-2,5-end'),
2135+ ('Psalm 23{verse}1{to}2{_and}24{verse}1{to}3', 'Psalm', '23:1-2,24:1-3'),
2136+ ('Psalm 23{verse}1{to}{end}{_and}24{verse}1{to}{end}', 'Psalm', '23:1-end,24:1-end'),
2137+ ('Psalm 23{verse}1{to}24{verse}1', 'Psalm', '23:1-24:1'),
2138+ ('Psalm 23{_and}24', 'Psalm', '23,24'),
2139+ ('1 John 23', '1 John', '23'),
2140+ ('1 John. 23', '1 John', '23'),
2141+ ('1 John 23{to}24', '1 John', '23-24'),
2142+ ('1 John 23{verse}1{to}2', '1 John', '23:1-2'),
2143+ ('1 John 23{verse}1{to}{end}', '1 John', '23:1-end'),
2144+ ('1 John 23{verse}1{to}2{_and}5{to}6', '1 John', '23:1-2,5-6'),
2145+ ('1 John 23{verse}1{to}2{_and}5{to}{end}', '1 John', '23:1-2,5-end'),
2146+ ('1 John 23{verse}1{to}2{_and}24{verse}1{to}3', '1 John', '23:1-2,24:1-3'),
2147+ ('1 John 23{verse}1{to}{end}{_and}24{verse}1{to}{end}', '1 John', '23:1-end,24:1-end'),
2148+ ('1 John 23{verse}1{to}24{verse}1', '1 John', '23:1-24:1'),
2149+ ('1 John 23{_and}24', '1 John', '23,24')]
2150+
2151+ full_reference_match = get_reference_match('full')
2152+ for reference_text, book_result, ranges_result in test_data:
2153+ to_separators = ['-', ' - ', 'to', ' to '] if '{to}' in reference_text else ['']
2154+ verse_separators = [':', ' : ', 'v', ' v ', 'V', ' V ', 'verse', ' verse ', 'verses', ' verses '] \
2155+ if '{verse}' in reference_text else ['']
2156+ and_separators = [',', ' , ', 'and', ' and '] if '{_and}' in reference_text else ['']
2157+ end_separators = ['end', ' end '] if '{end}' in reference_text else ['']
2158+
2159+ for to in to_separators:
2160+ for verse in verse_separators:
2161+ for _and in and_separators:
2162+ for end in end_separators:
2163+ reference_text = reference_text.format(to=to, verse=verse, _and=_and, end=end)
2164+
2165+ # WHEN: Attempting to parse the input string
2166+ match = full_reference_match.match(reference_text)
2167+
2168+ # THEN: A match should be returned, and the book and reference should match the
2169+ # expected result
2170+ self.assertIsNotNone(match, '{text} should provide a match'.format(text=reference_text))
2171+ self.assertEqual(book_result, match.group('book'),
2172+ '{text} does not provide the expected result for the book group.'
2173+ .format(text=reference_text))
2174+ self.assertEqual(ranges_result, match.group('ranges'),
2175+ '{text} does not provide the expected result for the ranges group.'
2176+ .format(text=reference_text))
2177+
2178+ def test_reference_matched_range(self):
2179+ """
2180+ Test that the 'range' regex parses bible verse references correctly.
2181+ Note: This test takes in to account that the regex does not work quite as expected!
2182+ see https://bugs.launchpad.net/openlp/+bug/1638620
2183+ """
2184+ # GIVEN: Some test data which contains different references to parse, with the expected results.
2185+ with patch('openlp.plugins.bibles.lib.Settings', return_value=MagicMock(**{'value.return_value': ''})):
2186+ # The following test data tests with 45 variants when using the default 'separators'
2187+ test_data = [
2188+ ('23', None, '23', None, None, None),
2189+ ('23{to}24', None, '23', '-24', None, '24'),
2190+ ('23{verse}1{to}2', '23', '1', '-2', None, '2'),
2191+ ('23{verse}1{to}{end}', '23', '1', '-end', None, None),
2192+ ('23{verse}1{to}24{verse}1', '23', '1', '-24:1', '24', '1')]
2193+ full_reference_match = get_reference_match('range')
2194+ for reference_text, from_chapter, from_verse, range_to, to_chapter, to_verse in test_data:
2195+ to_separators = ['-', ' - ', 'to', ' to '] if '{to}' in reference_text else ['']
2196+ verse_separators = [':', ' : ', 'v', ' v ', 'V', ' V ', 'verse', ' verse ', 'verses', ' verses '] \
2197+ if '{verse}' in reference_text else ['']
2198+ and_separators = [',', ' , ', 'and', ' and '] if '{_and}' in reference_text else ['']
2199+ end_separators = ['end', ' end '] if '{end}' in reference_text else ['']
2200+
2201+ for to in to_separators:
2202+ for verse in verse_separators:
2203+ for _and in and_separators:
2204+ for end in end_separators:
2205+ reference_text = reference_text.format(to=to, verse=verse, _and=_and, end=end)
2206+
2207+ # WHEN: Attempting to parse the input string
2208+ match = full_reference_match.match(reference_text)
2209+
2210+ # THEN: A match should be returned, and the to/from chapter/verses should match as
2211+ # expected
2212+ self.assertIsNotNone(match, '{text} should provide a match'.format(text=reference_text))
2213+ self.assertEqual(match.group('from_chapter'), from_chapter)
2214+ self.assertEqual(match.group('from_verse'), from_verse)
2215+ self.assertEqual(match.group('range_to'), range_to)
2216+ self.assertEqual(match.group('to_chapter'), to_chapter)
2217+ self.assertEqual(match.group('to_verse'), to_verse)
2218+
2219+ def test_reference_matched_range_separator(self):
2220+ # GIVEN: Some test data which contains different references to parse, with the expected results.
2221+ with patch('openlp.plugins.bibles.lib.Settings', return_value=MagicMock(**{'value.return_value': ''})):
2222+ # The following test data tests with 111 variants when using the default 'separators'
2223+ # The regex for handling ranges is a bit screwy, see https://bugs.launchpad.net/openlp/+bug/1638620
2224+ test_data = [
2225+ ('23', ['23']),
2226+ ('23{to}24', ['23-24']),
2227+ ('23{verse}1{to}2', ['23:1-2']),
2228+ ('23{verse}1{to}{end}', ['23:1-end']),
2229+ ('23{verse}1{to}2{_and}5{to}6', ['23:1-2', '5-6']),
2230+ ('23{verse}1{to}2{_and}5{to}{end}', ['23:1-2', '5-end']),
2231+ ('23{verse}1{to}2{_and}24{verse}1{to}3', ['23:1-2', '24:1-3']),
2232+ ('23{verse}1{to}{end}{_and}24{verse}1{to}{end}', ['23:1-end', '24:1-end']),
2233+ ('23{verse}1{to}24{verse}1', ['23:1-24:1']),
2234+ ('23,24', ['23', '24'])]
2235+ full_reference_match = get_reference_match('range_separator')
2236+ for reference_text, ranges in test_data:
2237+ to_separators = ['-', ' - ', 'to', ' to '] if '{to}' in reference_text else ['']
2238+ verse_separators = [':', ' : ', 'v', ' v ', 'V', ' V ', 'verse', ' verse ', 'verses', ' verses '] \
2239+ if '{verse}' in reference_text else ['']
2240+ and_separators = [',', ' , ', 'and', ' and '] if '{_and}' in reference_text else ['']
2241+ end_separators = ['end', ' end '] if '{end}' in reference_text else ['']
2242+
2243+ for to in to_separators:
2244+ for verse in verse_separators:
2245+ for _and in and_separators:
2246+ for end in end_separators:
2247+ reference_text = reference_text.format(to=to, verse=verse, _and=_and, end=end)
2248+
2249+ # WHEN: Attempting to parse the input string
2250+ references = full_reference_match.split(reference_text)
2251+
2252+ # THEN: The list of references should be as the expected results
2253+ self.assertEqual(references, ranges)
2254+
2255 def test_search_results_creation(self):
2256 """
2257 Test the creation and construction of the SearchResults class
2258
2259=== modified file 'tests/functional/openlp_plugins/bibles/test_mediaitem.py'
2260--- tests/functional/openlp_plugins/bibles/test_mediaitem.py 2016-12-31 11:01:36 +0000
2261+++ tests/functional/openlp_plugins/bibles/test_mediaitem.py 2017-02-18 20:23:59 +0000
2262@@ -23,10 +23,85 @@
2263 This module contains tests for the lib submodule of the Presentations plugin.
2264 """
2265 from unittest import TestCase
2266+from unittest.mock import MagicMock, call, patch
2267+
2268+from PyQt5 import QtCore, QtWidgets
2269+
2270+from tests.helpers.testmixin import TestMixin
2271+
2272 from openlp.core.common import Registry
2273-from openlp.plugins.bibles.lib.mediaitem import BibleMediaItem
2274-from tests.functional import MagicMock, patch
2275-from tests.helpers.testmixin import TestMixin
2276+from openlp.core.lib import MediaManagerItem
2277+from openlp.plugins.bibles.lib.mediaitem import BibleMediaItem, BibleSearch, get_reference_separators, VALID_TEXT_SEARCH
2278+
2279+
2280+class TestBibleMediaItemModulefunctions(TestCase):
2281+ """
2282+ Test the module functions in :mod:`openlp.plugins.bibles.lib.mediaitem`
2283+ """
2284+
2285+ def test_valid_text_search(self):
2286+ """
2287+ Test the compiled VALID_TEXT_SEARCH regex expression
2288+ """
2289+ # GIVEN: Some test data and some expected results
2290+ test_data = [('a a a', None), ('a ab a', None), ('a abc a', ((2, 5),)), ('aa 123 aa', ((3, 6),))]
2291+ for data, expected_result in test_data:
2292+
2293+ # WHEN: Calling search on the compiled regex expression
2294+ result = VALID_TEXT_SEARCH.search(data)
2295+
2296+ # THEN: The expected result should be returned
2297+ if expected_result is None:
2298+ self.assertIsNone(result, expected_result)
2299+ else:
2300+ self.assertEqual(result.regs, expected_result)
2301+
2302+ def test_get_reference_separators(self):
2303+ """
2304+ Test the module function get_reference_separators
2305+ """
2306+ # GIVEN: A mocked get_reference_separator from the :mod:`openlp.plugins.bibles.lib` module
2307+ with patch('openlp.plugins.bibles.lib.mediaitem.get_reference_separator') as mocked_get_reference_separator:
2308+
2309+ # WHEN: Calling get_reference_separators
2310+ result = get_reference_separators()
2311+
2312+ # THEN: The result should contain the 'verse', 'range', 'list' keys and get_reference_separator should have
2313+ # been called with the expected values.
2314+ self.assertTrue(all(key in result for key in ('verse', 'range', 'list')))
2315+ mocked_get_reference_separator.assert_has_calls(
2316+ [call('sep_v_display'), call('sep_r_display'), call('sep_l_display')])
2317+
2318+ def test_bible_search_enum(self):
2319+ """
2320+ Test that the :class:`BibleSearch` class contains the expected enumerations
2321+ """
2322+ # GIVEN: The BibleSearch class
2323+ # WHEN: Testing its attributes
2324+ # THEN: The BibleSearch class should have the following enumrations
2325+ self.assertTrue(hasattr(BibleSearch, 'Combined'))
2326+ self.assertTrue(hasattr(BibleSearch, 'Reference'))
2327+ self.assertTrue(hasattr(BibleSearch, 'Text'))
2328+
2329+ def test_bible_media_item_subclass(self):
2330+ """
2331+ Test that the :class:`BibleMediaItem` class is a subclass of the :class:`MediaManagerItem` class
2332+ """
2333+ # GIVEN: The :class:`BibleMediaItem`
2334+ # WHEN: Checking if it is a subclass of MediaManagerItem
2335+ # THEN: BibleMediaItem should be a subclass of MediaManagerItem
2336+ self.assertTrue(issubclass(BibleMediaItem, MediaManagerItem))
2337+
2338+ def test_bible_media_item_signals(self):
2339+ """
2340+ Test that the :class:`BibleMediaItem` class has the expected signals
2341+ """
2342+ # GIVEN: The :class:`BibleMediaItem`
2343+ # THEN: The :class:`BibleMediaItem` should contain the following pyqtSignal's
2344+ self.assertTrue(hasattr(BibleMediaItem, 'bibles_go_live'))
2345+ self.assertTrue(hasattr(BibleMediaItem, 'bibles_add_to_service'))
2346+ self.assertTrue(isinstance(BibleMediaItem.bibles_go_live, QtCore.pyqtSignal))
2347+ self.assertTrue(isinstance(BibleMediaItem.bibles_add_to_service, QtCore.pyqtSignal))
2348
2349
2350 class TestMediaItem(TestCase, TestMixin):
2351@@ -38,189 +113,1315 @@
2352 """
2353 Set up the components need for all tests.
2354 """
2355- with patch('openlp.plugins.bibles.lib.mediaitem.MediaManagerItem._setup'),\
2356- patch('openlp.plugins.bibles.lib.mediaitem.BibleMediaItem.setup_item'):
2357- self.media_item = BibleMediaItem(None, MagicMock())
2358- self.setup_application()
2359+ log_patcher = patch('openlp.plugins.bibles.lib.mediaitem.log')
2360+ self.addCleanup(log_patcher.stop)
2361+ self.mocked_log = log_patcher.start()
2362+
2363+ qtimer_patcher = patch('openlp.plugins.bibles.lib.mediaitem.QtCore.QTimer')
2364+ self.addCleanup(qtimer_patcher.stop)
2365+ self.mocked_qtimer = qtimer_patcher.start()
2366+
2367+ self.mocked_settings_instance = MagicMock()
2368+ self.mocked_settings_instance.value.side_effect = lambda key: self.setting_values[key]
2369+ settings_patcher = patch(
2370+ 'openlp.plugins.bibles.lib.mediaitem.Settings', return_value=self.mocked_settings_instance)
2371+ self.addCleanup(settings_patcher.stop)
2372+ self.mocked_settings = settings_patcher.start()
2373+
2374+ Registry.create()
2375+
2376+ # self.setup_application()
2377+ self.mocked_application = MagicMock()
2378+ Registry().register('application', self.mocked_application)
2379 self.mocked_main_window = MagicMock()
2380- Registry.create()
2381 Registry().register('main_window', self.mocked_main_window)
2382
2383- def test_display_results_no_results(self):
2384- """
2385- Test the display_results method when called with a single bible, returning no results
2386- """
2387-
2388- # GIVEN: A mocked build_display_results which returns an empty list
2389- with patch('openlp.plugins.bibles.lib.BibleMediaItem.build_display_results', **{'return_value': []}) \
2390- as mocked_build_display_results:
2391- mocked_list_view = MagicMock()
2392- self.media_item.search_results = 'results'
2393- self.media_item.list_view = mocked_list_view
2394-
2395- # WHEN: Calling display_results with a single bible version
2396- self.media_item.display_results('NIV')
2397-
2398- # THEN: No items should be added to the list, and select all should have been called.
2399- mocked_build_display_results.assert_called_once_with('NIV', '', 'results')
2400- self.assertFalse(mocked_list_view.addItem.called)
2401- mocked_list_view.selectAll.assert_called_once_with()
2402- self.assertEqual(self.media_item.search_results, {})
2403- self.assertEqual(self.media_item.second_search_results, {})
2404-
2405- def test_display_results_two_bibles_no_results(self):
2406- """
2407- Test the display_results method when called with two bibles, returning no results
2408- """
2409-
2410- # GIVEN: A mocked build_display_results which returns an empty list
2411- with patch('openlp.plugins.bibles.lib.BibleMediaItem.build_display_results', **{'return_value': []}) \
2412- as mocked_build_display_results:
2413- mocked_list_view = MagicMock()
2414- self.media_item.search_results = 'results'
2415- self.media_item.list_view = mocked_list_view
2416-
2417- # WHEN: Calling display_results with two single bible versions
2418- self.media_item.display_results('NIV', 'GNB')
2419-
2420- # THEN: build_display_results should have been called with two bible versions.
2421- # No items should be added to the list, and select all should have been called.
2422- mocked_build_display_results.assert_called_once_with('NIV', 'GNB', 'results')
2423- self.assertFalse(mocked_list_view.addItem.called)
2424- mocked_list_view.selectAll.assert_called_once_with()
2425- self.assertEqual(self.media_item.search_results, {})
2426- self.assertEqual(self.media_item.second_search_results, {})
2427-
2428- def test_display_results_returns_lots_of_results(self):
2429- """
2430- Test the display_results method a large number of results (> 100) are returned
2431- """
2432-
2433- # GIVEN: A mocked build_display_results which returns a large list of results
2434- long_list = list(range(100))
2435- with patch('openlp.plugins.bibles.lib.BibleMediaItem.build_display_results', **{'return_value': long_list})\
2436- as mocked_build_display_results:
2437- mocked_list_view = MagicMock()
2438- self.media_item.search_results = 'results'
2439- self.media_item.list_view = mocked_list_view
2440-
2441- # WHEN: Calling display_results
2442- self.media_item.display_results('NIV', 'GNB')
2443-
2444- # THEN: addItem should have been called 100 times, and the lsit items should not be selected.
2445- mocked_build_display_results.assert_called_once_with('NIV', 'GNB', 'results')
2446- self.assertEqual(mocked_list_view.addItem.call_count, 100)
2447- mocked_list_view.selectAll.assert_called_once_with()
2448- self.assertEqual(self.media_item.search_results, {})
2449- self.assertEqual(self.media_item.second_search_results, {})
2450+ self.mocked_plugin = MagicMock()
2451+ with patch('openlp.plugins.bibles.lib.mediaitem.build_icon'), \
2452+ patch('openlp.plugins.bibles.lib.mediaitem.MediaManagerItem._setup'), \
2453+ patch('openlp.plugins.bibles.lib.mediaitem.BibleMediaItem.setup_item'):
2454+ self.media_item = BibleMediaItem(None, self.mocked_plugin)
2455+
2456+ self.media_item.settings_section = 'bibles'
2457+
2458+ self.mocked_book_1 = MagicMock(**{'get_name.return_value': 'Book 1', 'book_reference_id': 1})
2459+ self.mocked_book_2 = MagicMock(**{'get_name.return_value': 'Book 2', 'book_reference_id': 2})
2460+ self.mocked_book_3 = MagicMock(**{'get_name.return_value': 'Book 3', 'book_reference_id': 3})
2461+ self.mocked_book_4 = MagicMock(**{'get_name.return_value': 'Book 4', 'book_reference_id': 4})
2462+
2463+ self.book_list_1 = [self.mocked_book_1, self.mocked_book_2, self.mocked_book_3]
2464+ self.book_list_2 = [self.mocked_book_2, self.mocked_book_3, self.mocked_book_4]
2465+ self.mocked_bible_1 = MagicMock(**{'get_books.return_value': self.book_list_1})
2466+ self.mocked_bible_1.name = 'Bible 1'
2467+ self.mocked_bible_2 = MagicMock(**{'get_books.return_value': self.book_list_2})
2468+ self.mocked_bible_2.name = 'Bible 2'
2469+
2470+ def test_media_item_instance(self):
2471+ """
2472+ When creating an instance of C test that it is also an instance of
2473+ :class:`MediaManagerItem`
2474+ """
2475+ # GIVEN: An instance of :class:`BibleMediaItem`
2476+ # WEHN: Checking its class
2477+ # THEN: It should be a subclass of :class:`MediaManagerItem`
2478+ self.assertTrue(isinstance(self.media_item, MediaManagerItem))
2479+
2480+ def test_steup_item(self):
2481+ """
2482+ Test the setup_item method
2483+ """
2484+ # Could have tested the connection of the custom signals, however they're class vairables, and I could not find
2485+ # a way to properly test them.
2486+
2487+ # GIVEN: A mocked Registry.register_function method and an instance of BibleMediaItem
2488+ with patch.object(Registry(), 'register_function') as mocked_register_function:
2489+
2490+ # WHEN: Calling setup_itme
2491+ self.media_item.setup_item()
2492+
2493+ # THEN: Registry.register_function method should have been called with the reload_bibles method
2494+ mocked_register_function.assert_called_once_with('bibles_load_list', self.media_item.reload_bibles)
2495
2496 def test_required_icons(self):
2497 """
2498 Test that all the required icons are set properly.
2499 """
2500- # GIVEN: Mocked icons that need to be called.
2501- self.media_item.has_import_icon = MagicMock()
2502- self.media_item.has_new_icon = MagicMock()
2503- self.media_item.has_edit_icon = MagicMock()
2504- self.media_item.has_delete_icon = MagicMock()
2505- self.media_item.add_to_service_item = MagicMock()
2506-
2507- # WHEN: self.media_item.required_icons is called
2508+ # GIVEN: An instance of :class:`MediaManagerItem`
2509+ # WHEN: required_icons is called
2510 self.media_item.required_icons()
2511
2512- # THEN: On windows it should return True, on other platforms False
2513+ # THEN: The correct icons should be set
2514 self.assertTrue(self.media_item.has_import_icon, 'Check that the icon is as True.')
2515 self.assertFalse(self.media_item.has_new_icon, 'Check that the icon is called as False.')
2516 self.assertTrue(self.media_item.has_edit_icon, 'Check that the icon is called as True.')
2517 self.assertTrue(self.media_item.has_delete_icon, 'Check that the icon is called as True.')
2518 self.assertFalse(self.media_item.add_to_service_item, 'Check that the icon is called as False')
2519
2520- def test_on_quick_search_button_general(self):
2521- """
2522- Test that general things, which should be called on all Quick searches are called.
2523- """
2524-
2525- # GIVEN: self.application as self.app, all the required functions
2526- Registry.create()
2527- Registry().register('application', self.app)
2528- self.media_item.quickSearchButton = MagicMock()
2529- self.app.process_events = MagicMock()
2530- self.media_item.quickVersionComboBox = MagicMock()
2531- self.media_item.quickVersionComboBox.currentText = MagicMock()
2532- self.media_item.quickSecondComboBox = MagicMock()
2533- self.media_item.quickSecondComboBox.currentText = MagicMock()
2534- self.media_item.quick_search_edit = MagicMock()
2535- self.media_item.quick_search_edit.text = MagicMock()
2536- self.media_item.quickLockButton = MagicMock()
2537- self.media_item.list_view = MagicMock()
2538- self.media_item.search_results = MagicMock()
2539- self.media_item.display_results = MagicMock()
2540- self.app.set_normal_cursor = MagicMock()
2541-
2542- # WHEN: on_quick_search_button is called
2543- self.media_item.on_quick_search_button()
2544-
2545- # THEN: Search should had been started and finalized properly
2546- self.assertEqual(1, self.app.process_events.call_count, 'Normal cursor should had been called once')
2547- self.assertEqual(1, self.media_item.quickVersionComboBox.currentText.call_count, 'Should had been called once')
2548- self.assertEqual(1, self.media_item.quickSecondComboBox.currentText.call_count, 'Should had been called once')
2549- self.assertEqual(1, self.media_item.quick_search_edit.text.call_count, 'Text edit Should had been called once')
2550- self.assertEqual(1, self.media_item.quickLockButton.isChecked.call_count, 'Lock Should had been called once')
2551- self.assertEqual(1, self.media_item.display_results.call_count, 'Display results Should had been called once')
2552- self.assertEqual(2, self.media_item.quickSearchButton.setEnabled.call_count, 'Disable and Enable the button')
2553- self.assertEqual(1, self.app.set_normal_cursor.call_count, 'Normal cursor should had been called once')
2554+ # TODO: Test add_end_header_bar
2555+ # TODO: Test setupUi
2556+
2557+ def test_on_focus_search_tab_visible(self):
2558+ """
2559+ Test the correct widget gets focus when the BibleMediaItem receives focus
2560+ """
2561+ # GIVEN: An instance of :class:`MediaManagerItem` and a mocked out search_tab and search_edit
2562+ self.media_item.search_tab = MagicMock(**{'isVisible.return_value': True})
2563+ self.media_item.search_edit = MagicMock()
2564+
2565+ # WHEN: Calling on_focus
2566+ self.media_item.on_focus()
2567+
2568+ # THEN: setFocus and selectAll should have been called on search_edit
2569+ self.assertEqual(self.media_item.search_edit.mock_calls, [call.setFocus(), call.selectAll()])
2570+
2571+ def test_on_focus_search_tab_not_visible(self):
2572+ """
2573+ Test the correct widget gets focus when the BibleMediaItem receives focus
2574+ """
2575+ # GIVEN: An instance of :class:`MediaManagerItem` and a mocked out search_tab and select_book_combo_box
2576+ self.media_item.search_tab = MagicMock(**{'isVisible.return_value': False})
2577+ self.media_item.select_book_combo_box = MagicMock()
2578+
2579+ # WHEN: Calling on_focus
2580+ self.media_item.on_focus()
2581+
2582+ # THEN: setFocus should have been called on select_book_combo_box
2583+ self.assertTrue(self.media_item.select_book_combo_box.setFocus.called)
2584+
2585+ def test_config_update_show_second_bible(self):
2586+ """
2587+ Test the config update method
2588+ """
2589+ # GIVEN: An instance of :class:`MediaManagerItem` and mocked out settings class with known values
2590+ self.setting_values = {'bibles/second bibles': True}
2591+ self.media_item.general_bible_layout = MagicMock()
2592+ self.media_item.second_combo_box = MagicMock()
2593+
2594+ # WHEN: Calling config_update()
2595+ self.media_item.config_update()
2596+
2597+ # THEN: second_combo_box() should be set visible
2598+ self.media_item.second_combo_box.setVisible.assert_called_once_with(True)
2599+
2600+ def test_config_update_hide_second_bible(self):
2601+ """
2602+ Test the config update method
2603+ """
2604+ # GIVEN: An instance of :class:`MediaManagerItem` and mocked out settings class with known values
2605+ self.setting_values = {'bibles/second bibles': False}
2606+ self.media_item.general_bible_layout = MagicMock()
2607+ self.media_item.second_combo_box = MagicMock()
2608+
2609+ # WHEN: Calling config_update()
2610+ self.media_item.config_update()
2611+
2612+ # THEN: second_combo_box() should hidden
2613+ self.media_item.second_combo_box.setVisible.assert_called_once_with(False)
2614+
2615+ def test_initalise(self):
2616+ """
2617+ Test the initalise method
2618+ """
2619+ # GIVEN: An instance of :class:`MediaManagerItem` and mocked out settings class with known values
2620+ self.setting_values = {'bibles/reset to combined quick search': False}
2621+ with patch.object(self.media_item, 'populate_bible_combo_boxes'), \
2622+ patch.object(self.media_item, 'config_update'):
2623+ self.media_item.search_edit = MagicMock()
2624+
2625+ # WHEN: Calling initialise()
2626+ self.media_item.initialise()
2627+
2628+ # THEN: The search_edit search types should have been set.
2629+ self.assertTrue(self.media_item.search_edit.set_search_types.called)
2630+ self.assertFalse(self.media_item.search_edit.set_current_search_type.called)
2631+
2632+ def test_initalise_reset_search_type(self):
2633+ """
2634+ Test the initalise method
2635+ """
2636+ # GIVEN: An instance of :class:`MediaManagerItem` and mocked out settings class with known values
2637+ self.setting_values = {'bibles/reset to combined quick search': True}
2638+ with patch.object(self.media_item, 'populate_bible_combo_boxes'), \
2639+ patch.object(self.media_item, 'config_update'):
2640+ self.media_item.search_edit = MagicMock()
2641+
2642+ # WHEN: Calling initialise()
2643+ self.media_item.initialise()
2644+
2645+ # THEN: The search_edit search types should have been set and that the current search type should be set to
2646+ # 'Combined'
2647+ self.assertTrue(self.media_item.search_edit.set_search_types.called)
2648+ self.media_item.search_edit.set_current_search_type.assert_called_once_with(BibleSearch.Combined)
2649+
2650+ def test_populate_bible_combo_boxes(self):
2651+ """
2652+ Test populate_bible_combo_boxes method
2653+ """
2654+ # GIVEN: An instance of :class:`MediaManagerItem` and mocked out settings class with known values
2655+ bible_1 = MagicMock()
2656+ bible_2 = MagicMock()
2657+ bible_3 = MagicMock()
2658+ self.setting_values = {'bibles/primary bible': bible_2}
2659+ self.media_item.version_combo_box = MagicMock()
2660+ self.media_item.second_combo_box = MagicMock()
2661+ self.mocked_plugin.manager.get_bibles.return_value = \
2662+ {'Bible 2': bible_2, 'Bible 1': bible_1, 'Bible 3': bible_3}
2663+ with patch('openlp.plugins.bibles.lib.mediaitem.get_locale_key', side_effect=lambda x: x), \
2664+ patch('openlp.plugins.bibles.lib.mediaitem.find_and_set_in_combo_box'):
2665+
2666+ # WHEN: Calling populate_bible_combo_boxes
2667+ self.media_item.populate_bible_combo_boxes()
2668+
2669+ # THEN: The bible combo boxes should be filled with the bible names and data, in a sorted order.
2670+ self.media_item.version_combo_box.addItem.assert_has_calls(
2671+ [call('Bible 1', bible_1), call('Bible 2', bible_2), call('Bible 3', bible_3)])
2672+ self.media_item.second_combo_box.addItem.assert_has_calls(
2673+ [call('', None), call('Bible 1', bible_1), call('Bible 2', bible_2), call('Bible 3', bible_3)])
2674+
2675+ def test_reload_bibles(self):
2676+ """
2677+ Test reload_bibles
2678+ """
2679+ # GIVEN: An instance of :class:`MediaManagerItem` and mocked out settings class with known values
2680+ with patch.object(self.media_item, 'populate_bible_combo_boxes') as mocked_populate_bible_combo_boxes:
2681+ # WHEN: Calling reload_bibles()
2682+ self.media_item.reload_bibles()
2683+
2684+ # THEN: The manager reload_bibles method should have been called and the bible combo boxes updated
2685+ self.mocked_plugin.manager.reload_bibles.assert_called_once_with()
2686+ mocked_populate_bible_combo_boxes.assert_called_once_with()
2687+
2688+ def test_get_common_books_no_second_book(self):
2689+ """
2690+ Test get_common_books when called with out a second bible
2691+ """
2692+ # GIVEN: An instance of :class:`MediaManagerItem` and a mocked first bible
2693+ # WHEN: Calling get_common_books with only one bible
2694+ result = self.media_item.get_common_books(self.mocked_bible_1)
2695+
2696+ # THEN: The book of the bible should be returned
2697+ self.assertEqual(result, self.book_list_1)
2698+
2699+ def test_get_common_books_second_book(self):
2700+ """
2701+ Test get_common_books when called with a second bible
2702+ """
2703+ # GIVEN: An instance of :class:`MediaManagerItem` and two mocked bibles with differing books
2704+ # WHEN: Calling get_common_books with two bibles
2705+ result = self.media_item.get_common_books(self.mocked_bible_1, self.mocked_bible_2)
2706+
2707+ # THEN: Only the books contained in both bibles should be returned
2708+ self.assertEqual(result, [self.mocked_book_2, self.mocked_book_3])
2709+
2710+ def test_initialise_advanced_bible_no_bible(self):
2711+ """
2712+ Test initialise_advanced_bible when there is no main bible
2713+ """
2714+ # GIVEN: An instance of :class:`MediaManagerItem`
2715+ self.media_item.select_book_combo_box = MagicMock()
2716+ with patch.object(self.media_item, 'get_common_books') as mocked_get_common_books:
2717+
2718+ # WHEN: Calling initialise_advanced_bible() when there is no main bible
2719+ self.media_item.bible = None
2720+ result = self.media_item.initialise_advanced_bible()
2721+
2722+ # THEN: initialise_advanced_bible should return with put calling get_common_books
2723+ self.assertIsNone(result)
2724+ mocked_get_common_books.assert_not_called()
2725+
2726+ def test_initialise_advanced_bible_add_books_with_last_id_found(self):
2727+ """
2728+ Test initialise_advanced_bible when the last_id argument is supplied and it is found in the list
2729+ """
2730+ # GIVEN: An instance of :class:`MediaManagerItem` and a mocked_book_combo_box which simulates data being found
2731+ # in the list
2732+ self.media_item.select_book_combo_box = MagicMock(**{'findData.return_value': 2})
2733+ with patch.object(self.media_item, 'get_common_books', return_value=self.book_list_1), \
2734+ patch.object(self.media_item, 'on_advanced_book_combo_box'):
2735+
2736+ # WHEN: Calling initialise_advanced_bible() with the last_id argument set
2737+ self.media_item.bible = MagicMock()
2738+ self.media_item.initialise_advanced_bible(10)
2739+
2740+ # THEN: The books should be added to the combo box, and the chosen book should be reselected
2741+ self.media_item.select_book_combo_box.addItem.assert_has_calls(
2742+ [call('Book 1', 1), call('Book 2', 2), call('Book 3', 3)])
2743+ self.media_item.select_book_combo_box.setCurrentIndex.assert_called_once_with(2)
2744+
2745+ def test_initialise_advanced_bible_add_books_with_last_id_not_found(self):
2746+ """
2747+ Test initialise_advanced_bible when the last_id argument is supplied and it is not found in the list
2748+ """
2749+ # GIVEN: An instance of :class:`MediaManagerItem` and a mocked_book_combo_box which simulates data not being
2750+ # found in the list
2751+ self.media_item.select_book_combo_box = MagicMock(**{'findData.return_value': -1})
2752+ with patch.object(self.media_item, 'get_common_books', return_value=self.book_list_1), \
2753+ patch.object(self.media_item, 'on_advanced_book_combo_box'):
2754+
2755+ # WHEN: Calling initialise_advanced_bible() with the last_id argument set
2756+ self.media_item.bible = MagicMock()
2757+ self.media_item.initialise_advanced_bible(10)
2758+
2759+ # THEN: The books should be added to the combo box, and the first book should be selected
2760+ self.media_item.select_book_combo_box.addItem.assert_has_calls(
2761+ [call('Book 1', 1), call('Book 2', 2), call('Book 3', 3)])
2762+ self.media_item.select_book_combo_box.setCurrentIndex.assert_called_once_with(0)
2763+
2764+ def test_update_auto_completer_search_no_bible(self):
2765+ """
2766+ Test update_auto_completer when there is no main bible selected and the search_edit type is
2767+ 'BibleSearch.Reference'
2768+ """
2769+ # GIVEN: An instance of :class:`MediaManagerItem` and a mocked search_edit
2770+ mocked_search_edit = MagicMock(**{'current_search_type.return_value': BibleSearch.Reference})
2771+ self.media_item.search_edit = mocked_search_edit
2772+ self.media_item.bible = None
2773+ with patch.object(self.media_item, 'get_common_books') as mocked_get_common_books, \
2774+ patch('openlp.plugins.bibles.lib.mediaitem.set_case_insensitive_completer') \
2775+ as mocked_set_case_insensitive_completer:
2776+
2777+ # WHEN: Calling update_auto_completer
2778+ self.media_item.update_auto_completer()
2779+
2780+ # THEN: get_common_books should not have been called. set_case_insensitive_completer should have been called
2781+ # with an empty list
2782+ mocked_get_common_books.assert_not_called()
2783+ mocked_set_case_insensitive_completer.assert_called_once_with([], mocked_search_edit)
2784+
2785+ def test_update_auto_completer_search_reference_type(self):
2786+ """
2787+ Test update_auto_completer when a main bible is selected and the search_edit type is 'BibleSearch.Reference'
2788+ """
2789+ # GIVEN: An instance of :class:`MediaManagerItem` and a mocked search_edit
2790+ mocked_search_edit = MagicMock(**{'current_search_type.return_value': BibleSearch.Reference})
2791+ self.media_item.search_edit = mocked_search_edit
2792+ self.media_item.bible = MagicMock()
2793+ with patch.object(self.media_item, 'get_common_books', return_value=self.book_list_1), \
2794+ patch('openlp.plugins.bibles.lib.mediaitem.get_locale_key', side_effect=lambda x: x), \
2795+ patch('openlp.plugins.bibles.lib.mediaitem.set_case_insensitive_completer') \
2796+ as mocked_set_case_insensitive_completer:
2797+
2798+ # WHEN: Calling update_auto_completer
2799+ self.media_item.update_auto_completer()
2800+
2801+ # THEN: set_case_insensitive_completer should have been called with the names of the books in order
2802+ mocked_set_case_insensitive_completer.assert_called_once_with(
2803+ ['Book 1', 'Book 2', 'Book 3'], mocked_search_edit)
2804+
2805+ def test_update_auto_completer_search_combined_type(self):
2806+ """
2807+ Test update_auto_completer when a main bible is selected and the search_edit type is 'BibleSearch.Combined'
2808+ """
2809+ # GIVEN: An instance of :class:`MediaManagerItem` and a mocked search_edit
2810+ mocked_search_edit = MagicMock(**{'current_search_type.return_value': BibleSearch.Combined})
2811+ self.media_item.search_edit = mocked_search_edit
2812+ self.media_item.bible = MagicMock()
2813+ with patch.object(self.media_item, 'get_common_books', return_value=self.book_list_1), \
2814+ patch('openlp.plugins.bibles.lib.mediaitem.get_locale_key', side_effect=lambda x: x), \
2815+ patch('openlp.plugins.bibles.lib.mediaitem.set_case_insensitive_completer') \
2816+ as mocked_set_case_insensitive_completer:
2817+
2818+ # WHEN: Calling update_auto_completer
2819+ self.media_item.update_auto_completer()
2820+
2821+ # THEN: set_case_insensitive_completer should have been called with the names of the books in order
2822+ mocked_set_case_insensitive_completer.assert_called_once_with(
2823+ ['Book 1', 'Book 2', 'Book 3'], mocked_search_edit)
2824+
2825+ def test_on_import_click_no_import_wizzard_attr(self):
2826+ """
2827+ Test on_import_click when media_item does not have the `import_wizard` attribute. And the wizard was canceled.
2828+ """
2829+ # GIVEN: An instance of :class:`MediaManagerItem` and a mocked BibleImportForm
2830+ mocked_bible_import_form_instance = MagicMock(**{'exec.return_value': False})
2831+ with patch('openlp.plugins.bibles.lib.mediaitem.BibleImportForm',
2832+ return_value=mocked_bible_import_form_instance) as mocked_bible_import_form, \
2833+ patch.object(self.media_item, 'reload_bibles') as mocked_reload_bibles:
2834+
2835+ # WHEN: Calling on_import_click
2836+ self.media_item.on_import_click()
2837+
2838+ # THEN: BibleImport wizard should have been instianted and reload_bibles should not have been called
2839+ self.assertTrue(mocked_bible_import_form.called)
2840+ self.assertFalse(mocked_reload_bibles.called)
2841+
2842+ def test_on_import_click_wizzard_not_canceled(self):
2843+ """
2844+ Test on_import_click when the media item has the import_wizzard attr set and wizard completes sucessfully.
2845+ """
2846+ # GIVEN: An instance of :class:`MediaManagerItem` and a mocked import_wizard
2847+ mocked_import_wizard = MagicMock(**{'exec.return_value': True})
2848+ self.media_item.import_wizard = mocked_import_wizard
2849+
2850+ with patch.object(self.media_item, 'reload_bibles') as mocked_reload_bibles:
2851+
2852+ # WHEN: Calling on_import_click
2853+ self.media_item.on_import_click()
2854+
2855+ # THEN: BibleImport wizard should have been instianted and reload_bibles should not have been called
2856+ self.assertFalse(mocked_import_wizard.called)
2857+ self.assertTrue(mocked_reload_bibles.called)
2858+
2859+ def test_on_edit_click_no_bible(self):
2860+ """
2861+ Test on_edit_click when there is no main bible selected
2862+ """
2863+ # GIVEN: An instance of :class:`MediaManagerItem`
2864+ with patch('openlp.plugins.bibles.lib.mediaitem.EditBibleForm') as mocked_edit_bible_form:
2865+
2866+ # WHEN: A main bible is not selected and on_edit_click is called
2867+ self.media_item.bible = None
2868+ self.media_item.on_edit_click()
2869+
2870+ # THEN: EditBibleForm should not have been instianted
2871+ self.assertFalse(mocked_edit_bible_form.called)
2872+
2873+ def test_on_edit_click_user_cancel_edit_form(self):
2874+ """
2875+ Test on_edit_click when the user cancels the EditBibleForm
2876+ """
2877+ # GIVEN: An instance of :class:`MediaManagerItem` and a mocked EditBibleForm which returns False when exec is
2878+ # called
2879+ self.media_item.bible = MagicMock()
2880+ mocked_edit_bible_form_instance = MagicMock(**{'exec.return_value': False})
2881+ with patch('openlp.plugins.bibles.lib.mediaitem.EditBibleForm', return_value=mocked_edit_bible_form_instance) \
2882+ as mocked_edit_bible_form, \
2883+ patch.object(self.media_item, 'reload_bibles') as mocked_reload_bibles:
2884+
2885+ # WHEN: on_edit_click is called, and the user cancels the EditBibleForm
2886+ self.media_item.on_edit_click()
2887+
2888+ # THEN: EditBibleForm should have been been instianted but reload_bibles should not have been called
2889+ self.assertTrue(mocked_edit_bible_form.called)
2890+ self.assertFalse(mocked_reload_bibles.called)
2891+
2892+ def test_on_edit_click_user_accepts_edit_form(self):
2893+ """
2894+ Test on_edit_click when the user accepts the EditBibleForm
2895+ """
2896+ # GIVEN: An instance of :class:`MediaManagerItem` and a mocked EditBibleForm which returns True when exec is
2897+ # called
2898+ self.media_item.bible = MagicMock()
2899+ mocked_edit_bible_form_instance = MagicMock(**{'exec.return_value': True})
2900+ with patch('openlp.plugins.bibles.lib.mediaitem.EditBibleForm',
2901+ return_value=mocked_edit_bible_form_instance) \
2902+ as mocked_edit_bible_form, \
2903+ patch.object(self.media_item, 'reload_bibles') as mocked_reload_bibles:
2904+
2905+ # WHEN: on_edit_click is called, and the user accpets the EditBibleForm
2906+ self.media_item.on_edit_click()
2907+
2908+ # THEN: EditBibleForm should have been been instianted and reload_bibles should have been called
2909+ self.assertTrue(mocked_edit_bible_form.called)
2910+ self.assertTrue(mocked_reload_bibles.called)
2911+
2912+ def test_on_delete_click_no_bible(self):
2913+ """
2914+ Test on_delete_click when there is no main bible selected
2915+ """
2916+ # GIVEN: An instance of :class:`MediaManagerItem`
2917+ with patch('openlp.plugins.bibles.lib.mediaitem.QtWidgets.QMessageBox') as mocked_qmessage_box:
2918+
2919+ # WHEN: A main bible is not selected and on_delete_click is called
2920+ self.media_item.bible = None
2921+ self.media_item.on_delete_click()
2922+
2923+ # THEN: QMessageBox.question should not have been called
2924+ self.assertFalse(mocked_qmessage_box.question.called)
2925+
2926+ def test_on_delete_click_response_no(self):
2927+ """
2928+ Test on_delete_click when the user selects no from the message box
2929+ """
2930+ # GIVEN: An instance of :class:`MediaManagerItem` and a QMessageBox which reutrns QtWidgets.QMessageBox.No
2931+ self.media_item.bible = MagicMock()
2932+ with patch('openlp.plugins.bibles.lib.mediaitem.QtWidgets.QMessageBox.question',
2933+ return_value=QtWidgets.QMessageBox.No) as mocked_qmessage_box:
2934+
2935+ # WHEN: on_delete_click is called
2936+ self.media_item.on_delete_click()
2937+
2938+ # THEN: QMessageBox.question should have been called, but the delete_bible should not have been called
2939+ self.assertTrue(mocked_qmessage_box.called)
2940+ self.assertFalse(self.mocked_plugin.manager.delete_bible.called)
2941+
2942+ def test_on_delete_click_response_yes(self):
2943+ """
2944+ Test on_delete_click when the user selects yes from the message box
2945+ """
2946+ # GIVEN: An instance of :class:`MediaManagerItem` and a QMessageBox which reutrns QtWidgets.QMessageBox.Yes
2947+ self.media_item.bible = MagicMock()
2948+ with patch('openlp.plugins.bibles.lib.mediaitem.QtWidgets.QMessageBox.question',
2949+ return_value=QtWidgets.QMessageBox.Yes) as mocked_qmessage_box, \
2950+ patch.object(self.media_item, 'reload_bibles'):
2951+
2952+ # WHEN: on_delete_click is called
2953+ self.media_item.on_delete_click()
2954+
2955+ # THEN: QMessageBox.question should and delete_bible should not have been called
2956+ self.assertTrue(mocked_qmessage_box.called)
2957+ self.assertTrue(self.mocked_plugin.manager.delete_bible.called)
2958+
2959+ def test_on_search_tab_bar_current_changed_search_tab_selected(self):
2960+ """
2961+ Test on_search_tab_bar_current_changed when the search_tab is selected
2962+ """
2963+ # GIVEN: An instance of :class:`MediaManagerItem` and mocked out search_tab and select_tab
2964+ self.media_item.search_tab = MagicMock()
2965+ self.media_item.select_tab = MagicMock()
2966+ with patch.object(self.media_item, 'on_focus'):
2967+
2968+ # WHEN: The search_tab has been selected
2969+ self.media_item.on_search_tab_bar_current_changed(0)
2970+
2971+ # THEN: search_tab should be setVisible and select_tab should be hidder
2972+ self.media_item.search_tab.setVisible.assert_called_once_with(True)
2973+ self.media_item.select_tab.setVisible.assert_called_once_with(False)
2974+
2975+ def test_on_search_tab_bar_current_changed_select_tab_selected(self):
2976+ """
2977+ Test on_search_tab_bar_current_changed when the select_tab is selected
2978+ """
2979+ # GIVEN: An instance of :class:`MediaManagerItem` and mocked out search_tab and select_tab
2980+ self.media_item.search_tab = MagicMock()
2981+ self.media_item.select_tab = MagicMock()
2982+ with patch.object(self.media_item, 'on_focus'):
2983+
2984+ # WHEN: The select_tab has been selected
2985+ self.media_item.on_search_tab_bar_current_changed(1)
2986+
2987+ # THEN: search_tab should be setVisible and select_tab should be hidder
2988+ self.media_item.search_tab.setVisible.assert_called_once_with(False)
2989+ self.media_item.select_tab.setVisible.assert_called_once_with(True)
2990+
2991+ def test_on_book_order_button_toggled_checked(self):
2992+ """
2993+ Test that 'on_book_order_button_toggled' changes the order of the book list
2994+ """
2995+ self.media_item.select_book_combo_box = MagicMock()
2996+
2997+ # WHEN: When the book_order_button is checked
2998+ self.media_item.on_book_order_button_toggled(True)
2999+
3000+ # THEN: The select_book_combo_box model should have been sorted
3001+ self.media_item.select_book_combo_box.model().sort.assert_called_once_with(0)
3002+
3003+ def test_on_book_order_button_toggled_un_checked(self):
3004+ """
3005+ Test that 'on_book_order_button_toggled' changes the order of the book list
3006+ """
3007+ self.media_item.select_book_combo_box = MagicMock()
3008+
3009+ # WHEN: When the book_order_button is un-checked
3010+ self.media_item.on_book_order_button_toggled(False)
3011+
3012+ # THEN: The select_book_combo_box model sort should have been reset
3013+ self.media_item.select_book_combo_box.model().sort.assert_called_once_with(-1)
3014
3015 def test_on_clear_button_clicked(self):
3016 """
3017- Test that the on_clear_button_clicked works properly. (Used by Bible search tab)
3018+ Test on_clear_button_clicked
3019 """
3020- # GIVEN: Mocked list_view, check_search_results & quick_search_edit.
3021+ # GIVEN: An instance of :class:`MediaManagerItem` and mocked out search_tab and select_tab and a mocked out
3022+ # list_view and search_edit
3023 self.media_item.list_view = MagicMock()
3024- self.media_item.quick_search_edit = MagicMock()
3025-
3026- # WHEN: on_clear_button_clicked is called
3027- self.media_item.on_clear_button_clicked()
3028-
3029- # THEN: Search result should be reset and search field should receive focus.
3030- self.media_item.list_view.clear.assert_called_once_with(),
3031- self.media_item.quick_search_edit.clear.assert_called_once_with(),
3032- self.media_item.quick_search_edit.setFocus.assert_called_once_with()
3033+ self.media_item.search_edit = MagicMock()
3034+ with patch.object(self.media_item, 'on_focus'):
3035+
3036+ # WHEN: Calling on_clear_button_clicked
3037+ self.media_item.on_clear_button_clicked()
3038+
3039+ # THEN: The list_view and the search_edit should be cleared
3040+ self.media_item.list_view.clear.assert_called_once_with()
3041+ self.media_item.search_edit.clear.assert_called_once_with()
3042
3043 def test_on_lock_button_toggled_search_tab_lock_icon(self):
3044 """
3045- Test that "on_lock_button_toggled" gives focus to the right field and toggles the lock properly.
3046+ Test that "on_lock_button_toggled" toggles the lock properly.
3047 """
3048- # GIVEN: Mocked sender & Search edit, quickTab returning value = True on isVisible.
3049- self.media_item.sender = MagicMock()
3050- self.media_item.quick_search_edit = MagicMock()
3051- self.media_item.quickTab = MagicMock(**{'isVisible.return_value': True})
3052-
3053+ # GIVEN: An instance of :class:`MediaManagerItem` a mocked sender and list_view
3054+ self.media_item.list_view = MagicMock()
3055 self.media_item.lock_icon = 'lock icon'
3056- sender_instance_mock = MagicMock()
3057- self.media_item.sender = MagicMock(return_value=sender_instance_mock)
3058-
3059- # WHEN: on_lock_button_toggled is called and checked returns = True.
3060- self.media_item.on_lock_button_toggled(True)
3061-
3062- # THEN: on_quick_search_edit should receive focus and Lock icon should be set.
3063- self.media_item.quick_search_edit.setFocus.assert_called_once_with()
3064- sender_instance_mock.setIcon.assert_called_once_with('lock icon')
3065+ mocked_sender_instance = MagicMock()
3066+ with patch.object(self.media_item, 'sender', return_value=mocked_sender_instance):
3067+
3068+ # WHEN: When the lock_button is checked
3069+ self.media_item.on_lock_button_toggled(True)
3070+
3071+ # THEN: list_view should be 'locked' and the lock icon set
3072+ self.assertTrue(self.media_item.list_view.locked)
3073+ mocked_sender_instance.setIcon.assert_called_once_with('lock icon')
3074
3075 def test_on_lock_button_toggled_unlock_icon(self):
3076 """
3077- Test that lock button unlocks properly and lock toggles properly.
3078+ Test that "on_lock_button_toggled" toggles the lock properly.
3079 """
3080- # GIVEN: Mocked sender & Search edit, quickTab returning value = False on isVisible.
3081- self.media_item.sender = MagicMock()
3082- self.media_item.quick_search_edit = MagicMock()
3083- self.media_item.quickTab = MagicMock()
3084- self.media_item.quickTab.isVisible = MagicMock()
3085+ # GIVEN: An instance of :class:`MediaManagerItem` a mocked sender and list_view
3086+ self.media_item.list_view = MagicMock()
3087 self.media_item.unlock_icon = 'unlock icon'
3088- sender_instance_mock = MagicMock()
3089- self.media_item.sender = MagicMock(return_value=sender_instance_mock)
3090-
3091- # WHEN: on_lock_button_toggled is called and checked returns = False.
3092- self.media_item.on_lock_button_toggled(False)
3093-
3094- # THEN: Unlock icon should be set.
3095- sender_instance_mock.setIcon.assert_called_once_with('unlock icon')
3096+ mocked_sender_instance = MagicMock()
3097+ with patch.object(self.media_item, 'sender', return_value=mocked_sender_instance):
3098+
3099+ # WHEN: When the lock_button is unchecked
3100+ self.media_item.on_lock_button_toggled(False)
3101+
3102+ # THEN: list_view should be 'unlocked' and the unlock icon set
3103+ self.assertFalse(self.media_item.list_view.locked)
3104+ mocked_sender_instance.setIcon.assert_called_once_with('unlock icon')
3105+
3106+ def test_on_style_combo_box_changed(self):
3107+ """
3108+ Test on_style_combo_box_index_changed
3109+ """
3110+ # GIVEN: An instance of :class:`MediaManagerItem` a mocked media_item.settings
3111+ self.media_item.settings = MagicMock()
3112+
3113+ # WHEN: Calling on_style_combo_box_index_changed
3114+ self.media_item.on_style_combo_box_index_changed(2)
3115+
3116+ # THEN: The layput_style settimg should have been set
3117+ self.assertEqual(self.media_item.settings.layout_style, 2)
3118+ self.media_item.settings.layout_style_combo_box.setCurrentIndex.assert_called_once_with(2)
3119+ self.mocked_settings_instance.setValue.assert_called_once_with('bibles/verse layout style', 2)
3120+
3121+ def test_on_version_combo_box_index_changed_no_bible(self):
3122+ """
3123+ Test on_version_combo_box_index_changed when there is no main bible.
3124+ """
3125+ # GIVEN: An instance of :class:`MediaManagerItem` and mocked media_item.settings and select_book_combo_box
3126+ self.media_item.version_combo_box = MagicMock(**{'currentData.return_value': None})
3127+ self.media_item.select_book_combo_box = MagicMock()
3128+ with patch.object(self.media_item, 'initialise_advanced_bible') as mocked_initialise_advanced_bible:
3129+
3130+ # WHEN: Calling on_version_combo_box_index_changed
3131+ self.media_item.on_version_combo_box_index_changed()
3132+
3133+ # THEN: The vesion should be saved to settings and the 'select tab' should be initialised
3134+ self.assertFalse(self.mocked_settings_instance.setValue.called)
3135+ self.assertTrue(self.media_item.initialise_advanced_bible.called)
3136+
3137+ def test_on_version_combo_box_index_changed_bible_selected(self):
3138+ """
3139+ Test on_version_combo_box_index_changed when a bible has been selected.
3140+ """
3141+ # GIVEN: An instance of :class:`MediaManagerItem` and mocked media_item.settings and select_book_combo_box
3142+ mocked_bible_db = MagicMock()
3143+ mocked_bible_db.name = 'ABC'
3144+ self.media_item.version_combo_box = MagicMock(**{'currentData.return_value': mocked_bible_db})
3145+ self.media_item.select_book_combo_box = MagicMock()
3146+ with patch.object(self.media_item, 'initialise_advanced_bible') as mocked_initialise_advanced_bible:
3147+
3148+ # WHEN: Calling on_version_combo_box_index_changed
3149+ self.media_item.on_version_combo_box_index_changed()
3150+
3151+ # THEN: The vesion should be saved to settings and the 'select tab' should be initialised
3152+ self.mocked_settings_instance.setValue.assert_called_once_with('bibles/primary bible', 'ABC')
3153+ self.assertTrue(self.media_item.initialise_advanced_bible.called)
3154+
3155+ def test_on_second_combo_box_index_changed_mode_not_changed(self):
3156+ """
3157+ Test on_second_combo_box_index_changed when the user does not change from dual mode
3158+ results and the user chooses no to the message box
3159+ """
3160+ # GIVEN: An instance of :class:`MediaManagerItem`
3161+ self.media_item.list_view = MagicMock(**{'count.return_value': 5})
3162+ self.media_item.style_combo_box = MagicMock()
3163+ self.media_item.select_book_combo_box = MagicMock()
3164+ with patch.object(self.media_item, 'initialise_advanced_bible') as mocked_initialise_advanced_bible, \
3165+ patch('openlp.plugins.bibles.lib.mediaitem.critical_error_message_box') \
3166+ as mocked_critical_error_message_box:
3167+
3168+ # WHEN: The previously selected bible is one bible and the new selection is annother bible
3169+ self.media_item.second_bible = self.mocked_bible_1
3170+ self.media_item.second_combo_box = MagicMock(**{'currentData.return_value': self.mocked_bible_2})
3171+ self.media_item.on_second_combo_box_index_changed(5)
3172+
3173+ # THEN: The new bible should now be the current bible
3174+ self.assertFalse(mocked_critical_error_message_box.called)
3175+ self.media_item.style_combo_box.setEnabled.assert_called_once_with(False)
3176+ self.assertEqual(self.media_item.second_bible, self.mocked_bible_2)
3177+
3178+ def test_on_second_combo_box_index_changed_single_to_dual_user_abort(self):
3179+ """
3180+ Test on_second_combo_box_index_changed when the user changes from single to dual bible mode, there are search
3181+ results and the user chooses no to the message box
3182+ """
3183+ # GIVEN: An instance of :class:`MediaManagerItem`
3184+ self.media_item.list_view = MagicMock(**{'count.return_value': 5})
3185+ self.media_item.style_combo_box = MagicMock()
3186+ self.media_item.select_book_combo_box = MagicMock()
3187+ with patch.object(self.media_item, 'initialise_advanced_bible') as mocked_initialise_advanced_bible, \
3188+ patch('openlp.plugins.bibles.lib.mediaitem.critical_error_message_box',
3189+ return_value=QtWidgets.QMessageBox.No) as mocked_critical_error_message_box:
3190+
3191+ # WHEN: The previously selected bible is None and the new selection is a bible and the user selects yes
3192+ # to the dialog box
3193+ self.media_item.second_bible = None
3194+ self.media_item.second_combo_box = MagicMock(**{'currentData.return_value': self.mocked_bible_1})
3195+ self.media_item.on_second_combo_box_index_changed(5)
3196+
3197+ # THEN: The list_view should be cleared and the currently selected bible should not be channged
3198+ self.assertTrue(mocked_critical_error_message_box.called)
3199+ self.assertTrue(self.media_item.second_combo_box.setCurrentIndex.called)
3200+ self.assertFalse(self.media_item.style_combo_box.setEnabled.called)
3201+ self.assertEqual(self.media_item.second_bible, None)
3202+
3203+ def test_on_second_combo_box_index_changed_single_to_dual(self):
3204+ """
3205+ Test on_second_combo_box_index_changed when the user changes from single to dual bible mode, there are search
3206+ results and the user chooses yes to the message box
3207+ """
3208+ # GIVEN: An instance of :class:`MediaManagerItem`
3209+ self.media_item.list_view = MagicMock(**{'count.return_value': 5})
3210+ self.media_item.style_combo_box = MagicMock()
3211+ self.media_item.select_book_combo_box = MagicMock()
3212+ with patch.object(self.media_item, 'initialise_advanced_bible') as mocked_initialise_advanced_bible, \
3213+ patch('openlp.plugins.bibles.lib.mediaitem.critical_error_message_box',
3214+ return_value=QtWidgets.QMessageBox.Yes) as mocked_critical_error_message_box:
3215+
3216+ # WHEN: The previously selected bible is None and the new selection is a bible and the user selects yes
3217+ # to the dialog box
3218+ self.media_item.second_bible = None
3219+ self.media_item.second_combo_box = MagicMock(**{'currentData.return_value': self.mocked_bible_1})
3220+ self.media_item.on_second_combo_box_index_changed(5)
3221+
3222+ # THEN: The list_view should be cleared and the selected bible should be set as the current bible
3223+ self.assertTrue(mocked_critical_error_message_box.called)
3224+ self.media_item.list_view.clear.assert_called_once_with(override_lock=True)
3225+ self.media_item.style_combo_box.setEnabled.assert_called_once_with(False)
3226+ self.assertTrue(mocked_initialise_advanced_bible.called)
3227+ self.assertEqual(self.media_item.second_bible, self.mocked_bible_1)
3228+
3229+ def test_on_second_combo_box_index_changed_dual_to_single(self):
3230+ """
3231+ Test on_second_combo_box_index_changed when the user changes from dual to single bible mode, there are search
3232+ results and the user chooses yes to the message box
3233+ """
3234+ # GIVEN: An instance of :class:`MediaManagerItem`
3235+ self.media_item.list_view = MagicMock(**{'count.return_value': 5})
3236+ self.media_item.style_combo_box = MagicMock()
3237+ self.media_item.select_book_combo_box = MagicMock()
3238+ with patch.object(self.media_item, 'initialise_advanced_bible') as mocked_initialise_advanced_bible, \
3239+ patch('openlp.plugins.bibles.lib.mediaitem.critical_error_message_box',
3240+ return_value=QtWidgets.QMessageBox.Yes) as mocked_critical_error_message_box:
3241+ # WHEN: The previously is a bible new selection is None and the user selects yes
3242+ # to the dialog box
3243+ self.media_item.second_bible = self.mocked_bible_1
3244+ self.media_item.second_combo_box = MagicMock(**{'currentData.return_value': None})
3245+ self.media_item.on_second_combo_box_index_changed(0)
3246+
3247+ # THEN: The list_view should be cleared and the selected bible should be set as the current bible
3248+ self.assertTrue(mocked_critical_error_message_box.called)
3249+ self.media_item.list_view.clear.assert_called_once_with(override_lock=True)
3250+ self.media_item.style_combo_box.setEnabled.assert_called_once_with(True)
3251+ self.assertFalse(mocked_initialise_advanced_bible.called)
3252+ self.assertEqual(self.media_item.second_bible, None)
3253+
3254+ def test_on_advanced_book_combo_box(self):
3255+ """
3256+ Test on_advanced_book_combo_box when the book returns 0 for the verse count.
3257+ """
3258+ # GIVEN: An instance of :class:`MediaManagerItem` and a mocked get_verse_count_by_book_ref_id which returns 0
3259+ self.media_item.select_book_combo_box = MagicMock(**{'currentData.return_value': 2})
3260+ self.media_item.bible = self.mocked_bible_1
3261+ self.mocked_plugin.manager.get_verse_count_by_book_ref_id.return_value = 0
3262+ self.media_item.search_button = MagicMock()
3263+ with patch('openlp.plugins.bibles.lib.mediaitem.critical_error_message_box') \
3264+ as mocked_critical_error_message_box:
3265+
3266+ # WHEN: Calling on_advanced_book_combo_box
3267+ self.media_item.on_advanced_book_combo_box()
3268+
3269+ # THEN: The user should be informed that the bible cannot be used and the search button should be disabled
3270+ self.mocked_plugin.manager.get_book_by_id.assert_called_once_with('Bible 1', 2)
3271+ self.media_item.search_button.setEnabled.assert_called_once_with(False)
3272+ self.assertTrue(mocked_critical_error_message_box.called)
3273+
3274+ def test_on_advanced_book_combo_box_set_up_comboboxes(self):
3275+ """
3276+ Test on_advanced_book_combo_box when the book returns 6 for the verse count.
3277+ """
3278+ # GIVEN: An instance of :class:`MediaManagerItem` and a mocked get_verse_count_by_book_ref_id which returns 6
3279+ self.media_item.from_chapter = 0
3280+ self.media_item.to_chapter = 0
3281+ self.media_item.from_verse = 0
3282+ self.media_item.to_verse = 0
3283+ self.media_item.select_book_combo_box = MagicMock(**{'currentData.return_value': 2})
3284+ self.media_item.bible = self.mocked_bible_1
3285+ self.mocked_plugin.manager.get_verse_count_by_book_ref_id.return_value = 6
3286+ self.media_item.search_button = MagicMock()
3287+ with patch.object(self.media_item, 'adjust_combo_box') as mocked_adjust_combo_box:
3288+ # WHEN: Calling on_advanced_book_combo_box
3289+ self.media_item.on_advanced_book_combo_box()
3290+
3291+ # THEN: The verse selection combobox's should be set up
3292+ self.mocked_plugin.manager.get_book_by_id.assert_called_once_with('Bible 1', 2)
3293+ self.media_item.search_button.setEnabled.assert_called_once_with(True)
3294+ self.assertEqual(mocked_adjust_combo_box.call_count, 4)
3295+
3296+ def test_on_from_chapter_activated_invalid_to_chapter(self):
3297+ """
3298+ Test on_from_chapter_activated when the to_chapter is less than the from_chapter
3299+ """
3300+ # GIVEN: An instance of :class:`MediaManagerItem`, some mocked comboboxes with test data
3301+ self.media_item.chapter_count = 25
3302+ self.media_item.bible = self.mocked_bible_1
3303+ self.media_item.select_book_combo_box = MagicMock()
3304+ self.media_item.from_chapter = MagicMock(**{'currentData.return_value': 10})
3305+ self.media_item.to_chapter = MagicMock(**{'currentData.return_value': 5})
3306+ self.media_item.from_verse = MagicMock()
3307+ self.media_item.to_verse = MagicMock()
3308+ self.mocked_plugin.manager.get_verse_count_by_book_ref_id.return_value = 20
3309+ with patch.object(self.media_item, 'adjust_combo_box') as mocked_adjust_combo_box:
3310+
3311+ # WHEN: Calling on_from_chapter_activated
3312+ self.media_item.on_from_chapter_activated()
3313+
3314+ # THEN: The to_verse and to_chapter comboboxes should be updated appropriately
3315+ self.assertEqual(mocked_adjust_combo_box.call_args_list, [
3316+ call(1, 20, self.media_item.from_verse), call(1, 20, self.media_item.to_verse, False),
3317+ call(10, 25, self.media_item.to_chapter, False)])
3318+
3319+ def test_on_from_chapter_activated_same_chapter(self):
3320+ """
3321+ Test on_from_chapter_activated when the to_chapter is the same as from_chapter
3322+ """
3323+ # GIVEN: An instance of :class:`MediaManagerItem`, some mocked comboboxes with test data
3324+ self.media_item.chapter_count = 25
3325+ self.media_item.bible = self.mocked_bible_1
3326+ self.media_item.select_book_combo_box = MagicMock()
3327+ self.media_item.from_chapter = MagicMock(**{'currentData.return_value': 5})
3328+ self.media_item.to_chapter = MagicMock(**{'currentData.return_value': 5})
3329+ self.media_item.from_verse = MagicMock()
3330+ self.media_item.to_verse = MagicMock()
3331+ self.mocked_plugin.manager.get_verse_count_by_book_ref_id.return_value = 20
3332+ with patch.object(self.media_item, 'adjust_combo_box') as mocked_adjust_combo_box:
3333+
3334+ # WHEN: Calling on_from_chapter_activated
3335+ self.media_item.on_from_chapter_activated()
3336+
3337+ # THEN: The to_verse and to_chapter comboboxes should be updated appropriately
3338+ self.assertEqual(mocked_adjust_combo_box.call_args_list, [
3339+ call(1, 20, self.media_item.from_verse), call(1, 20, self.media_item.to_verse, True),
3340+ call(5, 25, self.media_item.to_chapter, False)])
3341+
3342+ def test_on_from_chapter_activated_lower_chapter(self):
3343+ """
3344+ Test on_from_chapter_activated when the to_chapter is greater than the from_chapter
3345+ """
3346+ # GIVEN: An instance of :class:`MediaManagerItem`, some mocked comboboxes with test data
3347+ self.media_item.chapter_count = 25
3348+ self.media_item.bible = self.mocked_bible_1
3349+ self.media_item.select_book_combo_box = MagicMock()
3350+ self.media_item.from_chapter = MagicMock(**{'currentData.return_value': 5})
3351+ self.media_item.to_chapter = MagicMock(**{'currentData.return_value': 7})
3352+ self.media_item.from_verse = MagicMock()
3353+ self.media_item.to_verse = MagicMock()
3354+ self.mocked_plugin.manager.get_verse_count_by_book_ref_id.return_value = 20
3355+ with patch.object(self.media_item, 'adjust_combo_box') as mocked_adjust_combo_box:
3356+ # WHEN: Calling on_from_chapter_activated
3357+ self.media_item.on_from_chapter_activated()
3358+
3359+ # THEN: The to_verse and to_chapter comboboxes should be updated appropriately
3360+ self.assertEqual(mocked_adjust_combo_box.call_args_list, [
3361+ call(1, 20, self.media_item.from_verse), call(5, 25, self.media_item.to_chapter, True)])
3362+
3363+ def test_on_from_verse(self):
3364+ """
3365+ Test on_from_verse when the to_chapter is not equal to the from_chapter
3366+ """
3367+ # GIVEN: An instance of :class:`MediaManagerItem`, some mocked comboboxes with test data
3368+ self.media_item.select_book_combo_box = MagicMock()
3369+ self.media_item.from_chapter = MagicMock(**{'currentData.return_value': 2})
3370+ self.media_item.to_chapter = MagicMock(**{'currentData.return_value': 5})
3371+
3372+ # WHEN: Calling on_from_verse
3373+ self.media_item.on_from_verse()
3374+
3375+ # THEN: select_book_combo_box.currentData should nto be called
3376+ self.assertFalse(self.media_item.select_book_combo_box.currentData.called)
3377+
3378+ def test_on_from_verse_equal(self):
3379+ """
3380+ Test on_from_verse when the to_chapter is equal to the from_chapter
3381+ """
3382+ # GIVEN: An instance of :class:`MediaManagerItem`, some mocked comboboxes with test data
3383+ self.media_item.bible = self.mocked_bible_1
3384+ self.media_item.select_book_combo_box = MagicMock()
3385+ self.media_item.from_chapter = MagicMock(**{'currentData.return_value': 5})
3386+ self.media_item.to_chapter = MagicMock(**{'currentData.return_value': 5})
3387+ self.media_item.from_verse = MagicMock(**{'currentData.return_value': 7})
3388+ self.media_item.to_verse = MagicMock()
3389+ self.mocked_plugin.manager.get_verse_count_by_book_ref_id.return_value = 20
3390+ with patch.object(self.media_item, 'adjust_combo_box') as mocked_adjust_combo_box:
3391+
3392+ # WHEN: Calling on_from_verse
3393+ self.media_item.on_from_verse()
3394+
3395+ # THEN: The to_verse should have been updated
3396+ mocked_adjust_combo_box.assert_called_once_with(7, 20, self.media_item.to_verse, True)
3397+
3398+ def test_on_to_chapter_same_chapter_from_greater_than(self):
3399+ """
3400+ Test on_to_chapter when the to_chapter is equal to the from_chapter and the from_verse is greater than the
3401+ to_verse
3402+ """
3403+ # GIVEN: An instance of :class:`MediaManagerItem`, some mocked comboboxes with test data
3404+ self.media_item.bible = self.mocked_bible_1
3405+ self.media_item.select_book_combo_box = MagicMock()
3406+ self.media_item.from_chapter = MagicMock(**{'currentData.return_value': 5})
3407+ self.media_item.to_chapter = MagicMock(**{'currentData.return_value': 5})
3408+ self.media_item.from_verse = MagicMock(**{'currentData.return_value': 10})
3409+ self.media_item.to_verse = MagicMock(**{'currentData.return_value': 7})
3410+ self.mocked_plugin.manager.get_verse_count_by_book_ref_id.return_value = 20
3411+ with patch.object(self.media_item, 'adjust_combo_box') as mocked_adjust_combo_box:
3412+
3413+ # WHEN: Calling on_tp_chapter
3414+ self.media_item.on_to_chapter()
3415+
3416+ # THEN: The to_verse should have been updated
3417+ mocked_adjust_combo_box.assert_called_once_with(10, 20, self.media_item.to_verse)
3418+
3419+ def test_on_from_verse_chapters_not_equal(self):
3420+ """
3421+ Test on_from_verse when the to_chapter is not equal to the from_chapter
3422+ """
3423+ # GIVEN: An instance of :class:`MediaManagerItem`, some mocked comboboxes with test data
3424+ self.media_item.bible = self.mocked_bible_1
3425+ self.media_item.select_book_combo_box = MagicMock()
3426+ self.media_item.from_chapter = MagicMock(**{'currentData.return_value': 7})
3427+ self.media_item.to_chapter = MagicMock(**{'currentData.return_value': 5})
3428+ self.media_item.from_verse = MagicMock(**{'currentData.return_value': 10})
3429+ self.media_item.to_verse = MagicMock(**{'currentData.return_value': 7})
3430+ self.mocked_plugin.manager.get_verse_count_by_book_ref_id.return_value = 20
3431+ with patch.object(self.media_item, 'adjust_combo_box') as mocked_adjust_combo_box:
3432+
3433+ # WHEN: Calling on_from_chapter_activated
3434+ self.media_item.on_to_chapter()
3435+
3436+ # THEN: The to_verse should have been updated
3437+ mocked_adjust_combo_box.assert_called_once_with(1, 20, self.media_item.to_verse)
3438+
3439+ def test_on_from_verse_from_verse_less_than(self):
3440+ """
3441+ Test on_from_verse when the to_chapter is equal to the from_chapter and from_verse is less than to_verse
3442+ """
3443+ # GIVEN: An instance of :class:`MediaManagerItem`, some mocked comboboxes with test data
3444+ self.media_item.bible = self.mocked_bible_1
3445+ self.media_item.select_book_combo_box = MagicMock()
3446+ self.media_item.from_chapter = MagicMock(**{'currentData.return_value': 5})
3447+ self.media_item.to_chapter = MagicMock(**{'currentData.return_value': 5})
3448+ self.media_item.from_verse = MagicMock(**{'currentData.return_value': 6})
3449+ self.media_item.to_verse = MagicMock(**{'currentData.return_value': 7})
3450+ self.mocked_plugin.manager.get_verse_count_by_book_ref_id.return_value = 20
3451+ with patch.object(self.media_item, 'adjust_combo_box') as mocked_adjust_combo_box:
3452+
3453+ # WHEN: Calling on_from_chapter_activated
3454+ self.media_item.on_to_chapter()
3455+
3456+ # THEN: The to_verse should have been updated
3457+ mocked_adjust_combo_box.assert_called_once_with(1, 20, self.media_item.to_verse)
3458+
3459+ def test_adjust_combo_box_no_restore(self):
3460+ """
3461+ Test adjust_combo_box when being used with out the restore function
3462+ """
3463+ # GIVEN: An instance of :class:`MediaManagerItem`
3464+ mocked_combo_box = MagicMock()
3465+
3466+ # WHEN: Calling adjust_combo_box with out setting the kwarg `restore`
3467+ self.media_item.adjust_combo_box(10, 13, mocked_combo_box)
3468+
3469+ # THEN: The combo_box should be cleared, and new items added
3470+ mocked_combo_box.clear.assert_called_once_with()
3471+ self.assertEqual(mocked_combo_box.addItem.call_args_list,
3472+ [call('10', 10), call('11', 11), call('12', 12), call('13', 13)])
3473+
3474+ def test_adjust_combo_box_restore_found(self):
3475+ """
3476+ Test adjust_combo_box when being used with out the restore function
3477+ """
3478+ # GIVEN: An instance of :class:`MediaManagerItem`, with the 2nd item '12' selected
3479+ mocked_combo_box = MagicMock(**{'currentData.return_value': 12, 'findData.return_value': 2})
3480+
3481+ # WHEN: Calling adjust_combo_box with the kwarg `restore` set to True
3482+ self.media_item.adjust_combo_box(10, 13, mocked_combo_box, True)
3483+
3484+ # THEN: The combo_box should be cleared, and new items added. Finally the previously selected item should be
3485+ # reselected
3486+ mocked_combo_box.clear.assert_called_once_with()
3487+ self.assertEqual(mocked_combo_box.addItem.call_args_list,
3488+ [call('10', 10), call('11', 11), call('12', 12), call('13', 13)])
3489+ mocked_combo_box.setCurrentIndex.assert_called_once_with(2)
3490+
3491+ def test_adjust_combo_box_restore_not_found(self):
3492+ """
3493+ Test adjust_combo_box when being used with out the restore function when the selected item is not available
3494+ after the combobox has been updated
3495+ """
3496+ # GIVEN: An instance of :class:`MediaManagerItem`, with the 2nd item '12' selected
3497+ mocked_combo_box = MagicMock(**{'currentData.return_value': 9, 'findData.return_value': -1})
3498+
3499+ # WHEN: Calling adjust_combo_box with the kwarg `restore` set to True
3500+ self.media_item.adjust_combo_box(10, 13, mocked_combo_box, True)
3501+
3502+ # THEN: The combo_box should be cleared, and new items added. Finally the first item should be selected
3503+ mocked_combo_box.clear.assert_called_once_with()
3504+ self.assertEqual(mocked_combo_box.addItem.call_args_list,
3505+ [call('10', 10), call('11', 11), call('12', 12), call('13', 13)])
3506+ mocked_combo_box.setCurrentIndex.assert_called_once_with(0)
3507+
3508+ def test_on_search_button_no_bible(self):
3509+ """
3510+ Test on_search_button_clicked when there is no bible selected
3511+ """
3512+ # GIVEN: An instance of :class:`MediaManagerItem`
3513+ # WHEN calling on_search_button_clicked and there is no selected bible
3514+ self.media_item.bible = None
3515+ self.media_item.on_search_button_clicked()
3516+
3517+ # THEN: The user should be informed that there are no bibles selected
3518+ self.assertEqual(self.mocked_main_window.information_message.call_count, 1)
3519+
3520+ def test_on_search_button_search_tab(self):
3521+ """
3522+ Test on_search_button_clicked when the `Search` tab is selected
3523+ """
3524+ # GIVEN: An instance of :class:`MediaManagerItem`, and a mocked text_search method
3525+ self.media_item.bible = self.mocked_bible_1
3526+ self.media_item.search_button = MagicMock()
3527+ self.media_item.search_tab = MagicMock(**{'isVisible.return_value': True})
3528+ with patch.object(self.media_item, 'text_search') as mocked_text_search:
3529+
3530+ # WHEN: Calling on_search_button_clicked and the 'Search' tab is selected
3531+ self.media_item.on_search_button_clicked()
3532+
3533+ # THEN: The text_search method should have been called
3534+ mocked_text_search.assert_called_once_with()
3535+
3536+ def test_on_search_button_select_tab(self):
3537+ """
3538+ Test on_search_button_clicked when the `Select` tab is selected
3539+ """
3540+ # GIVEN: An instance of :class:`MediaManagerItem`, and a mocked select_search method
3541+ self.media_item.bible = self.mocked_bible_1
3542+ self.media_item.search_button = MagicMock()
3543+ self.media_item.search_tab = MagicMock(**{'isVisible.return_value': False})
3544+ self.media_item.select_tab = MagicMock(**{'isVisible.return_value': True})
3545+ with patch.object(self.media_item, 'select_search') as mocked_select_search:
3546+
3547+ # WHEN: Calling on_search_button_clicked and the 'Select' tab is selected
3548+ self.media_item.on_search_button_clicked()
3549+
3550+ # THEN: The text_search method should have been called
3551+ mocked_select_search.assert_called_once_with()
3552+
3553+ def test_select_search_single_bible(self):
3554+ """
3555+ Test select_search when only one bible is selected
3556+ """
3557+ # GIVEN: An instance of :class:`MediaManagerItem` and mocked plugin.manager.get_verses
3558+ self.media_item.select_book_combo_box = MagicMock()
3559+ self.media_item.from_chapter = MagicMock()
3560+ self.media_item.from_verse = MagicMock()
3561+ self.media_item.to_chapter = MagicMock()
3562+ self.media_item.to_verse = MagicMock()
3563+ with patch.object(self.media_item, 'display_results') as mocked_display_results:
3564+
3565+ # WHEN: Calling select_search and there is only one bible selected
3566+ self.media_item.bible = self.mocked_bible_1
3567+ self.media_item.second_bible = None
3568+ self.media_item.select_search()
3569+
3570+ # THEN: reference_search should only be called once
3571+ self.assertEqual(self.mocked_plugin.manager.get_verses.call_count, 1)
3572+ mocked_display_results.assert_called_once_with()
3573+
3574+ def test_select_search_dual_bibles(self):
3575+ """
3576+ Test select_search when two bibles are selected
3577+ """
3578+ # GIVEN: An instance of :class:`MediaManagerItem` and mocked_reference_search
3579+ self.media_item.select_book_combo_box = MagicMock()
3580+ self.media_item.from_chapter = MagicMock()
3581+ self.media_item.from_verse = MagicMock()
3582+ self.media_item.to_chapter = MagicMock()
3583+ self.media_item.to_verse = MagicMock()
3584+ with patch.object(self.media_item, 'display_results') as mocked_display_results:
3585+
3586+ # WHEN: Calling select_search and there are two bibles selected
3587+ self.media_item.bible = self.mocked_bible_1
3588+ self.media_item.second_bible = self.mocked_bible_2
3589+ self.media_item.select_search()
3590+
3591+ # THEN: reference_search should be called twice
3592+ self.assertEqual(self.mocked_plugin.manager.get_verses.call_count, 2)
3593+ mocked_display_results.assert_called_once_with()
3594+
3595+ def test_text_reference_search_single_bible(self):
3596+ """
3597+ Test text_reference_search when only one bible is selected
3598+ """
3599+ # GIVEN: An instance of :class:`MediaManagerItem` and mocked plugin.manager.get_verses
3600+ with patch.object(self.media_item, 'display_results') as mocked_display_results:
3601+
3602+ # WHEN: Calling text_reference_search with only one bible selected
3603+ self.media_item.bible = self.mocked_bible_1
3604+ self.media_item.second_bible = None
3605+ self.media_item.text_reference_search('Search Text')
3606+
3607+ # THEN: reference_search should only be called once
3608+ self.assertEqual(self.mocked_plugin.manager.get_verses.call_count, 1)
3609+ mocked_display_results.assert_called_once_with()
3610+
3611+ def text_reference_search(self, search_text, search_while_type=False):
3612+ """
3613+ We are doing a 'Reference Search'.
3614+ This search is called on def text_search by Reference and Combined Searches.
3615+ """
3616+ verse_refs = self.plugin.manager.parse_ref(self.bible.name, search_text)
3617+ self.search_results = self.reference_search(verse_refs, self.bible)
3618+ if self.second_bible and self.search_results:
3619+ self.second_search_results = self.reference_search(verse_refs, self.second_bible)
3620+ self.display_results()
3621+
3622+ def test_text_reference_search_dual_bible_no_results(self):
3623+ """
3624+ Test text_reference_search when two bible are selected, but the search of the first bible does not return any
3625+ results
3626+ """
3627+ # GIVEN: An instance of :class:`MediaManagerItem` and mocked plugin.manager.get_verses
3628+ # WHEN: Calling text_reference_search with two bibles selected, but no results are found in the first bible
3629+ with patch.object(self.media_item, 'display_results') as mocked_display_results:
3630+ self.mocked_plugin.manager.get_verses.return_value = []
3631+ self.media_item.bible = self.mocked_bible_1
3632+ self.media_item.second_bible = self.mocked_bible_2
3633+ self.media_item.text_reference_search('Search Text')
3634+
3635+ # THEN: reference_search should only be called once
3636+ self.assertEqual(self.mocked_plugin.manager.get_verses.call_count, 1)
3637+ mocked_display_results.assert_called_once_with()
3638+
3639+ def test_text_reference_search_dual_bible(self):
3640+ """
3641+ Test text_reference_search when two bible are selected
3642+ """
3643+ # GIVEN: An instance of :class:`MediaManagerItem` and mocked plugin.manager.get_verses
3644+ with patch.object(self.media_item, 'display_results') as mocked_display_results:
3645+ self.media_item.bible = self.mocked_bible_1
3646+ self.media_item.second_bible = self.mocked_bible_2
3647+
3648+ # WHEN: Calling text_reference_search with two bibles selected
3649+ self.media_item.text_reference_search('Search Text')
3650+
3651+ # THEN: reference_search should be called twice
3652+ self.assertEqual(self.mocked_plugin.manager.get_verses.call_count, 2)
3653+ mocked_display_results.assert_called_once_with()
3654+
3655+ def test_on_text_search_single_bible(self):
3656+ """
3657+ Test on_text_search when only one bible is selected
3658+ """
3659+ # GIVEN: An instance of :class:`MediaManagerItem`
3660+ self.media_item.bible = self.mocked_bible_1
3661+ self.media_item.second_bible = None
3662+
3663+ # WHEN: Calling on_text_search and plugin.manager.verse_search returns a list of results
3664+ self.mocked_plugin.manager.verse_search.return_value = ['results', 'list']
3665+ with patch.object(self.media_item, 'display_results') as mocked_display_results:
3666+ self.media_item.on_text_search('Search Text')
3667+
3668+ # THEN: The search results should be the same as those returned by plugin.manager.verse_search
3669+ self.assertEqual(self.media_item.search_results, ['results', 'list'])
3670+ mocked_display_results.assert_called_once_with()
3671+
3672+ def test_on_text_search_no_results(self):
3673+ """
3674+ Test on_text_search when the search of the first bible does not return any results
3675+ """
3676+ # GIVEN: An instance of :class:`MediaManagerItem`
3677+ self.media_item.bible = self.mocked_bible_1
3678+ self.media_item.second_bible = self.mocked_bible_2
3679+
3680+ # WHEN: Calling on_text_search and plugin.manager.verse_search returns an empty list
3681+ self.mocked_plugin.manager.verse_search.return_value = []
3682+ with patch.object(self.media_item, 'display_results') as mocked_display_results:
3683+ self.media_item.on_text_search('Search Text')
3684+
3685+ # THEN: The search results should be an empty list
3686+ self.assertEqual(self.media_item.search_results, [])
3687+ mocked_display_results.assert_called_once_with()
3688+
3689+ def test_on_text_search_all_results_in_both_books(self):
3690+ """
3691+ Test on_text_search when all of the results from the first bible are found in the second
3692+ """
3693+ # GIVEN: An instance of :class:`MediaManagerItem` and some test data
3694+ mocked_verse_1 = MagicMock(**{'book.book_reference_id': 1, 'chapter': 2, 'verse': 3})
3695+ mocked_verse_1a = MagicMock(**{'book.book_reference_id': 1, 'chapter': 2, 'verse': 3})
3696+ mocked_verse_2 = MagicMock(**{'book.book_reference_id': 4, 'chapter': 5, 'verse': 6})
3697+ mocked_verse_2a = MagicMock(**{'book.book_reference_id': 4, 'chapter': 5, 'verse': 6})
3698+ self.media_item.bible = self.mocked_bible_1
3699+ self.media_item.second_bible = self.mocked_bible_2
3700+ self.media_item.second_search_results = []
3701+
3702+ # WHEN: Calling on_text_search and plugin.manager.verse_search returns a list of search results
3703+ self.mocked_plugin.manager.verse_search.return_value = [mocked_verse_1, mocked_verse_2]
3704+ self.media_item.second_bible.get_verses.side_effect = [[mocked_verse_1a], [mocked_verse_2a]]
3705+ with patch.object(self.media_item, 'display_results') as mocked_display_results:
3706+ self.media_item.on_text_search('Search Text')
3707+
3708+ # THEN: The search results for both bibles should be returned
3709+ self.assertEqual(self.media_item.search_results, [mocked_verse_1, mocked_verse_2])
3710+ self.assertEqual(self.media_item.second_search_results, [mocked_verse_1a, mocked_verse_2a])
3711+ self.assertFalse(self.mocked_log.debug.called)
3712+ self.assertFalse(self.mocked_main_window.information_message.called)
3713+ mocked_display_results.assert_called_once_with()
3714+
3715+ def test_on_text_search_not_all_results_in_both_books(self):
3716+ """
3717+ Test on_text_search when not all of the results from the first bible are found in the second
3718+ """
3719+ # GIVEN: An instance of :class:`MediaManagerItem` and some test data
3720+ mocked_verse_1 = MagicMock(**{'book.book_reference_id': 1, 'chapter': 2, 'verse': 3})
3721+ mocked_verse_1a = MagicMock(**{'book.book_reference_id': 1, 'chapter': 2, 'verse': 3})
3722+ mocked_verse_2 = MagicMock(**{'book.book_reference_id': 4, 'chapter': 5, 'verse': 6})
3723+ mocked_verse_3 = MagicMock(**{'book.book_reference_id': 7, 'chapter': 8, 'verse': 9})
3724+ self.media_item.bible = self.mocked_bible_1
3725+ self.media_item.second_bible = self.mocked_bible_2
3726+ self.media_item.second_search_results = []
3727+
3728+ # WHEN: Calling on_text_search and not all results are found in the second bible
3729+ self.mocked_plugin.manager.verse_search.return_value = [mocked_verse_1, mocked_verse_2, mocked_verse_3]
3730+ self.media_item.second_bible.get_verses.side_effect = [[mocked_verse_1a], [], []]
3731+ with patch.object(self.media_item, 'display_results') as mocked_display_results:
3732+ self.media_item.on_text_search('Search Text')
3733+
3734+ # THEN: The search results included in both bibles should be returned and the user should be notified of
3735+ # the missing verses
3736+ self.assertEqual(self.media_item.search_results, [mocked_verse_1])
3737+ self.assertEqual(self.media_item.second_search_results, [mocked_verse_1a])
3738+ self.assertEqual(self.mocked_log.debug.call_count, 2)
3739+ self.assertTrue(self.mocked_main_window.information_message.called)
3740+ mocked_display_results.assert_called_once_with()
3741+
3742+ # TODO: Test text_search
3743+
3744+ def test_on_search_edit_text_changed_search_while_typing_disabled(self):
3745+ """
3746+ Test on_search_edit_text_changed when 'search while typing' is disabled
3747+ """
3748+ # GIVEN: An instance of BibleMediaItem and mocked Settings which returns False when the value
3749+ # 'bibles/is search while typing enabled' is requested
3750+ self.setting_values = {'bibles/is search while typing enabled': False}
3751+ self.mocked_qtimer.isActive.return_value = False
3752+
3753+ # WHEN: Calling on_search_edit_text_changed
3754+ self.media_item.on_search_edit_text_changed()
3755+
3756+ # THEN: The method should not have checked if the timer is active
3757+ self.assertFalse(self.media_item.search_timer.isActive.called)
3758+
3759+ def test_on_search_edit_text_changed_search_while_typing_enabled(self):
3760+ """
3761+ Test on_search_edit_text_changed when 'search while typing' is enabled
3762+ """
3763+ # GIVEN: An instance of BibleMediaItem and mocked Settings which returns True when the value
3764+ # 'bibles/is search while typing enabled' is requested
3765+ self.setting_values = {'bibles/is search while typing enabled': True}
3766+ self.media_item.search_timer.isActive.return_value = False
3767+
3768+ # WHEN: Calling on_search_edit_text_changed
3769+ self.media_item.on_search_edit_text_changed()
3770+
3771+ # THEN: The method should start the search_timer
3772+ self.media_item.search_timer.isActive.assert_called_once_with()
3773+ self.media_item.search_timer.start.assert_called_once_with()
3774+
3775+ def test_on_search_timer_timeout(self):
3776+ """
3777+ Test on_search_timer_timeout
3778+ """
3779+ # GIVEN: An instance of BibleMediaItem
3780+ with patch.object(self.media_item, 'text_search') as mocked_text_search:
3781+
3782+ # WHEN: Calling on_search_timer_timeout
3783+ self.media_item.on_search_timer_timeout()
3784+
3785+ # THEN: The text_search method should have been called with True
3786+ mocked_text_search.assert_called_once_with(True)
3787+
3788+ def test_display_results_no_results(self):
3789+ """
3790+ Test the display_results method when there are no items to display
3791+ """
3792+ # GIVEN: An instance of BibleMediaItem and a mocked build_display_results which returns an empty list
3793+ self.media_item.list_view = MagicMock()
3794+ self.media_item.bible = self.mocked_bible_1
3795+ self.media_item.second_bible = self.mocked_bible_2
3796+ self.media_item.search_results = []
3797+
3798+ with patch.object(self.media_item, 'build_display_results', return_value=[]):
3799+
3800+ # WHEN: Calling display_results with True
3801+ self.media_item.display_results()
3802+
3803+ # THEN: No items should be added to the list
3804+ self.media_item.list_view.clear.assert_called_once_with()
3805+ self.assertFalse(self.media_item.list_view.addItem.called)
3806+
3807+ def test_display_results_results(self):
3808+ """
3809+ Test the display_results method when there are items to display
3810+ """
3811+ # GIVEN: An instance of BibleMediaItem and a mocked build_display_results which returns a list of results
3812+ with patch.object(self.media_item, 'build_display_results', return_value=['list', 'items']):
3813+ self.media_item.search_results = ['results']
3814+ self.media_item.list_view = MagicMock()
3815+
3816+ # WHEN: Calling display_results
3817+ self.media_item.display_results()
3818+
3819+ # THEN: addItem should have been with the display items
3820+ self.media_item.list_view.clear.assert_called_once_with()
3821+ self.assertEqual(self.media_item.list_view.addItem.call_args_list, [call('list'), call('items')])
3822
3823=== modified file 'tests/resources/themes/Moss_on_tree.otz' (properties changed: +x to -x)