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: 3822 lines (+2178/-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 (+592/-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
Azaziah (community) Needs Fixing
Tim Bentley Needs Fixing
Review via email: mp+313506@code.launchpad.net

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 :

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 :

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
2728. By Phill

Fixes

2729. By Phill

HEAD

2730. By Phill

Fixed results not showing when using the search button as apposed to search as type!

2731. By Phill

Dont try to display search results if there are none!

2732. By Phill

Added tool tip

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