Merged in trunk

Adds more flexibility to 'locking' bible verses.
fixes #1625681 "If Bible search results are locked, duplicated results are listed"


lp:~phill-ridout/openlp/saved_bible_verses (revision 2746)
[SUCCESS] https://ci.openlp.io/job/Branch-01-Pull/2030/
[SUCCESS] https://ci.openlp.io/job/Branch-02-Functional-Tests/1940/
[SUCCESS] https://ci.openlp.io/job/Branch-03-Interface-Tests/1871/
[SUCCESS] https://ci.openlp.io/job/Branch-04a-Code_Analysis/1251/
[SUCCESS] https://ci.openlp.io...

bzr-revno: 2743
This commit is contained in:
Phill 2017-05-30 22:45:16 +02:00 committed by Tomas Groth
commit 392de6bcde
16 changed files with 317 additions and 195 deletions

14
openlp/core/ui/lib/listwidgetwithdnd.py Normal file → Executable file
View File

@ -44,7 +44,6 @@ class ListWidgetWithDnD(QtWidgets.QListWidget):
self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
self.setAlternatingRowColors(True)
self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
self.locked = False
def activateDnD(self):
"""
@ -54,15 +53,13 @@ class ListWidgetWithDnD(QtWidgets.QListWidget):
self.setDragDropMode(QtWidgets.QAbstractItemView.DragDrop)
Registry().register_function(('%s_dnd' % self.mime_data_text), self.parent().load_file)
def clear(self, search_while_typing=False, override_lock=False):
def clear(self, search_while_typing=False):
"""
Re-implement clear, so that we can customise feedback when using 'Search as you type'
:param search_while_typing: True if we want to display the customised message
:return: None
"""
if self.locked and not override_lock:
return
if search_while_typing:
self.no_results_text = UiStrings().ShortResults
else:
@ -128,6 +125,15 @@ class ListWidgetWithDnD(QtWidgets.QListWidget):
else:
event.ignore()
def allItems(self):
"""
An generator to list all the items in the widget
:return: a generator
"""
for row in range(self.count()):
yield self.item(row)
def paintEvent(self, event):
"""
Re-implement paintEvent so that we can add 'No Results' text when the listWidget is empty.

View File

@ -46,16 +46,16 @@ class PathEdit(QtWidgets.QWidget):
:param parent: The parent of the widget. This is just passed to the super method.
:type parent: QWidget or None
:param dialog_caption: Used to customise the caption in the QFileDialog.
:param dialog_caption: str
:param default_path: The default path. This is set as the path when the revert button is clicked
:type default_path: str
:param show_revert: Used to determin if the 'revert button' should be visible.
:type show_revert: bool
:return: None
:rtype: None
"""
@ -72,7 +72,7 @@ class PathEdit(QtWidgets.QWidget):
Set up the widget
:param show_revert: Show or hide the revert button
:type show_revert: bool
:return: None
:rtype: None
"""

View File

@ -341,10 +341,10 @@ def parse_reference(reference, bible, language_selection, book_ref_id=False):
if not book_ref_id:
book_ref_id = bible.get_book_ref_id_by_localised_name(book, language_selection)
elif not bible.get_book_by_book_ref_id(book_ref_id):
return False
return []
# We have not found the book so do not continue
if not book_ref_id:
return False
return []
ranges = match.group('ranges')
range_list = get_reference_match('range_separator').split(ranges)
ref_list = []
@ -403,7 +403,7 @@ def parse_reference(reference, bible, language_selection, book_ref_id=False):
return ref_list
else:
log.debug('Invalid reference: {text}'.format(text=reference))
return None
return []
class SearchResults(object):

View File

@ -158,6 +158,7 @@ class BibleDB(Manager):
self.get_name()
if 'path' in kwargs:
self.path = kwargs['path']
self._is_web_bible = None
def get_name(self):
"""
@ -426,6 +427,18 @@ class BibleDB(Manager):
return 0
return count
@property
def is_web_bible(self):
"""
A read only property indicating if the bible is a 'web bible'
:return: If the bible is a web bible.
:rtype: bool
"""
if self._is_web_bible is None:
self._is_web_bible = bool(self.get_object(BibleMeta, 'download_source'))
return self._is_web_bible
def dump_bible(self):
"""
Utility debugging method to dump the contents of a bible.

View File

@ -142,8 +142,8 @@ class BibleManager(OpenLPMixin, RegistryProperties):
log.debug('Bible Name: "{name}"'.format(name=name))
self.db_cache[name] = bible
# Look to see if lazy load bible exists and get create getter.
source = self.db_cache[name].get_object(BibleMeta, 'download_source')
if source:
if self.db_cache[name].is_web_bible:
source = self.db_cache[name].get_object(BibleMeta, 'download_source')
download_name = self.db_cache[name].get_object(BibleMeta, 'download_name').value
meta_proxy = self.db_cache[name].get_object(BibleMeta, 'proxy_server')
web_bible = HTTPBible(self.parent, path=self.path, file=filename, download_source=source.value,
@ -278,7 +278,7 @@ class BibleManager(OpenLPMixin, RegistryProperties):
:param show_error:
"""
if not bible or not ref_list:
return None
return []
return self.db_cache[bible].get_verses(ref_list, show_error)
def get_language_selection(self, bible):
@ -305,11 +305,17 @@ class BibleManager(OpenLPMixin, RegistryProperties):
"""
Does a verse search for the given bible and text.
:param bible: The bible to search in (unicode).
:param second_bible: The second bible (unicode). We do not search in this bible.
:param text: The text to search for (unicode).
:param bible: The bible to search
:type bible: str
:param text: The text to search for
:type text: str
:return: The search results if valid, or None if the search is invalid.
:rtype: None, list
"""
log.debug('BibleManager.verse_search("{bible}", "{text}")'.format(bible=bible, text=text))
if not text:
return None
# If no bibles are installed, message is given.
if not bible:
self.main_window.information_message(
@ -317,8 +323,7 @@ class BibleManager(OpenLPMixin, RegistryProperties):
UiStrings().BibleNoBibles)
return None
# Check if the bible or second_bible is a web bible.
web_bible = self.db_cache[bible].get_object(BibleMeta, 'download_source')
if web_bible:
if self.db_cache[bible].is_web_bible:
# If either Bible is Web, cursor is reset to normal and message is given.
self.application.set_normal_cursor()
self.main_window.information_message(
@ -328,41 +333,8 @@ class BibleManager(OpenLPMixin, RegistryProperties):
'This means that the currently selected Bible is a Web Bible.')
)
return None
# Shorter than 3 char searches break OpenLP with very long search times, thus they are blocked.
if len(text) - text.count(' ') < 3:
return None
# Fetch the results from db. If no results are found, return None, no message is given for this.
elif text:
return self.db_cache[bible].verse_search(text)
else:
return None
def verse_search_while_typing(self, bible, second_bible, text):
"""
Does a verse search for the given bible and text.
This is used during "Search while typing"
It's the same thing as the normal text search, but it does not show the web Bible error.
(It would result in the error popping every time a char is entered or removed)
It also does not have a minimum text len, this is set in mediaitem.py
:param bible: The bible to search in (unicode).
:param second_bible: The second bible (unicode). We do not search in this bible.
:param text: The text to search for (unicode).
"""
# If no bibles are installed, message is given.
if not bible:
return None
# Check if the bible or second_bible is a web bible.
web_bible = self.db_cache[bible].get_object(BibleMeta, 'download_source')
second_web_bible = ''
if second_bible:
second_web_bible = self.db_cache[second_bible].get_object(BibleMeta, 'download_source')
if web_bible or second_web_bible:
# If either Bible is Web, cursor is reset to normal and search ends w/o any message.
self.application.set_normal_cursor()
return None
# Fetch the results from db. If no results are found, return None, no message is given for this.
elif text:
if text:
return self.db_cache[bible].verse_search(text)
else:
return None

241
openlp/plugins/bibles/lib/mediaitem.py Normal file → Executable file
View File

@ -22,6 +22,7 @@
import logging
import re
from enum import IntEnum, unique
from PyQt5 import QtCore, QtWidgets
@ -48,15 +49,45 @@ def get_reference_separators():
'list': get_reference_separator('sep_l_display')}
class BibleSearch(object):
@unique
class BibleSearch(IntEnum):
"""
Enumeration class for the different search methods for the "Search" tab.
Enumeration class for the different search types for the "Search" tab.
"""
Reference = 1
Text = 2
Combined = 3
@unique
class ResultsTab(IntEnum):
"""
Enumeration class for the different tabs for the results list.
"""
Saved = 0
Search = 1
@unique
class SearchStatus(IntEnum):
"""
Enumeration class for the different search methods.
"""
SearchButton = 0
SearchAsYouType = 1
NotEnoughText = 2
@unique
class SearchTabs(IntEnum):
"""
Enumeration class for the tabs on the media item.
"""
Search = 0
Select = 1
Options = 2
class BibleMediaItem(MediaManagerItem):
"""
This is the custom media manager item for Bibles.
@ -73,11 +104,13 @@ class BibleMediaItem(MediaManagerItem):
:param kwargs: Keyword arguments to pass to the super method. (dict)
"""
self.clear_icon = build_icon(':/bibles/bibles_search_clear.png')
self.lock_icon = build_icon(':/bibles/bibles_search_lock.png')
self.unlock_icon = build_icon(':/bibles/bibles_search_unlock.png')
self.save_results_icon = build_icon(':/bibles/bibles_save_results.png')
self.sort_icon = build_icon(':/bibles/bibles_book_sort.png')
self.bible = None
self.second_bible = None
self.saved_results = []
self.current_results = []
self.search_status = SearchStatus.SearchButton
# TODO: Make more central and clean up after!
self.search_timer = QtCore.QTimer()
self.search_timer.setInterval(200)
@ -162,8 +195,10 @@ class BibleMediaItem(MediaManagerItem):
self.select_tab.setVisible(False)
self.page_layout.addWidget(self.select_tab)
# General Search Opions
self.options_widget = QtWidgets.QGroupBox(translate('BiblesPlugin.MediaItem', 'Options'), self)
self.general_bible_layout = QtWidgets.QFormLayout(self.options_widget)
self.options_tab = QtWidgets.QWidget()
self.options_tab.setSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum)
self.search_tab_bar.addTab(translate('BiblesPlugin.MediaItem', 'Options'))
self.general_bible_layout = QtWidgets.QFormLayout(self.options_tab)
self.version_combo_box = create_horizontal_adjusting_combo_box(self, 'version_combo_box')
self.general_bible_layout.addRow('{version}:'.format(version=UiStrings().Version), self.version_combo_box)
self.second_combo_box = create_horizontal_adjusting_combo_box(self, 'second_combo_box')
@ -171,20 +206,28 @@ class BibleMediaItem(MediaManagerItem):
self.style_combo_box = create_horizontal_adjusting_combo_box(self, 'style_combo_box')
self.style_combo_box.addItems(['', '', ''])
self.general_bible_layout.addRow(UiStrings().LayoutStyle, self.style_combo_box)
self.search_button_layout = QtWidgets.QHBoxLayout()
self.options_tab.setVisible(False)
self.page_layout.addWidget(self.options_tab)
# This widget is the easier way to reset the spacing of search_button_layout. (Because page_layout has had its
# spacing set to 0)
self.search_button_widget = QtWidgets.QWidget()
self.search_button_layout = QtWidgets.QHBoxLayout(self.search_button_widget)
self.search_button_layout.addStretch()
# Note: If we use QPushButton instead of the QToolButton, the icon will be larger than the Lock icon.
self.clear_button = QtWidgets.QToolButton(self)
self.clear_button = QtWidgets.QPushButton()
self.clear_button.setIcon(self.clear_icon)
self.lock_button = QtWidgets.QToolButton(self)
self.lock_button.setIcon(self.unlock_icon)
self.lock_button.setCheckable(True)
self.save_results_button = QtWidgets.QPushButton()
self.save_results_button.setIcon(self.save_results_icon)
self.search_button_layout.addWidget(self.clear_button)
self.search_button_layout.addWidget(self.lock_button)
self.search_button_layout.addWidget(self.save_results_button)
self.search_button = QtWidgets.QPushButton(self)
self.search_button_layout.addWidget(self.search_button)
self.general_bible_layout.addRow(self.search_button_layout)
self.page_layout.addWidget(self.options_widget)
self.page_layout.addWidget(self.search_button_widget)
self.results_view_tab = QtWidgets.QTabBar(self)
self.results_view_tab.addTab('')
self.results_view_tab.addTab('')
self.results_view_tab.setCurrentIndex(ResultsTab.Search)
self.page_layout.addWidget(self.results_view_tab)
def setupUi(self):
super().setupUi()
@ -211,12 +254,15 @@ class BibleMediaItem(MediaManagerItem):
# Buttons
self.book_order_button.toggled.connect(self.on_book_order_button_toggled)
self.clear_button.clicked.connect(self.on_clear_button_clicked)
self.lock_button.toggled.connect(self.on_lock_button_toggled)
self.save_results_button.clicked.connect(self.on_save_results_button_clicked)
self.search_button.clicked.connect(self.on_search_button_clicked)
# Other stuff
self.search_edit.returnPressed.connect(self.on_search_button_clicked)
self.search_tab_bar.currentChanged.connect(self.on_search_tab_bar_current_changed)
self.results_view_tab.currentChanged.connect(self.on_results_view_tab_current_changed)
self.search_edit.textChanged.connect(self.on_search_edit_text_changed)
self.on_results_view_tab_total_update(ResultsTab.Saved)
self.on_results_view_tab_total_update(ResultsTab.Search)
def retranslateUi(self):
log.debug('retranslateUi')
@ -225,9 +271,9 @@ class BibleMediaItem(MediaManagerItem):
self.style_combo_box.setItemText(LayoutStyle.VersePerSlide, UiStrings().VersePerSlide)
self.style_combo_box.setItemText(LayoutStyle.VersePerLine, UiStrings().VersePerLine)
self.style_combo_box.setItemText(LayoutStyle.Continuous, UiStrings().Continuous)
self.clear_button.setToolTip(translate('BiblesPlugin.MediaItem', 'Clear the search results.'))
self.lock_button.setToolTip(
translate('BiblesPlugin.MediaItem', 'Toggle to keep or clear the previous results.'))
self.clear_button.setToolTip(translate('BiblesPlugin.MediaItem', 'Clear the results on the current tab.'))
self.save_results_button.setToolTip(
translate('BiblesPlugin.MediaItem', 'Add the search results to the saved list.'))
self.search_button.setText(UiStrings().Search)
def on_focus(self):
@ -241,8 +287,10 @@ class BibleMediaItem(MediaManagerItem):
if self.search_tab.isVisible():
self.search_edit.setFocus()
self.search_edit.selectAll()
else:
if self.select_tab.isVisible():
self.select_book_combo_box.setFocus()
if self.options_tab.isVisible():
self.version_combo_box.setFocus()
def config_update(self):
"""
@ -415,14 +463,48 @@ class BibleMediaItem(MediaManagerItem):
"""
Show the selected tab and set focus to it
:param index: The tab selected (int)
:param index: The tab selected
:type index: int
:return: None
"""
search_tab = index == 0
self.search_tab.setVisible(search_tab)
self.select_tab.setVisible(not search_tab)
if index == SearchTabs.Search or index == SearchTabs.Select:
self.search_button.setEnabled(True)
else:
self.search_button.setEnabled(False)
self.search_tab.setVisible(index == SearchTabs.Search)
self.select_tab.setVisible(index == SearchTabs.Select)
self.options_tab.setVisible(index == SearchTabs.Options)
self.on_focus()
def on_results_view_tab_current_changed(self, index):
"""
Update list_widget with the contents of the selected list
:param index: The index of the tab that has been changed to. (int)
:return: None
"""
if index == ResultsTab.Saved:
self.add_built_results_to_list_widget(self.saved_results)
elif index == ResultsTab.Search:
self.add_built_results_to_list_widget(self.current_results)
def on_results_view_tab_total_update(self, index):
"""
Update the result total count on the tab with the given index.
:param index: Index of the tab to update (int)
:return: None
"""
string = ''
count = 0
if index == ResultsTab.Saved:
string = translate('BiblesPlugin.MediaItem', 'Saved ({result_count})')
count = len(self.saved_results)
elif index == ResultsTab.Search:
string = translate('BiblesPlugin.MediaItem', 'Results ({result_count})')
count = len(self.current_results)
self.results_view_tab.setTabText(index, string.format(result_count=count))
def on_book_order_button_toggled(self, checked):
"""
Change the sort order of the book names
@ -442,22 +524,25 @@ class BibleMediaItem(MediaManagerItem):
:return: None
"""
self.list_view.clear()
self.search_edit.clear()
self.on_focus()
current_index = self.results_view_tab.currentIndex()
for item in self.list_view.selectedItems():
self.list_view.takeItem(self.list_view.row(item))
results = [item.data(QtCore.Qt.UserRole) for item in self.list_view.allItems()]
if current_index == ResultsTab.Saved:
self.saved_results = results
elif current_index == ResultsTab.Search:
self.current_results = results
self.on_results_view_tab_total_update(current_index)
def on_lock_button_toggled(self, checked):
def on_save_results_button_clicked(self):
"""
Toggle the lock button, if Search tab is used, set focus to search field.
Add the selected verses to the saved_results list.
:param checked: The state of the toggle button. (bool)
:return: None
"""
self.list_view.locked = checked
if checked:
self.sender().setIcon(self.lock_icon)
else:
self.sender().setIcon(self.unlock_icon)
for verse in self.list_view.selectedItems():
self.saved_results.append(verse.data(QtCore.Qt.UserRole))
self.on_results_view_tab_total_update(ResultsTab.Saved)
def on_style_combo_box_index_changed(self, index):
"""
@ -490,16 +575,17 @@ class BibleMediaItem(MediaManagerItem):
:return: None
"""
new_selection = self.second_combo_box.currentData()
if self.list_view.count():
if self.saved_results:
# Exclusive or (^) the new and previous selections to detect if the user has switched between single and
# dual bible mode
if (new_selection is None) ^ (self.second_bible is None):
if critical_error_message_box(
message=translate('BiblesPlugin.MediaItem',
'OpenLP cannot combine single and dual Bible verse search results. '
'Do you want to clear your search results and start a new search?'),
'Do you want to clear your saved results?'),
parent=self, question=True) == QtWidgets.QMessageBox.Yes:
self.list_view.clear(override_lock=True)
self.saved_results = []
self.on_results_view_tab_total_update(ResultsTab.Saved)
else:
self.second_combo_box.setCurrentIndex(self.second_combo_box.findData(self.second_bible))
return
@ -525,7 +611,8 @@ class BibleMediaItem(MediaManagerItem):
log.warning('Not enough chapters in %s', book_ref_id)
critical_error_message_box(message=translate('BiblesPlugin.MediaItem', 'Bible not fully loaded.'))
else:
self.search_button.setEnabled(True)
if self.select_tab.isVisible():
self.search_button.setEnabled(True)
self.adjust_combo_box(1, self.chapter_count, self.from_chapter)
self.adjust_combo_box(1, self.chapter_count, self.to_chapter)
self.adjust_combo_box(1, verse_count, self.from_verse)
@ -602,6 +689,8 @@ class BibleMediaItem(MediaManagerItem):
:return: None
"""
self.search_timer.stop()
self.search_status = SearchStatus.SearchButton
if not self.bible:
self.main_window.information_message(UiStrings().BibleNoBiblesTitle, UiStrings().BibleNoBibles)
return
@ -613,6 +702,7 @@ class BibleMediaItem(MediaManagerItem):
elif self.select_tab.isVisible():
self.select_search()
self.search_button.setEnabled(True)
self.results_view_tab.setCurrentIndex(ResultsTab.Search)
self.application.set_normal_cursor()
def select_search(self):
@ -636,18 +726,21 @@ class BibleMediaItem(MediaManagerItem):
:return: None
"""
self.search_results = []
verse_refs = self.plugin.manager.parse_ref(self.bible.name, search_text)
self.search_results = self.plugin.manager.get_verses(self.bible.name, verse_refs, True)
if self.second_bible and self.search_results:
self.search_results = self.plugin.manager.get_verses(self.second_bible.name, verse_refs, True)
self.display_results()
def on_text_search(self, text, search_while_type=False):
def on_text_search(self, text):
"""
We are doing a 'Text Search'.
This search is called on def text_search by 'Search' Text and Combined Searches.
"""
self.search_results = self.plugin.manager.verse_search(self.bible.name, text)
if self.search_results is None:
return
if self.second_bible and self.search_results:
filtered_search_results = []
not_found_count = 0
@ -663,7 +756,7 @@ class BibleMediaItem(MediaManagerItem):
verse=verse.verse, bible_name=self.second_bible.name))
not_found_count += 1
self.search_results = filtered_search_results
if not_found_count != 0 and not search_while_type:
if not_found_count != 0 and self.search_status == SearchStatus.SearchButton:
self.main_window.information_message(
translate('BiblesPlugin.MediaItem', 'Verses not found'),
translate('BiblesPlugin.MediaItem',
@ -673,22 +766,23 @@ class BibleMediaItem(MediaManagerItem):
).format(second_name=self.second_bible.name, name=self.bible.name, count=not_found_count))
self.display_results()
def text_search(self, search_while_type=False):
def text_search(self):
"""
This triggers the proper 'Search' search based on which search type is used.
"Eg. "Reference Search", "Text Search" or "Combined search".
"""
self.search_results = []
log.debug('text_search called')
text = self.search_edit.text()
if text == '':
self.list_view.clear()
self.display_results()
return
self.list_view.clear(search_while_typing=search_while_type)
self.on_results_view_tab_total_update(ResultsTab.Search)
if self.search_edit.current_search_type() == BibleSearch.Reference:
if get_reference_match('full').match(text):
# Valid reference found. Do reference search.
self.text_reference_search(text)
elif not search_while_type:
elif self.search_status == SearchStatus.SearchButton:
self.main_window.information_message(
translate('BiblesPlugin.BibleManager', 'Scripture Reference Error'),
translate('BiblesPlugin.BibleManager',
@ -700,10 +794,12 @@ class BibleMediaItem(MediaManagerItem):
self.text_reference_search(text)
else:
# It can only be a 'Combined' search without a valid reference, or a 'Text' search
if search_while_type:
if len(text) > 8 and VALID_TEXT_SEARCH.search(text):
self.on_text_search(text, True)
elif VALID_TEXT_SEARCH.search(text):
if self.search_status == SearchStatus.SearchAsYouType:
if len(text) <= 8:
self.search_status = SearchStatus.NotEnoughText
self.display_results()
return
if VALID_TEXT_SEARCH.search(text):
self.on_text_search(text)
def on_search_edit_text_changed(self):
@ -713,9 +809,12 @@ class BibleMediaItem(MediaManagerItem):
:return: None
"""
if Settings().value('bibles/is search while typing enabled'):
if not self.search_timer.isActive():
self.search_timer.start()
if not Settings().value('bibles/is search while typing enabled') or \
not self.bible or self.bible.is_web_bible or \
(self.second_bible and self.bible.is_web_bible):
return
if not self.search_timer.isActive():
self.search_timer.start()
def on_search_timer_timeout(self):
"""
@ -724,7 +823,9 @@ class BibleMediaItem(MediaManagerItem):
:return: None
"""
self.text_search(True)
self.search_status = SearchStatus.SearchAsYouType
self.text_search()
self.results_view_tab.setCurrentIndex(ResultsTab.Search)
def display_results(self):
"""
@ -732,14 +833,16 @@ class BibleMediaItem(MediaManagerItem):
:return: None
"""
self.list_view.clear()
if self.search_results:
items = self.build_display_results(self.bible, self.second_bible, self.search_results)
for item in items:
self.list_view.addItem(item)
self.list_view.selectAll()
self.current_results = self.build_display_results(self.bible, self.second_bible, self.search_results)
self.search_results = []
self.second_search_results = []
self.add_built_results_to_list_widget(self.current_results)
def add_built_results_to_list_widget(self, results):
self.list_view.clear(self.search_status == SearchStatus.NotEnoughText)
for item in self.build_list_widget_items(results):
self.list_view.addItem(item)
self.list_view.selectAll()
self.on_results_view_tab_total_update(ResultsTab.Search)
def build_display_results(self, bible, second_bible, search_results):
"""
@ -789,11 +892,18 @@ class BibleMediaItem(MediaManagerItem):
bible_text = '{book} {chapter:d}{sep}{verse:d} ({version}, {second_version})'
else:
bible_text = '{book} {chapter:d}{sep}{verse:d} ({version})'
bible_verse = QtWidgets.QListWidgetItem(bible_text.format(sep=verse_separator, **data))
bible_verse.setData(QtCore.Qt.UserRole, data)
items.append(bible_verse)
data['item_title'] = bible_text.format(sep=verse_separator, **data)
items.append(data)
return items
def build_list_widget_items(self, items):
list_widget_items = []
for data in items:
bible_verse = QtWidgets.QListWidgetItem(data['item_title'])
bible_verse.setData(QtCore.Qt.UserRole, data)
list_widget_items.append(bible_verse)
return list_widget_items
def generate_slide_data(self, service_item, item=None, xml_version=False, remote=False,
context=ServiceItemContext.Service):
"""
@ -897,6 +1007,8 @@ class BibleMediaItem(MediaManagerItem):
"""
Search for some Bible verses (by reference).
"""
if self.bible is None:
return []
reference = self.plugin.manager.parse_ref(self.bible.name, string)
search_results = self.plugin.manager.get_verses(self.bible.name, reference, showError)
if search_results:
@ -908,6 +1020,9 @@ class BibleMediaItem(MediaManagerItem):
"""
Create a media item from an item id.
"""
if self.bible is None:
return []
reference = self.plugin.manager.parse_ref(self.bible.name, item_id)
search_results = self.plugin.manager.get_verses(self.bible.name, reference, False)
return self.build_display_results(self.bible, None, search_results)
items = self.build_display_results(self.bible, None, search_results)
return self.build_list_widget_items(items)

View File

@ -1,4 +1,4 @@
# -*- coding: utf-8 -*-
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################

View File

@ -69,7 +69,7 @@ class Ui_SongUsageDetailDialog(object):
self.file_horizontal_layout.setSpacing(8)
self.file_horizontal_layout.setContentsMargins(8, 8, 8, 8)
self.file_horizontal_layout.setObjectName('file_horizontal_layout')
self.report_path_edit = PathEdit(self.file_group_box, path_type = PathType.Directories, show_revert=False)
self.report_path_edit = PathEdit(self.file_group_box, path_type=PathType.Directories, show_revert=False)
self.file_horizontal_layout.addWidget(self.report_path_edit)
self.vertical_layout.addWidget(self.file_group_box)
self.button_box = create_button_box(song_usage_detail_dialog, 'button_box', ['cancel', 'ok'])

Binary file not shown.

After

Width:  |  Height:  |  Size: 735 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 452 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 440 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 577 B

View File

@ -34,8 +34,7 @@
<file>bibles_search_text.png</file>
<file>bibles_search_reference.png</file>
<file>bibles_search_clear.png</file>
<file>bibles_search_unlock.png</file>
<file>bibles_search_lock.png</file>
<file>bibles_save_results.png</file>
</qresource>
<qresource prefix="plugins">
<file>plugin_alerts.png</file>
@ -144,7 +143,6 @@
</qresource>
<qresource prefix="remote">
<file>network_server.png</file>
<file>network_ssl.png</file>
<file>network_auth.png</file>
</qresource>
<qresource prefix="songusage">
@ -188,4 +186,4 @@
<file>android_app_qr.png</file>
<file>ios_app_qr.png</file>
</qresource>
</RCC>
</RCC>

View File

@ -23,6 +23,7 @@
This module contains tests for the openlp.core.lib.listwidgetwithdnd module
"""
from unittest import TestCase
from types import GeneratorType
from openlp.core.common.uistrings import UiStrings
from openlp.core.ui.lib.listwidgetwithdnd import ListWidgetWithDnD
@ -33,37 +34,6 @@ class TestListWidgetWithDnD(TestCase):
"""
Test the :class:`~openlp.core.lib.listwidgetwithdnd.ListWidgetWithDnD` class
"""
def test_clear_locked(self):
"""
Test the clear method the list is 'locked'
"""
with patch('openlp.core.ui.lib.listwidgetwithdnd.QtWidgets.QListWidget.clear') as mocked_clear_super_method:
# GIVEN: An instance of ListWidgetWithDnD
widget = ListWidgetWithDnD()
# WHEN: The list is 'locked' and clear has been called
widget.locked = True
widget.clear()
# THEN: The super method should not have been called (i.e. The list not cleared)
self.assertFalse(mocked_clear_super_method.called)
def test_clear_overide_locked(self):
"""
Test the clear method the list is 'locked', but clear is called with 'override_lock' set to True
"""
with patch('openlp.core.ui.lib.listwidgetwithdnd.QtWidgets.QListWidget.clear') as mocked_clear_super_method:
# GIVEN: An instance of ListWidgetWithDnD
widget = ListWidgetWithDnD()
# WHEN: The list is 'locked' and clear has been called with override_lock se to True
widget.locked = True
widget.clear(override_lock=True)
# THEN: The super method should have been called (i.e. The list is cleared regardless whether it is locked
# or not)
mocked_clear_super_method.assert_called_once_with()
def test_clear(self):
"""
Test the clear method when called without any arguments.
@ -90,6 +60,38 @@ class TestListWidgetWithDnD(TestCase):
# THEN: The results text should be the 'short results' text.
self.assertEqual(widget.no_results_text, UiStrings().ShortResults)
def test_all_items_no_list_items(self):
"""
Test allItems when there are no items in the list widget
"""
# GIVEN: An instance of ListWidgetWithDnD
widget = ListWidgetWithDnD()
with patch.object(widget, 'count', return_value=0), \
patch.object(widget, 'item', side_effect=lambda x: [][x]):
# WHEN: Calling allItems
result = widget.allItems()
# THEN: An instance of a Generator object should be returned. The generator should not yeild any results
self.assertIsInstance(result, GeneratorType)
self.assertEqual(list(result), [])
def test_all_items_list_items(self):
"""
Test allItems when the list widget contains some items.
"""
# GIVEN: An instance of ListWidgetWithDnD
widget = ListWidgetWithDnD()
with patch.object(widget, 'count', return_value=2), \
patch.object(widget, 'item', side_effect=lambda x: [5, 3][x]):
# WHEN: Calling allItems
result = widget.allItems()
# THEN: An instance of a Generator object should be returned. The generator should not yeild any results
self.assertIsInstance(result, GeneratorType)
self.assertEqual(list(result), [5, 3])
def test_paint_event(self):
"""
Test the paintEvent method when the list is not empty

View File

@ -31,7 +31,8 @@ from tests.helpers.testmixin import TestMixin
from openlp.core.common import Registry
from openlp.core.lib import MediaManagerItem
from openlp.plugins.bibles.lib.mediaitem import BibleMediaItem, BibleSearch, get_reference_separators, VALID_TEXT_SEARCH
from openlp.plugins.bibles.lib.mediaitem import BibleMediaItem, BibleSearch, ResultsTab, SearchStatus, \
get_reference_separators, VALID_TEXT_SEARCH
class TestBibleMediaItemModulefunctions(TestCase):
@ -143,6 +144,7 @@ class TestMediaItem(TestCase, TestMixin):
self.media_item = BibleMediaItem(None, self.mocked_plugin)
self.media_item.settings_section = 'bibles'
self.media_item.results_view_tab = MagicMock()
self.mocked_book_1 = MagicMock(**{'get_name.return_value': 'Book 1', 'book_reference_id': 1})
self.mocked_book_2 = MagicMock(**{'get_name.return_value': 'Book 2', 'book_reference_id': 2})
@ -658,56 +660,65 @@ class TestMediaItem(TestCase, TestMixin):
# THEN: The select_book_combo_box model sort should have been reset
self.media_item.select_book_combo_box.model().sort.assert_called_once_with(-1)
def test_on_clear_button_clicked(self):
def test_on_clear_button_clicked_saved_tab(self):
"""
Test on_clear_button_clicked
Test on_clear_button_clicked when the saved tab is selected
"""
# GIVEN: An instance of :class:`MediaManagerItem` and mocked out saved_tab and select_tab and a mocked out
# list_view and search_edit
self.media_item.list_view = MagicMock()
self.media_item.search_edit = MagicMock()
self.media_item.results_view_tab = MagicMock(**{'currentIndex.return_value': ResultsTab.Saved})
self.media_item.saved_results = ['Some', 'Results']
with patch.object(self.media_item, 'on_focus'):
# WHEN: Calling on_clear_button_clicked
self.media_item.on_clear_button_clicked()
# THEN: The list_view should be cleared
self.assertEqual(self.media_item.saved_results, [])
self.media_item.list_view.clear.assert_called_once_with()
def test_on_clear_button_clicked_search_tab(self):
"""
Test on_clear_button_clicked when the search tab is selected
"""
# GIVEN: An instance of :class:`MediaManagerItem` and mocked out search_tab and select_tab and a mocked out
# list_view and search_edit
self.media_item.list_view = MagicMock()
self.media_item.search_edit = MagicMock()
self.media_item.results_view_tab = MagicMock(**{'currentIndex.return_value': ResultsTab.Search})
self.media_item.current_results = ['Some', 'Results']
with patch.object(self.media_item, 'on_focus'):
# WHEN: Calling on_clear_button_clicked
self.media_item.on_clear_button_clicked()
# THEN: The list_view and the search_edit should be cleared
self.assertEqual(self.media_item.current_results, [])
self.media_item.list_view.clear.assert_called_once_with()
self.media_item.search_edit.clear.assert_called_once_with()
def test_on_lock_button_toggled_search_tab_lock_icon(self):
def test_on_save_results_button_clicked(self):
"""
Test that "on_lock_button_toggled" toggles the lock properly.
Test that "on_save_results_button_clicked" saves the results.
"""
# GIVEN: An instance of :class:`MediaManagerItem` a mocked sender and list_view
self.media_item.list_view = MagicMock()
self.media_item.lock_icon = 'lock icon'
mocked_sender_instance = MagicMock()
with patch.object(self.media_item, 'sender', return_value=mocked_sender_instance):
# GIVEN: An instance of :class:`MediaManagerItem` and a mocked list_view
result_1 = MagicMock(**{'data.return_value': 'R1'})
result_2 = MagicMock(**{'data.return_value': 'R2'})
result_3 = MagicMock(**{'data.return_value': 'R3'})
self.media_item.list_view = MagicMock(**{'selectedItems.return_value': [result_1, result_2, result_3]})
# WHEN: When the lock_button is checked
self.media_item.on_lock_button_toggled(True)
with patch.object(self.media_item, 'on_results_view_tab_total_update') as \
mocked_on_results_view_tab_total_update:
# THEN: list_view should be 'locked' and the lock icon set
self.assertTrue(self.media_item.list_view.locked)
mocked_sender_instance.setIcon.assert_called_once_with('lock icon')
# WHEN: When the save_results_button is clicked
self.media_item.on_save_results_button_clicked()
def test_on_lock_button_toggled_unlock_icon(self):
"""
Test that "on_lock_button_toggled" toggles the lock properly.
"""
# GIVEN: An instance of :class:`MediaManagerItem` a mocked sender and list_view
self.media_item.list_view = MagicMock()
self.media_item.unlock_icon = 'unlock icon'
mocked_sender_instance = MagicMock()
with patch.object(self.media_item, 'sender', return_value=mocked_sender_instance):
# WHEN: When the lock_button is unchecked
self.media_item.on_lock_button_toggled(False)
# THEN: list_view should be 'unlocked' and the unlock icon set
self.assertFalse(self.media_item.list_view.locked)
mocked_sender_instance.setIcon.assert_called_once_with('unlock icon')
# THEN: The selected results in the list_view should be added to the 'saved_results' list. And the saved_tab
# total should be updated.
self.assertEqual(self.media_item.saved_results, ['R1', 'R2', 'R3'])
mocked_on_results_view_tab_total_update.assert_called_once_with(ResultsTab.Saved)
def test_on_style_combo_box_changed(self):
"""
@ -815,7 +826,9 @@ class TestMediaItem(TestCase, TestMixin):
self.media_item.list_view = MagicMock(**{'count.return_value': 5})
self.media_item.style_combo_box = MagicMock()
self.media_item.select_book_combo_box = MagicMock()
self.media_item.search_results = ['list', 'of', 'results']
with patch.object(self.media_item, 'initialise_advanced_bible') as mocked_initialise_advanced_bible, \
patch.object(self.media_item, 'display_results'), \
patch('openlp.plugins.bibles.lib.mediaitem.critical_error_message_box',
return_value=QtWidgets.QMessageBox.Yes) as mocked_critical_error_message_box:
@ -825,9 +838,8 @@ class TestMediaItem(TestCase, TestMixin):
self.media_item.second_combo_box = MagicMock(**{'currentData.return_value': self.mocked_bible_1})
self.media_item.on_second_combo_box_index_changed(5)
# THEN: The list_view should be cleared and the selected bible should be set as the current bible
# THEN: The selected bible should be set as the current bible
self.assertTrue(mocked_critical_error_message_box.called)
self.media_item.list_view.clear.assert_called_once_with(override_lock=True)
self.media_item.style_combo_box.setEnabled.assert_called_once_with(False)
self.assertTrue(mocked_initialise_advanced_bible.called)
self.assertEqual(self.media_item.second_bible, self.mocked_bible_1)
@ -841,7 +853,9 @@ class TestMediaItem(TestCase, TestMixin):
self.media_item.list_view = MagicMock(**{'count.return_value': 5})
self.media_item.style_combo_box = MagicMock()
self.media_item.select_book_combo_box = MagicMock()
self.media_item.search_results = ['list', 'of', 'results']
with patch.object(self.media_item, 'initialise_advanced_bible') as mocked_initialise_advanced_bible, \
patch.object(self.media_item, 'display_results'), \
patch('openlp.plugins.bibles.lib.mediaitem.critical_error_message_box',
return_value=QtWidgets.QMessageBox.Yes) as mocked_critical_error_message_box:
# WHEN: The previously is a bible new selection is None and the user selects yes
@ -850,9 +864,8 @@ class TestMediaItem(TestCase, TestMixin):
self.media_item.second_combo_box = MagicMock(**{'currentData.return_value': None})
self.media_item.on_second_combo_box_index_changed(0)
# THEN: The list_view should be cleared and the selected bible should be set as the current bible
# THEN: The selected bible should be set as the current bible
self.assertTrue(mocked_critical_error_message_box.called)
self.media_item.list_view.clear.assert_called_once_with(override_lock=True)
self.media_item.style_combo_box.setEnabled.assert_called_once_with(True)
self.assertFalse(mocked_initialise_advanced_bible.called)
self.assertEqual(self.media_item.second_bible, None)
@ -1388,8 +1401,9 @@ class TestMediaItem(TestCase, TestMixin):
# WHEN: Calling on_search_timer_timeout
self.media_item.on_search_timer_timeout()
# THEN: The text_search method should have been called with True
mocked_text_search.assert_called_once_with(True)
# THEN: The search_status should be set to SearchAsYouType and text_search should have been called
self.assertEqual(self.media_item.search_status, SearchStatus().SearchAsYouType)
mocked_text_search.assert_called_once_with()
def test_display_results_no_results(self):
"""
@ -1407,7 +1421,6 @@ class TestMediaItem(TestCase, TestMixin):
self.media_item.display_results()
# THEN: No items should be added to the list
self.media_item.list_view.clear.assert_called_once_with()
self.assertFalse(self.media_item.list_view.addItem.called)
def test_display_results_results(self):
@ -1415,7 +1428,10 @@ class TestMediaItem(TestCase, TestMixin):
Test the display_results method when there are items to display
"""
# GIVEN: An instance of BibleMediaItem and a mocked build_display_results which returns a list of results
with patch.object(self.media_item, 'build_display_results', return_value=['list', 'items']):
with patch.object(self.media_item, 'build_display_results', return_value=[
{'item_title': 'Title 1'}, {'item_title': 'Title 2'}]), \
patch.object(self.media_item, 'add_built_results_to_list_widget') as \
mocked_add_built_results_to_list_widget:
self.media_item.search_results = ['results']
self.media_item.list_view = MagicMock()
@ -1423,5 +1439,5 @@ class TestMediaItem(TestCase, TestMixin):
self.media_item.display_results()
# THEN: addItem should have been with the display items
self.media_item.list_view.clear.assert_called_once_with()
self.assertEqual(self.media_item.list_view.addItem.call_args_list, [call('list'), call('items')])
mocked_add_built_results_to_list_widget.assert_called_once_with(
[{'item_title': 'Title 1'}, {'item_title': 'Title 2'}])

View File

@ -108,7 +108,7 @@ class TestBibleManager(TestCase, TestMixin):
# WHEN asking to parse the bible reference
results = parse_reference('Raoul 1', self.manager.db_cache['tests'], MagicMock())
# THEN a verse array should be returned
self.assertEqual(False, results, "The bible Search should return False")
self.assertEqual([], results, "The bible Search should return an empty list")
def test_parse_reference_five(self):
"""