This branch introduces following improvements to Quick Bible search:

- Combined Reference/Text search which first performs the Reference
  search and then moves to Text search if nothing is found.
- Added Search while typing functionality for Quick Bible search
- Possibility to use “.” when shortening Book names in Reference search.
  For an example Gen. 1 = Gen 1 = Genesis 1.
- New/Improved error messages
  (E.g. added actual example verses to Reference error)
- 3 New settings for controllin...

bzr-revno: 2686
Fixes: https://launchpad.net/bugs/1591480
This commit is contained in:
suutari.olli@gmail.com 2016-08-07 19:33:46 +01:00 committed by Tim Bentley
commit fb105f2e0b
10 changed files with 507 additions and 87 deletions

View File

@ -23,6 +23,7 @@
The :mod:`uistrings` module provides standard strings for OpenLP.
"""
import logging
import itertools
from openlp.core.common import translate
@ -155,3 +156,30 @@ class UiStrings(object):
self.View = translate('OpenLP.Ui', 'View')
self.ViewMode = translate('OpenLP.Ui', 'View Mode')
self.Video = translate('OpenLP.Ui', 'Video')
self.BibleShortSearchTitle = translate('OpenLP.Ui', 'Search is Empty or too Short')
self.BibleShortSearch = translate('OpenLP.Ui', '<strong>The search you have entered is empty or shorter '
'than 3 characters long.</strong><br><br>Please try again with '
'a longer search.')
self.BibleNoBiblesTitle = translate('OpenLP.Ui', 'No Bibles Available')
self.BibleNoBibles = translate('OpenLP.Ui', '<strong>There are no Bibles currently installed.</strong><br><br>'
'Please use the Import Wizard to install one or more Bibles.')
book_chapter = translate('OpenLP.Ui', 'Book Chapter')
chapter = translate('OpenLP.Ui', 'Chapter')
verse = translate('OpenLP.Ui', 'Verse')
gap = ' | '
psalm = translate('OpenLP.Ui', 'Psalm')
may_shorten = translate('OpenLP.Ui', 'Book names may be shortened from full names, for an example Ps 23 = '
'Psalm 23')
bible_scripture_items = \
[book_chapter, gap, psalm, ' 23<br>',
book_chapter, '%(range)s', chapter, gap, psalm, ' 23%(range)s24<br>',
book_chapter, '%(verse)s', verse, '%(range)s', verse, gap, psalm, ' 23%(verse)s1%(range)s2<br>',
book_chapter, '%(verse)s', verse, '%(range)s', verse, '%(list)s', verse, '%(range)s', verse, gap, psalm,
' 23%(verse)s1%(range)s2%(list)s5%(range)s6<br>',
book_chapter, '%(verse)s', verse, '%(range)s', verse, '%(list)s', chapter, '%(verse)s', verse, '%(range)s',
verse, gap, psalm, ' 23%(verse)s1%(range)s2%(list)s24%(verse)s1%(range)s3<br>',
book_chapter, '%(verse)s', verse, '%(range)s', chapter, '%(verse)s', verse, gap, psalm,
' 23%(verse)s1%(range)s24%(verse)s1<br><br>', may_shorten]
itertools.chain.from_iterable(itertools.repeat(strings, 1) if isinstance(strings, str)
else strings for strings in bible_scripture_items)
self.BibleScriptureError = ''.join(str(joined) for joined in bible_scripture_items)

View File

@ -651,6 +651,20 @@ class MediaManagerItem(QtWidgets.QWidget, RegistryProperties):
item.setFont(font)
self.list_view.addItem(item)
def check_search_result_search_while_typing_short(self):
"""
This is used in Bible "Search while typing" if the search is shorter than the min required len.
"""
if self.list_view.count():
return
message = translate('OpenLP.MediaManagerItem', 'Search is too short to be used in: "Search while typing"')
item = QtWidgets.QListWidgetItem(message)
item.setFlags(QtCore.Qt.NoItemFlags)
font = QtGui.QFont()
font.setItalic(True)
item.setFont(font)
self.list_view.addItem(item)
def _get_id_of_item_to_generate(self, item, remote_item):
"""
Utility method to check items being submitted for slide generation.

View File

@ -41,7 +41,8 @@ __default_settings__ = {
'bibles/db password': '',
'bibles/db hostname': '',
'bibles/db database': '',
'bibles/last search type': BibleSearch.Reference,
'bibles/last search type': BibleSearch.Combined,
'bibles/reset to combined quick search': True,
'bibles/verse layout style': LayoutStyle.VersePerSlide,
'bibles/book name language': LanguageSelection.Bible,
'bibles/display brackets': DisplayStyle.NoBrackets,
@ -59,7 +60,9 @@ __default_settings__ = {
'bibles/range separator': '',
'bibles/list separator': '',
'bibles/end separator': '',
'bibles/last directory import': ''
'bibles/last directory import': '',
'bibles/hide combined quick error': False,
'bibles/is search while typing enabled': True
}

View File

@ -128,6 +128,20 @@ class BiblesTab(SettingsTab):
self.language_selection_layout.addWidget(self.language_selection_label)
self.language_selection_layout.addWidget(self.language_selection_combo_box)
self.right_layout.addWidget(self.language_selection_group_box)
self.bible_quick_settings_group_box = QtWidgets.QGroupBox(self.right_column)
self.bible_quick_settings_group_box.setObjectName('bible_quick_settings_group_box')
self.right_layout.addWidget(self.bible_quick_settings_group_box)
self.search_settings_layout = QtWidgets.QFormLayout(self.bible_quick_settings_group_box)
self.search_settings_layout.setObjectName('search_settings_layout')
self.reset_to_combined_quick_search_check_box = QtWidgets.QCheckBox(self.bible_quick_settings_group_box)
self.reset_to_combined_quick_search_check_box.setObjectName('reset_to_combined_quick_search_check_box')
self.search_settings_layout.addRow(self.reset_to_combined_quick_search_check_box)
self.hide_combined_quick_error_check_box = QtWidgets.QCheckBox(self.bible_quick_settings_group_box)
self.hide_combined_quick_error_check_box.setObjectName('hide_combined_quick_error_check_box')
self.search_settings_layout.addRow(self.hide_combined_quick_error_check_box)
self.bible_search_while_typing_check_box = QtWidgets.QCheckBox(self.bible_quick_settings_group_box)
self.bible_search_while_typing_check_box.setObjectName('bible_search_while_typing_check_box')
self.search_settings_layout.addRow(self.bible_search_while_typing_check_box)
self.left_layout.addStretch()
self.right_layout.addStretch()
# Signals and slots
@ -151,6 +165,12 @@ class BiblesTab(SettingsTab):
self.end_separator_line_edit.editingFinished.connect(self.on_end_separator_line_edit_finished)
Registry().register_function('theme_update_list', self.update_theme_list)
self.language_selection_combo_box.activated.connect(self.on_language_selection_combo_box_changed)
self.reset_to_combined_quick_search_check_box.stateChanged.connect(
self.on_reset_to_combined_quick_search_check_box_changed)
self.hide_combined_quick_error_check_box.stateChanged.connect(
self.on_hide_combined_quick_error_check_box_changed)
self.bible_search_while_typing_check_box.stateChanged.connect(
self.on_bible_search_while_typing_check_box_changed)
def retranslateUi(self):
self.verse_display_group_box.setTitle(translate('BiblesPlugin.BiblesTab', 'Verse Display'))
@ -194,6 +214,17 @@ class BiblesTab(SettingsTab):
LanguageSelection.Application, translate('BiblesPlugin.BiblesTab', 'Application Language'))
self.language_selection_combo_box.setItemText(
LanguageSelection.English, translate('BiblesPlugin.BiblesTab', 'English'))
self.bible_quick_settings_group_box.setTitle(translate('BiblesPlugin.BiblesTab', 'Quick Search Settings'))
self.reset_to_combined_quick_search_check_box.setText(translate('BiblesPlugin.BiblesTab',
'Reset search type to "Text or Scripture'
' Reference" on startup'))
self.hide_combined_quick_error_check_box.setText(translate('BiblesPlugin.BiblesTab',
'Don\'t show error if nothing is found in "Text or '
'Scripture Reference"'))
self.bible_search_while_typing_check_box.setText(translate('BiblesPlugin.BiblesTab',
'Search automatically while typing (Text search must'
' contain a\nminimum of {count} characters and a '
'space for performance reasons)').format(count='8'))
def on_bible_theme_combo_box_changed(self):
self.bible_theme = self.bible_theme_combo_box.currentText()
@ -302,6 +333,24 @@ class BiblesTab(SettingsTab):
self.end_separator_line_edit.setText(get_reference_separator('sep_e_default'))
self.end_separator_line_edit.setPalette(self.get_grey_text_palette(True))
def on_reset_to_combined_quick_search_check_box_changed(self, check_state):
"""
Event handler for the 'hide_combined_quick_error' check box
"""
self.reset_to_combined_quick_search = (check_state == QtCore.Qt.Checked)
def on_hide_combined_quick_error_check_box_changed(self, check_state):
"""
Event handler for the 'hide_combined_quick_error' check box
"""
self.hide_combined_quick_error = (check_state == QtCore.Qt.Checked)
def on_bible_search_while_typing_check_box_changed(self, check_state):
"""
Event handler for the 'hide_combined_quick_error' check box
"""
self.bible_search_while_typing = (check_state == QtCore.Qt.Checked)
def load(self):
settings = Settings()
settings.beginGroup(self.settings_section)
@ -355,6 +404,12 @@ class BiblesTab(SettingsTab):
self.end_separator_check_box.setChecked(True)
self.language_selection = settings.value('book name language')
self.language_selection_combo_box.setCurrentIndex(self.language_selection)
self.reset_to_combined_quick_search = settings.value('reset to combined quick search')
self.reset_to_combined_quick_search_check_box.setChecked(self.reset_to_combined_quick_search)
self.hide_combined_quick_error = settings.value('hide combined quick error')
self.hide_combined_quick_error_check_box.setChecked(self.hide_combined_quick_error)
self.bible_search_while_typing = settings.value('is search while typing enabled')
self.bible_search_while_typing_check_box.setChecked(self.bible_search_while_typing)
settings.endGroup()
def save(self):
@ -386,6 +441,9 @@ class BiblesTab(SettingsTab):
if self.language_selection != settings.value('book name language'):
settings.setValue('book name language', self.language_selection)
self.settings_form.register_post_process('bibles_load_list')
settings.setValue('reset to combined quick search', self.reset_to_combined_quick_search)
settings.setValue('hide combined quick error', self.hide_combined_quick_error)
settings.setValue('is search while typing enabled', self.bible_search_while_typing)
settings.endGroup()
if self.tab_visited:
self.settings_form.register_post_process('bibles_config_updated')

View File

@ -23,8 +23,8 @@
import logging
import os
from openlp.core.common import RegistryProperties, AppLocation, Settings, translate, delete_file
from openlp.plugins.bibles.lib import parse_reference, get_reference_separator, LanguageSelection
from openlp.core.common import RegistryProperties, AppLocation, Settings, translate, delete_file, UiStrings
from openlp.plugins.bibles.lib import parse_reference, LanguageSelection
from openlp.plugins.bibles.lib.db import BibleDB, BibleMeta
from .csvbible import CSVBible
from .http import HTTPBible
@ -267,42 +267,21 @@ class BibleManager(RegistryProperties):
For second bible this is necessary.
:param show_error:
"""
# If no bibles are installed, message is given.
log.debug('BibleManager.get_verses("{bible}", "{verse}")'.format(bible=bible, verse=verse_text))
if not bible:
if show_error:
self.main_window.information_message(
translate('BiblesPlugin.BibleManager', 'No Bibles Available'),
translate('BiblesPlugin.BibleManager', 'There are no Bibles currently installed. Please use the '
'Import Wizard to install one or more Bibles.')
)
UiStrings().BibleNoBiblesTitle,
UiStrings().BibleNoBibles)
return None
# Get the language for books.
language_selection = self.get_language_selection(bible)
ref_list = parse_reference(verse_text, self.db_cache[bible], language_selection, book_ref_id)
if ref_list:
return self.db_cache[bible].get_verses(ref_list, show_error)
# If nothing is found. Message is given if this is not combined search. (defined in mediaitem.py)
else:
if show_error:
reference_separators = {
'verse': get_reference_separator('sep_v_display'),
'range': get_reference_separator('sep_r_display'),
'list': get_reference_separator('sep_l_display')}
self.main_window.information_message(
translate('BiblesPlugin.BibleManager', 'Scripture Reference Error'),
translate('BiblesPlugin.BibleManager', 'Your scripture reference is either not supported by '
'OpenLP or is invalid. Please make sure your reference '
'conforms to one of the following patterns or consult the manual:\n\n'
'Book Chapter\n'
'Book Chapter%(range)sChapter\n'
'Book Chapter%(verse)sVerse%(range)sVerse\n'
'Book Chapter%(verse)sVerse%(range)sVerse%(list)sVerse'
'%(range)sVerse\n'
'Book Chapter%(verse)sVerse%(range)sVerse%(list)sChapter'
'%(verse)sVerse%(range)sVerse\n'
'Book Chapter%(verse)sVerse%(range)sChapter%(verse)sVerse',
'Please pay attention to the appended "s" of the wildcards '
'and refrain from translating the words inside the names in the brackets.')
% reference_separators
)
return None
def get_language_selection(self, bible):
@ -334,13 +313,11 @@ class BibleManager(RegistryProperties):
:param text: The text to search for (unicode).
"""
log.debug('BibleManager.verse_search("{bible}", "{text}")'.format(bible=bible, text=text))
# If no bibles are installed, message is given.
if not bible:
self.main_window.information_message(
translate('BiblesPlugin.BibleManager', 'No Bibles Available'),
translate('BiblesPlugin.BibleManager',
'There are no Bibles currently installed. Please use the Import Wizard to install one or '
'more Bibles.')
)
UiStrings().BibleNoBiblesTitle,
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')
@ -348,20 +325,56 @@ class BibleManager(RegistryProperties):
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 message is given.
self.application.set_normal_cursor()
self.main_window.information_message(
translate('BiblesPlugin.BibleManager', 'Web Bible cannot be used'),
translate('BiblesPlugin.BibleManager', 'Text Search is not available with Web Bibles.')
translate('BiblesPlugin.BibleManager', 'Web Bible cannot be used in Text Search'),
translate('BiblesPlugin.BibleManager', 'Text Search is not available with Web Bibles.\n'
'Please use the Scripture Reference Search instead.\n\n'
'This means that the currently used Bible\nor Second Bible '
'is installed as Web Bible.\n\n'
'If you were trying to perform a Reference search\nin Combined '
'Search, your reference is invalid.')
)
return None
if text:
# 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.check_search_result()
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:
return self.db_cache[bible].verse_search(text)
else:
self.main_window.information_message(
translate('BiblesPlugin.BibleManager', 'Scripture Reference Error'),
translate('BiblesPlugin.BibleManager', 'You did not enter a search keyword.\nYou can separate '
'different keywords by a space to search for all of your keywords and you can separate '
'them by a comma to search for one of them.')
)
return None
def save_meta_data(self, bible, version, copyright, permissions, book_name_language=None):

View File

@ -35,6 +35,7 @@ from openlp.plugins.bibles.forms.editbibleform import EditBibleForm
from openlp.plugins.bibles.lib import LayoutStyle, DisplayStyle, VerseReferenceList, get_reference_separator, \
LanguageSelection, BibleStrings
from openlp.plugins.bibles.lib.db import BiblesResourcesDB
import re
log = logging.getLogger(__name__)
@ -45,6 +46,7 @@ class BibleSearch(object):
"""
Reference = 1
Text = 2
Combined = 3
class BibleMediaItem(MediaManagerItem):
@ -56,6 +58,7 @@ class BibleMediaItem(MediaManagerItem):
log.info('Bible Media Item loaded')
def __init__(self, parent, plugin):
self.clear_icon = build_icon(':/bibles/bibles_search_clear.png')
self.lock_icon = build_icon(':/bibles/bibles_search_lock.png')
self.unlock_icon = build_icon(':/bibles/bibles_search_unlock.png')
MediaManagerItem.__init__(self, parent, plugin)
@ -157,10 +160,15 @@ class BibleMediaItem(MediaManagerItem):
search_button_layout = QtWidgets.QHBoxLayout()
search_button_layout.setObjectName(prefix + 'search_button_layout')
search_button_layout.addStretch()
# Note: If we use QPushButton instead of the QToolButton, the icon will be larger than the Lock icon.
clear_button = QtWidgets.QToolButton(tab)
clear_button.setIcon(self.clear_icon)
clear_button.setObjectName(prefix + 'ClearButton')
lock_button = QtWidgets.QToolButton(tab)
lock_button.setIcon(self.unlock_icon)
lock_button.setCheckable(True)
lock_button.setObjectName(prefix + 'LockButton')
search_button_layout.addWidget(clear_button)
search_button_layout.addWidget(lock_button)
search_button = QtWidgets.QPushButton(tab)
search_button.setObjectName(prefix + 'SearchButton')
@ -176,6 +184,7 @@ class BibleMediaItem(MediaManagerItem):
setattr(self, prefix + 'SecondComboBox', second_combo_box)
setattr(self, prefix + 'StyleLabel', style_label)
setattr(self, prefix + 'StyleComboBox', style_combo_box)
setattr(self, prefix + 'ClearButton', clear_button)
setattr(self, prefix + 'LockButton', lock_button)
setattr(self, prefix + 'SearchButtonLayout', search_button_layout)
setattr(self, prefix + 'SearchButton', search_button)
@ -245,11 +254,14 @@ class BibleMediaItem(MediaManagerItem):
self.quickStyleComboBox.activated.connect(self.on_quick_style_combo_box_changed)
self.advancedStyleComboBox.activated.connect(self.on_advanced_style_combo_box_changed)
# Buttons
self.advancedClearButton.clicked.connect(self.on_clear_button)
self.quickClearButton.clicked.connect(self.on_clear_button)
self.advancedSearchButton.clicked.connect(self.on_advanced_search_button)
self.quickSearchButton.clicked.connect(self.on_quick_search_button)
# Other stuff
self.quick_search_edit.returnPressed.connect(self.on_quick_search_button)
self.search_tab_bar.currentChanged.connect(self.on_search_tab_bar_current_changed)
self.quick_search_edit.textChanged.connect(self.on_search_text_edit_changed)
def on_focus(self):
if self.quickTab.isVisible():
@ -286,6 +298,7 @@ class BibleMediaItem(MediaManagerItem):
self.quickStyleComboBox.setItemText(LayoutStyle.VersePerSlide, UiStrings().VersePerSlide)
self.quickStyleComboBox.setItemText(LayoutStyle.VersePerLine, UiStrings().VersePerLine)
self.quickStyleComboBox.setItemText(LayoutStyle.Continuous, UiStrings().Continuous)
self.quickClearButton.setToolTip(translate('BiblesPlugin.MediaItem', 'Clear the search results.'))
self.quickLockButton.setToolTip(translate('BiblesPlugin.MediaItem',
'Toggle to keep or clear the previous results.'))
self.quickSearchButton.setText(UiStrings().Search)
@ -300,6 +313,7 @@ class BibleMediaItem(MediaManagerItem):
self.advancedStyleComboBox.setItemText(LayoutStyle.VersePerSlide, UiStrings().VersePerSlide)
self.advancedStyleComboBox.setItemText(LayoutStyle.VersePerLine, UiStrings().VersePerLine)
self.advancedStyleComboBox.setItemText(LayoutStyle.Continuous, UiStrings().Continuous)
self.advancedClearButton.setToolTip(translate('BiblesPlugin.MediaItem', 'Clear the search results.'))
self.advancedLockButton.setToolTip(translate('BiblesPlugin.MediaItem',
'Toggle to keep or clear the previous results.'))
self.advancedSearchButton.setText(UiStrings().Search)
@ -309,6 +323,9 @@ class BibleMediaItem(MediaManagerItem):
self.plugin.manager.media = self
self.load_bibles()
self.quick_search_edit.set_search_types([
(BibleSearch.Combined, ':/bibles/bibles_search_combined.png',
translate('BiblesPlugin.MediaItem', 'Text or Reference'),
translate('BiblesPlugin.MediaItem', 'Text or Reference...')),
(BibleSearch.Reference, ':/bibles/bibles_search_reference.png',
translate('BiblesPlugin.MediaItem', 'Scripture Reference'),
translate('BiblesPlugin.MediaItem', 'Search Scripture Reference...')),
@ -424,18 +441,24 @@ class BibleMediaItem(MediaManagerItem):
def update_auto_completer(self):
"""
This updates the bible book completion list for the search field. The completion depends on the bible. It is
only updated when we are doing a reference search, otherwise the auto completion list is removed.
only updated when we are doing reference or combined search, in text search the completion list is removed.
"""
log.debug('update_auto_completer')
# Save the current search type to the configuration.
Settings().setValue('{section}/last search type'.format(section=self.settings_section),
self.quick_search_edit.current_search_type())
# Save the current search type to the configuration. If setting for automatically resetting the search type to
# Combined is enabled, use that otherwise use the currently selected search type.
# Note: This setting requires a restart to take effect.
if Settings().value(self.settings_section + '/reset to combined quick search'):
Settings().setValue('{section}/last search type'.format(section=self.settings_section),
BibleSearch.Combined)
else:
Settings().setValue('{section}/last search type'.format(section=self.settings_section),
self.quick_search_edit.current_search_type())
# Save the current bible to the configuration.
Settings().setValue('{section}/quick bible'.format(section=self.settings_section),
self.quickVersionComboBox.currentText())
books = []
# We have to do a 'Reference Search'.
if self.quick_search_edit.current_search_type() == BibleSearch.Reference:
# We have to do a 'Reference Search' (Or as part of Combined Search).
if self.quick_search_edit.current_search_type() is not BibleSearch.Text:
bibles = self.plugin.manager.get_bibles()
bible = self.quickVersionComboBox.currentText()
if bible:
@ -525,7 +548,15 @@ class BibleMediaItem(MediaManagerItem):
self.advancedTab.setVisible(True)
self.advanced_book_combo_box.setFocus()
def on_clear_button(self):
# Clear the list, then set the "No search Results" message, then clear the text field and give it focus.
self.list_view.clear()
self.check_search_result()
self.quick_search_edit.clear()
self.quick_search_edit.setFocus()
def on_lock_button_toggled(self, checked):
self.quick_search_edit.setFocus()
if checked:
self.sender().setIcon(self.lock_icon)
else:
@ -652,10 +683,120 @@ class BibleMediaItem(MediaManagerItem):
self.check_search_result()
self.application.set_normal_cursor()
def on_quick_reference_search(self):
"""
We are doing a 'Reference Search'.
This search is called on def on_quick_search_button by Quick Reference and Combined Searches.
"""
# Set Bibles to use the text input from Quick search field.
bible = self.quickVersionComboBox.currentText()
second_bible = self.quickSecondComboBox.currentText()
"""
Get input from field and replace 'A-Z + . ' with ''
This will check if field has any '.' after A-Z and removes them. Eg. Gen. 1 = Ge 1 = Genesis 1
If Book name has '.' after number. eg. 1. Genesis, the search fails without the dot, and vice versa.
A better solution would be to make '.' optional in the search results. Current solution was easier to code.
"""
text = self.quick_search_edit.text()
text = re.sub('\D[.]\s', ' ', text)
# This is triggered on reference search, use the search from manager.py
if self.quick_search_edit.current_search_type() != BibleSearch.Text:
self.search_results = self.plugin.manager.get_verses(bible, text)
if second_bible and self.search_results:
self.second_search_results = \
self.plugin.manager.get_verses(second_bible, text, self.search_results[0].book.book_reference_id)
def on_quick_text_search(self):
"""
We are doing a 'Text Search'.
This search is called on def on_quick_search_button by Quick Text and Combined Searches.
"""
# Set Bibles to use the text input from Quick search field.
bible = self.quickVersionComboBox.currentText()
second_bible = self.quickSecondComboBox.currentText()
text = self.quick_search_edit.text()
# If Text search ends with "," OpenLP will crash, prevent this from happening by removing all ","s.
text = re.sub('[,]', '', text)
self.application.set_busy_cursor()
# Get Bibles list
bibles = self.plugin.manager.get_bibles()
# Add results to "search_results"
self.search_results = self.plugin.manager.verse_search(bible, second_bible, text)
if second_bible and self.search_results:
# new_search_results is needed to make sure 2nd bible contains all verses. (And counting them on error)
text = []
new_search_results = []
count = 0
passage_not_found = False
# Search second bible for results of search_results to make sure everythigns there.
# Count all the unfound passages.
for verse in self.search_results:
db_book = bibles[second_bible].get_book_by_book_ref_id(verse.book.book_reference_id)
if not db_book:
log.debug('Passage "{name} {chapter:d}:{verse:d}" not found in '
'Second Bible'.format(name=verse.book.name, chapter=verse.chapter, verse=verse.verse))
passage_not_found = True
count += 1
continue
new_search_results.append(verse)
text.append((verse.book.book_reference_id, verse.chapter, verse.verse, verse.verse))
if passage_not_found:
# This is for the 2nd Bible.
self.main_window.information_message(
translate('BiblesPlugin.MediaItem', 'Information'),
translate('BiblesPlugin.MediaItem', 'The second Bible does not contain all the verses '
'that are in the main Bible.\nOnly verses found in both Bibles'
' will be shown.\n\n{count:d} verses have not been included '
'in the results.').format(count=count))
# Join the searches so only verses that are found on both Bibles are shown.
self.search_results = new_search_results
self.second_search_results = bibles[second_bible].get_verses(text)
def on_quick_text_search_while_typing(self):
"""
We are doing a 'Text Search' while typing
Call the verse_search_while_typing from manager.py
It does not show web bible errors while typing.
(It would result in the error popping every time a char is entered or removed)
"""
# Set Bibles to use the text input from Quick search field.
bible = self.quickVersionComboBox.currentText()
second_bible = self.quickSecondComboBox.currentText()
text = self.quick_search_edit.text()
# If Text search ends with "," OpenLP will crash, prevent this from happening by removing all ","s.
text = re.sub('[,]', '', text)
self.application.set_busy_cursor()
# Get Bibles list
bibles = self.plugin.manager.get_bibles()
# Add results to "search_results"
self.search_results = self.plugin.manager.verse_search_while_typing(bible, second_bible, text)
if second_bible and self.search_results:
# new_search_results is needed to make sure 2nd bible contains all verses. (And counting them on error)
text = []
new_search_results = []
count = 0
passage_not_found = False
# Search second bible for results of search_results to make sure everythigns there.
# Count all the unfound passages. Even thou no error is shown, this needs to be done or
# the code breaks later on.
for verse in self.search_results:
db_book = bibles[second_bible].get_book_by_book_ref_id(verse.book.book_reference_id)
if not db_book:
log.debug('Passage ("{versebookname}","{versechapter}","{verseverse}") not found in Second Bible'
.format(versebookname=verse.book.name, versechapter='verse.chapter',
verseverse=verse.verse))
count += 1
continue
new_search_results.append(verse)
text.append((verse.book.book_reference_id, verse.chapter, verse.verse, verse.verse))
# Join the searches so only verses that are found on both Bibles are shown.
self.search_results = new_search_results
self.second_search_results = bibles[second_bible].get_verses(text)
def on_quick_search_button(self):
"""
Does a quick search and saves the search results. Quick search can either be "Reference Search" or
"Text Search".
This triggers the proper Quick search based on which search type is used.
"Eg. "Reference Search", "Text Search" or "Combined search".
"""
log.debug('Quick Search Button clicked')
self.quickSearchButton.setEnabled(False)
@ -664,41 +805,68 @@ class BibleMediaItem(MediaManagerItem):
second_bible = self.quickSecondComboBox.currentText()
text = self.quick_search_edit.text()
if self.quick_search_edit.current_search_type() == BibleSearch.Reference:
# We are doing a 'Reference Search'.
self.search_results = self.plugin.manager.get_verses(bible, text)
if second_bible and self.search_results:
self.second_search_results = \
self.plugin.manager.get_verses(second_bible, text, self.search_results[0].book.book_reference_id)
else:
# We are doing a 'Text Search'.
self.application.set_busy_cursor()
bibles = self.plugin.manager.get_bibles()
self.search_results = self.plugin.manager.verse_search(bible, second_bible, text)
if second_bible and self.search_results:
text = []
new_search_results = []
count = 0
passage_not_found = False
for verse in self.search_results:
db_book = bibles[second_bible].get_book_by_book_ref_id(verse.book.book_reference_id)
if not db_book:
log.debug('Passage "{name} {chapter:d}:{verse:d}" not found in '
'Second Bible'.format(name=verse.book.name, chapter=verse.chapter, verse=verse.verse))
passage_not_found = True
count += 1
continue
new_search_results.append(verse)
text.append((verse.book.book_reference_id, verse.chapter, verse.verse, verse.verse))
if passage_not_found:
QtWidgets.QMessageBox.information(
self, translate('BiblesPlugin.MediaItem', 'Information'),
translate('BiblesPlugin.MediaItem',
'The second Bible does not contain all the verses that are in the main Bible. '
'Only verses found in both Bibles will be shown. {count:d} verses have not been '
'included in the results.').format(count=count),
QtWidgets.QMessageBox.StandardButtons(QtWidgets.QMessageBox.Ok))
self.search_results = new_search_results
self.second_search_results = bibles[second_bible].get_verses(text)
# We are doing a 'Reference Search'. (Get script from def on_quick_reference_search)
self.on_quick_reference_search()
# Get reference separators from settings.
if not self.search_results:
reference_separators = {
'verse': get_reference_separator('sep_v_display'),
'range': get_reference_separator('sep_r_display'),
'list': get_reference_separator('sep_l_display')}
self.main_window.information_message(
translate('BiblesPlugin.BibleManager', 'Scripture Reference Error'),
translate('BiblesPlugin.BibleManager', '<strong>OpenLP couldnt find anything '
'with your search.<br><br>'
'Please make sure that your reference follows '
'one of these patterns:</strong><br><br>%s'
% UiStrings().BibleScriptureError % reference_separators))
elif self.quick_search_edit.current_search_type() == BibleSearch.Text:
# We are doing a 'Text Search'. (Get script from def on_quick_text_search)
self.on_quick_text_search()
if not self.search_results and len(text) - text.count(' ') < 3 and bible:
self.main_window.information_message(
UiStrings().BibleShortSearchTitle,
UiStrings().BibleShortSearch)
elif self.quick_search_edit.current_search_type() == BibleSearch.Combined:
# We are doing a 'Combined search'. Starting with reference search.
# Perform only if text contains any numbers
if (char.isdigit() for char in text):
self.on_quick_reference_search()
"""
If results are found, search will be finalized.
This check needs to be here in order to avoid duplicate errors.
If keyword is shorter than 3 (not including spaces), message is given. It's actually possible to find
verses with less than 3 chars (Eg. G1 = Genesis 1) thus this error is not shown if any results are found.
if no Bibles are installed, this message is not shown - "No bibles" message is shown instead.
"""
if not self.search_results and len(text) - text.count(' ') < 3 and bible:
self.main_window.information_message(
UiStrings().BibleShortSearchTitle,
UiStrings().BibleShortSearch)
if not self.search_results and len(text) - text.count(' ') > 2 and bible:
# Text search starts here if no reference was found and keyword is longer than 2.
# > 2 check is required in order to avoid duplicate error messages for short keywords.
self.on_quick_text_search()
if not self.search_results and not \
Settings().value(self.settings_section + '/hide combined quick error'):
self.application.set_normal_cursor()
# Reference separators need to be defined both, in here and on reference search,
# error won't work if they are left out from one.
reference_separators = {
'verse': get_reference_separator('sep_v_display'),
'range': get_reference_separator('sep_r_display'),
'list': get_reference_separator('sep_l_display')}
self.main_window.information_message(translate('BiblesPlugin.BibleManager', 'Nothing found'),
translate('BiblesPlugin.BibleManager',
'<strong>OpenLP couldnt find anything with your'
' search.</strong><br><br>If you tried to search'
' with Scripture Reference, please make<br> sure'
' that your reference follows one of these'
' patterns: <br><br>%s'
% UiStrings().BibleScriptureError %
reference_separators))
# Finalizing the search
# List is cleared if not locked, results are listed, button is set available, cursor is set to normal.
if not self.quickLockButton.isChecked():
self.list_view.clear()
if self.list_view.count() != 0 and self.search_results:
@ -709,6 +877,99 @@ class BibleMediaItem(MediaManagerItem):
self.check_search_result()
self.application.set_normal_cursor()
def on_quick_search_while_typing(self):
"""
This function is called when "Search as you type" is enabled for Bibles.
It is basically the same thing as "on_quick_search_search" but all the error messages are removed.
This also has increased min len for text search for performance reasons.
For commented version, please visit def on_quick_search_button.
"""
bible = self.quickVersionComboBox.currentText()
second_bible = self.quickSecondComboBox.currentText()
text = self.quick_search_edit.text()
if self.quick_search_edit.current_search_type() == BibleSearch.Combined:
# If text has no numbers, auto search limit is min 8 characters for performance reasons.
# If you change this value, also change it in biblestab.py (Count) in enabling search while typing.
if (char.isdigit() for char in text) and len(text) > 2:
self.on_quick_reference_search()
if not self.search_results and len(text) > 7:
self.on_quick_text_search_while_typing()
elif self.quick_search_edit.current_search_type() == BibleSearch.Reference:
self.on_quick_reference_search()
elif self.quick_search_edit.current_search_type() == BibleSearch.Text:
if len(text) > 7:
self.on_quick_text_search_while_typing()
if not self.quickLockButton.isChecked():
self.list_view.clear()
if self.list_view.count() != 0 and self.search_results:
self.__check_second_bible(bible, second_bible)
elif self.search_results:
self.display_results(bible, second_bible)
self.check_search_result()
self.application.set_normal_cursor()
def on_search_text_edit_changed(self):
"""
If search automatically while typing is enabled, perform the search and list results when conditions are met.
"""
if Settings().value('bibles/is search while typing enabled'):
text = self.quick_search_edit.text()
"""
Use Regex for finding space + number in reference search and space + 2 characters in text search.
Also search for two characters (Searches require at least two sets of two characters)
These are used to prevent bad search queries from starting. (Long/crashing queries)
"""
space_and_digit_reference = re.compile(' \d')
two_chars_text = re.compile('\S\S')
space_and_two_chars_text = re.compile(' \S\S')
# Turn this into a format that may be used in if statement.
count_space_digit_reference = space_and_digit_reference.findall(text)
count_two_chars_text = two_chars_text.findall(text)
count_spaces_two_chars_text = space_and_two_chars_text.findall(text)
"""
The Limit is required for setting the proper "No items found" message.
"Limit" is also hard coded to on_quick_search_while_typing, it must be there to avoid bad search
performance. Limit 8 = Text search, 3 = Reference search.
"""
limit = 8
if self.quick_search_edit.current_search_type() == BibleSearch.Combined:
if len(count_space_digit_reference) != 0:
limit = 3
elif self.quick_search_edit.current_search_type() == BibleSearch.Reference:
limit = 3
"""
If text is empty, clear the list.
else: Start by checking if the search is suitable for "Search while typing"
"""
if len(text) == 0:
if not self.quickLockButton.isChecked():
self.list_view.clear()
self.check_search_result()
else:
if limit == 3 and (len(text) < limit or len(count_space_digit_reference) == 0):
if not self.quickLockButton.isChecked():
self.list_view.clear()
self.check_search_result()
elif (limit == 8 and (len(text) < limit or len(count_spaces_two_chars_text) == 0 or
len(count_two_chars_text) < 2)):
if not self.quickLockButton.isChecked():
self.list_view.clear()
self.check_search_result_search_while_typing_short()
else:
"""
Start search if no chars are entered or deleted for 0.2 s
If no Timer is set, Text search will break the search by sending repeative search Quaries on
all chars. Use the self.on_quick_search_while_typing, this does not contain any error messages.
"""
self.search_timer = ()
if self.search_timer:
self.search_timer.stop()
self.search_timer.deleteLater()
self.search_timer = QtCore.QTimer()
self.search_timer.timeout.connect(self.on_quick_search_while_typing)
self.search_timer.setSingleShot(True)
self.search_timer.start(200)
def display_results(self, bible, second_bible=''):
"""
Displays the search results in the media manager. All data needed for further action is saved for/in each row.

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -30,9 +30,11 @@
<file>image_new_group.png</file>
</qresource>
<qresource prefix="bibles">
<file>bibles_search_combined.png</file>
<file>bibles_search_text.png</file>
<file>bibles_search_reference.png</file>
<file>bibles_upgrade_alert.png</file>
<file>bibles_search_clear.png</file>
<file>bibles_search_unlock.png</file>
<file>bibles_search_lock.png</file>
</qresource>

View File

@ -23,6 +23,7 @@
This module contains tests for the lib submodule of the Presentations plugin.
"""
from unittest import TestCase
from openlp.core.common import Registry
from openlp.plugins.bibles.lib.mediaitem import BibleMediaItem
from tests.functional import MagicMock, patch
from tests.helpers.testmixin import TestMixin
@ -41,6 +42,9 @@ class TestMediaItem(TestCase, TestMixin):
patch('openlp.plugins.bibles.lib.mediaitem.BibleMediaItem.setup_item'):
self.media_item = BibleMediaItem(None, MagicMock())
self.setup_application()
self.mocked_main_window = MagicMock()
Registry.create()
Registry().register('main_window', self.mocked_main_window)
def test_display_results_no_results(self):
"""
@ -109,3 +113,40 @@ class TestMediaItem(TestCase, TestMixin):
mocked_list_view.selectAll.assert_called_once_with()
self.assertEqual(self.media_item.search_results, {})
self.assertEqual(self.media_item.second_search_results, {})
def on_quick_search_button_general_test(self):
"""
Test that general things, which should be called on all Quick searches are called.
"""
# GIVEN: self.application as self.app, all the required functions
Registry.create()
Registry().register('application', self.app)
self.media_item.quickSearchButton = MagicMock()
self.app.process_events = MagicMock()
self.media_item.quickVersionComboBox = MagicMock()
self.media_item.quickVersionComboBox.currentText = MagicMock()
self.media_item.quickSecondComboBox = MagicMock()
self.media_item.quickSecondComboBox.currentText = MagicMock()
self.media_item.quick_search_edit = MagicMock()
self.media_item.quick_search_edit.text = MagicMock()
self.media_item.quickLockButton = MagicMock()
self.media_item.list_view = MagicMock()
self.media_item.search_results = MagicMock()
self.media_item.display_results = MagicMock()
self.media_item.check_search_result = MagicMock()
self.app.set_normal_cursor = MagicMock()
# WHEN: on_quick_search_button is called
self.media_item.on_quick_search_button()
# THEN: Search should had been started and finalized properly
self.assertEqual(1, self.app.process_events.call_count, 'Normal cursor should had been called once')
self.assertEqual(1, self.media_item.quickVersionComboBox.currentText.call_count, 'Should had been called once')
self.assertEqual(1, self.media_item.quickSecondComboBox.currentText.call_count, 'Should had been called once')
self.assertEqual(1, self.media_item.quick_search_edit.text.call_count, 'Text edit Should had been called once')
self.assertEqual(1, self.media_item.quickLockButton.isChecked.call_count, 'Lock Should had been called once')
self.assertEqual(1, self.media_item.display_results.call_count, 'Display results Should had been called once')
self.assertEqual(2, self.media_item.quickSearchButton.setEnabled.call_count, 'Disable and Enable the button')
self.assertEqual(1, self.media_item.check_search_result.call_count, 'Check results Should had been called once')
self.assertEqual(1, self.app.set_normal_cursor.call_count, 'Normal cursor should had been called once')