From f9d01e414aa95e7357773ece091d32560416a712 Mon Sep 17 00:00:00 2001 From: Philip Ridout Date: Sun, 18 Dec 2016 13:35:03 +0000 Subject: [PATCH 01/26] settings.py --- openlp/core/common/settings.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openlp/core/common/settings.py b/openlp/core/common/settings.py index 132e9652a..73a6cdbcf 100644 --- a/openlp/core/common/settings.py +++ b/openlp/core/common/settings.py @@ -217,7 +217,9 @@ class Settings(QtCore.QSettings): ('advanced/default image', 'core/logo file', []), # Default image renamed + moved to general after 2.4. ('shortcuts/escapeItem', 'shortcuts/desktopScreenEnable', []), # Escape item was removed in 2.6. ('shortcuts/offlineHelpItem', 'shortcuts/userManualItem', []), # Online and Offline help were combined in 2.6. - ('shortcuts/onlineHelpItem', 'shortcuts/userManualItem', []) # Online and Offline help were combined in 2.6. + ('shortcuts/onlineHelpItem', 'shortcuts/userManualItem', []), # Online and Offline help were combined in 2.6. + ('bibles/advanced bible', '', []), # Common bible search widgets combined in 2.6 + ('bibles/quick bible', 'bibles/primary bible', []) # Common bible search widgets combined in 2.6 ] @staticmethod From 46732c1f2c36e3069ebed373dfa690c36e7c9c63 Mon Sep 17 00:00:00 2001 From: Philip Ridout Date: Sun, 18 Dec 2016 13:47:48 +0000 Subject: [PATCH 02/26] bibleplugin.py --- openlp/plugins/bibles/bibleplugin.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openlp/plugins/bibles/bibleplugin.py b/openlp/plugins/bibles/bibleplugin.py index e9168d695..7b1aee1ea 100644 --- a/openlp/plugins/bibles/bibleplugin.py +++ b/openlp/plugins/bibles/bibleplugin.py @@ -46,8 +46,7 @@ __default_settings__ = { 'bibles/is verse number visible': True, 'bibles/display new chapter': False, 'bibles/second bibles': True, - 'bibles/advanced bible': '', - 'bibles/quick bible': '', + 'bibles/primary bible': '', 'bibles/proxy name': '', 'bibles/proxy address': '', 'bibles/proxy username': '', From f687f77226837ec600b9e3fc3592492533a64460 Mon Sep 17 00:00:00 2001 From: Philip Ridout Date: Sun, 18 Dec 2016 13:49:34 +0000 Subject: [PATCH 03/26] openlp/plugins/bibles/lib/__init__.py --- openlp/plugins/bibles/lib/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openlp/plugins/bibles/lib/__init__.py b/openlp/plugins/bibles/lib/__init__.py index e730009e7..f082f7757 100644 --- a/openlp/plugins/bibles/lib/__init__.py +++ b/openlp/plugins/bibles/lib/__init__.py @@ -230,7 +230,7 @@ def update_reference_separators(): REFERENCE_MATCHES['range_separator'] = re.compile(REFERENCE_SEPARATORS['sep_l'], re.UNICODE) # full reference match: ((,(?!$)|(?=$)))+ REFERENCE_MATCHES['full'] = \ - re.compile('^\s*(?!\s)(?P[\d]*[^\d]+)(?[\d]*[^\d\.]+)\.*(?(?:%(range_regex)s(?:%(sep_l)s(?!\s*$)|(?=\s*$)))+)\s*$' % dict(list(REFERENCE_SEPARATORS.items()) + [('range_regex', range_regex)]), re.UNICODE) @@ -326,7 +326,7 @@ def parse_reference(reference, bible, language_selection, book_ref_id=False): ``^\s*(?!\s)(?P[\d]*[^\d]+)(?(?:%(range_regex)s(?:%(sep_l)s(?!\s*$)|(?=\s*$)))+)\s*$`` The second group contains all ``ranges``. This can be multiple declarations of range_regex separated by a list From 2612977c06333fb324ad4a18327ed61fcc5bb63f Mon Sep 17 00:00:00 2001 From: Philip Ridout Date: Sun, 18 Dec 2016 13:54:25 +0000 Subject: [PATCH 04/26] Refactors --- openlp/plugins/bibles/lib/mediaitem.py | 1486 +++++++++--------------- 1 file changed, 577 insertions(+), 909 deletions(-) diff --git a/openlp/plugins/bibles/lib/mediaitem.py b/openlp/plugins/bibles/lib/mediaitem.py index c9b5c6b1e..c09767b89 100644 --- a/openlp/plugins/bibles/lib/mediaitem.py +++ b/openlp/plugins/bibles/lib/mediaitem.py @@ -21,28 +21,36 @@ ############################################################################### import logging +import re from PyQt5 import QtCore, QtWidgets from openlp.core.common import Registry, Settings, UiStrings, translate -from openlp.core.lib import MediaManagerItem, ItemCapabilities, ServiceItemContext, create_separated_list +from openlp.core.lib import MediaManagerItem, ItemCapabilities, ServiceItemContext from openlp.core.lib.searchedit import SearchEdit from openlp.core.lib.ui import set_case_insensitive_completer, create_horizontal_adjusting_combo_box, \ critical_error_message_box, find_and_set_in_combo_box, build_icon from openlp.core.common.languagemanager import get_locale_key from openlp.plugins.bibles.forms.bibleimportform import BibleImportForm 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 +from openlp.plugins.bibles.lib import DisplayStyle, LayoutStyle, VerseReferenceList, \ + get_reference_match, get_reference_separator log = logging.getLogger(__name__) +VALID_TEXT_SEARCH = re.compile('\w\w\w') + + +def get_reference_separators(): + return {'verse': get_reference_separator('sep_v_display'), + 'range': get_reference_separator('sep_r_display'), + 'list': get_reference_separator('sep_l_display')} + + class BibleSearch(object): """ - Enumeration class for the different search methods for the "quick search". + Enumeration class for the different search methods for the "Search" tab. """ Reference = 1 Text = 2 @@ -57,15 +65,31 @@ class BibleMediaItem(MediaManagerItem): bibles_add_to_service = QtCore.pyqtSignal(list) log.info('Bible Media Item loaded') - def __init__(self, parent, plugin): + def __init__(self, *args, **kwargs): + """ + Constructor + + :param args: Positional arguments to pass to the super method. (tuple) + :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') - MediaManagerItem.__init__(self, parent, plugin) + self.sort_icon = build_icon(':/bibles/bibles_book_sort.png') + self.bible = None + self.second_bible = None + # TODO: Make more central and clean up after! + self.search_timer = QtCore.QTimer() + self.search_timer.setInterval(200) + self.search_timer.setSingleShot(True) + self.search_timer.timeout.connect(self.on_search_timer_timeout) + super().__init__(*args, **kwargs) def setup_item(self): """ Do some additional setup. + + :return: None """ self.bibles_go_live.connect(self.go_live_remote) self.bibles_add_to_service.connect(self.add_to_service_remote) @@ -73,251 +97,174 @@ class BibleMediaItem(MediaManagerItem): self.settings = self.plugin.settings_tab self.quick_preview_allowed = True self.has_search = True - self.search_results = {} - self.second_search_results = {} + self.search_results = [] + self.second_search_results = [] Registry().register_function('bibles_load_list', self.reload_bibles) - def __check_second_bible(self, bible, second_bible): - """ - Check if the first item is a second bible item or not. - """ - if not self.list_view.count(): - self.display_results(bible, second_bible) - return - item_second_bible = self._decode_qt_object(self.list_view.item(0), 'second_bible') - if item_second_bible and second_bible or not item_second_bible and not second_bible: - self.display_results(bible, second_bible) - elif critical_error_message_box( - message=translate('BiblesPlugin.MediaItem', - 'You cannot combine single and dual Bible verse search results. ' - 'Do you want to delete your search results and start a new search?'), - parent=self, question=True) == QtWidgets.QMessageBox.Yes: - self.list_view.clear() - self.display_results(bible, second_bible) - - def _decode_qt_object(self, bitem, key): - reference = bitem.data(QtCore.Qt.UserRole) - obj = reference[str(key)] - return str(obj).strip() - def required_icons(self): """ Set which icons the media manager tab should show + + :return: None """ - MediaManagerItem.required_icons(self) + super().required_icons() self.has_import_icon = True self.has_new_icon = False self.has_edit_icon = True self.has_delete_icon = True self.add_to_service_item = False - def add_search_tab(self, prefix, name): - self.search_tab_bar.addTab(name) - tab = QtWidgets.QWidget() - tab.setObjectName(prefix + 'Tab') - tab.setSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum) - layout = QtWidgets.QGridLayout(tab) - layout.setObjectName(prefix + 'Layout') - setattr(self, prefix + 'Tab', tab) - setattr(self, prefix + 'Layout', layout) - - def add_search_fields(self, prefix, name): - """ - Creates and adds generic search tab. - - :param prefix: The prefix of the tab, this is either ``quick`` or ``advanced``. - :param name: The translated string to display. - """ - if prefix == 'quick': - idx = 2 - else: - idx = 5 - tab = getattr(self, prefix + 'Tab') - layout = getattr(self, prefix + 'Layout') - version_label = QtWidgets.QLabel(tab) - version_label.setObjectName(prefix + 'VersionLabel') - layout.addWidget(version_label, idx, 0, QtCore.Qt.AlignRight) - version_combo_box = create_horizontal_adjusting_combo_box(tab, prefix + 'VersionComboBox') - version_label.setBuddy(version_combo_box) - layout.addWidget(version_combo_box, idx, 1, 1, 2) - second_label = QtWidgets.QLabel(tab) - second_label.setObjectName(prefix + 'SecondLabel') - layout.addWidget(second_label, idx + 1, 0, QtCore.Qt.AlignRight) - second_combo_box = create_horizontal_adjusting_combo_box(tab, prefix + 'SecondComboBox') - version_label.setBuddy(second_combo_box) - layout.addWidget(second_combo_box, idx + 1, 1, 1, 2) - style_label = QtWidgets.QLabel(tab) - style_label.setObjectName(prefix + 'StyleLabel') - layout.addWidget(style_label, idx + 2, 0, QtCore.Qt.AlignRight) - style_combo_box = create_horizontal_adjusting_combo_box(tab, prefix + 'StyleComboBox') - style_combo_box.addItems(['', '', '']) - layout.addWidget(style_combo_box, idx + 2, 1, 1, 2) - 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') - search_button_layout.addWidget(search_button) - layout.addLayout(search_button_layout, idx + 3, 1, 1, 2) - self.page_layout.addWidget(tab) - tab.setVisible(False) - lock_button.toggled.connect(self.on_lock_button_toggled) - second_combo_box.currentIndexChanged.connect(self.on_second_bible_combobox_index_changed) - setattr(self, prefix + 'VersionLabel', version_label) - setattr(self, prefix + 'VersionComboBox', version_combo_box) - setattr(self, prefix + 'SecondLabel', second_label) - 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) - def add_end_header_bar(self): self.search_tab_bar = QtWidgets.QTabBar(self) self.search_tab_bar.setExpanding(False) - self.search_tab_bar.setObjectName('search_tab_bar') self.page_layout.addWidget(self.search_tab_bar) - # Add the Quick Search tab. - self.add_search_tab('quick', translate('BiblesPlugin.MediaItem', 'Search')) - self.quick_search_label = QtWidgets.QLabel(self.quickTab) - self.quick_search_label.setObjectName('quick_search_label') - self.quickLayout.addWidget(self.quick_search_label, 0, 0, QtCore.Qt.AlignRight) - self.quick_search_edit = SearchEdit(self.quickTab, self.settings_section) - self.quick_search_edit.setSizePolicy(QtWidgets.QSizePolicy.Ignored, QtWidgets.QSizePolicy.Fixed) - self.quick_search_edit.setObjectName('quick_search_edit') - self.quick_search_label.setBuddy(self.quick_search_edit) - self.quickLayout.addWidget(self.quick_search_edit, 0, 1, 1, 2) - self.add_search_fields('quick', translate('BiblesPlugin.MediaItem', 'Search')) - self.quickTab.setVisible(True) - # Add the Advanced Search tab. - self.add_search_tab('advanced', translate('BiblesPlugin.MediaItem', 'Select')) - self.advanced_book_label = QtWidgets.QLabel(self.advancedTab) - self.advanced_book_label.setObjectName('advanced_book_label') - self.advancedLayout.addWidget(self.advanced_book_label, 0, 0, QtCore.Qt.AlignRight) - self.advanced_book_combo_box = create_horizontal_adjusting_combo_box(self.advancedTab, - 'advanced_book_combo_box') - self.advanced_book_label.setBuddy(self.advanced_book_combo_box) - self.advancedLayout.addWidget(self.advanced_book_combo_box, 0, 1, 1, 2) - self.advanced_chapter_label = QtWidgets.QLabel(self.advancedTab) - self.advanced_chapter_label.setObjectName('advanced_chapter_label') - self.advancedLayout.addWidget(self.advanced_chapter_label, 1, 1, 1, 2) - self.advanced_verse_label = QtWidgets.QLabel(self.advancedTab) - self.advanced_verse_label.setObjectName('advanced_verse_label') - self.advancedLayout.addWidget(self.advanced_verse_label, 1, 2) - self.advanced_from_label = QtWidgets.QLabel(self.advancedTab) - self.advanced_from_label.setObjectName('advanced_from_label') - self.advancedLayout.addWidget(self.advanced_from_label, 3, 0, QtCore.Qt.AlignRight) - self.advanced_from_chapter = QtWidgets.QComboBox(self.advancedTab) - self.advanced_from_chapter.setObjectName('advanced_from_chapter') - self.advancedLayout.addWidget(self.advanced_from_chapter, 3, 1) - self.advanced_from_verse = QtWidgets.QComboBox(self.advancedTab) - self.advanced_from_verse.setObjectName('advanced_from_verse') - self.advancedLayout.addWidget(self.advanced_from_verse, 3, 2) - self.advanced_to_label = QtWidgets.QLabel(self.advancedTab) - self.advanced_to_label.setObjectName('advanced_to_label') - self.advancedLayout.addWidget(self.advanced_to_label, 4, 0, QtCore.Qt.AlignRight) - self.advanced_to_chapter = QtWidgets.QComboBox(self.advancedTab) - self.advanced_to_chapter.setObjectName('advanced_to_chapter') - self.advancedLayout.addWidget(self.advanced_to_chapter, 4, 1) - self.advanced_to_verse = QtWidgets.QComboBox(self.advancedTab) - self.advanced_to_verse.setObjectName('advanced_to_verse') - self.advancedLayout.addWidget(self.advanced_to_verse, 4, 2) - self.add_search_fields('advanced', UiStrings().Advanced) + # Add the Search tab. + self.search_tab = QtWidgets.QWidget() + self.search_tab.setSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum) + self.search_tab_bar.addTab(translate('BiblesPlugin.MediaItem', 'Find')) + self.search_layout = QtWidgets.QFormLayout(self.search_tab) + self.search_edit = SearchEdit(self.search_tab, self.settings_section) + self.search_layout.addRow(translate('BiblesPlugin.MediaItem', 'Find:'), self.search_edit) + self.search_tab.setVisible(True) + self.page_layout.addWidget(self.search_tab) + # Add the Select tab. + self.select_tab = QtWidgets.QWidget() + self.select_tab.setSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum) + self.search_tab_bar.addTab(translate('BiblesPlugin.MediaItem', 'Select')) + self.select_layout = QtWidgets.QFormLayout(self.select_tab) + self.book_layout = QtWidgets.QHBoxLayout(self.select_tab) + self.select_book_combo_box = create_horizontal_adjusting_combo_box(self.select_tab, 'select_book_combo_box') + self.book_layout.addWidget(self.select_book_combo_box) + self.book_order_button = QtWidgets.QToolButton() + self.book_order_button.setIcon(self.sort_icon) + self.book_order_button.setCheckable(True) + self.book_layout.addWidget(self.book_order_button) + self.select_layout.addRow(translate('BiblesPlugin.MediaItem', 'Book:'), self.book_layout) + self.verse_title_layout = QtWidgets.QHBoxLayout(self.select_tab) + self.chapter_label = QtWidgets.QLabel(self.select_tab) + self.verse_title_layout.addWidget(self.chapter_label) + self.verse_label = QtWidgets.QLabel(self.select_tab) + self.verse_title_layout.addWidget(self.verse_label) + self.select_layout.addRow('', self.verse_title_layout) + self.from_layout = QtWidgets.QHBoxLayout(self.select_tab) + self.from_chapter = QtWidgets.QComboBox(self.select_tab) + self.from_layout.addWidget(self.from_chapter) + self.from_verse = QtWidgets.QComboBox(self.select_tab) + self.from_layout.addWidget(self.from_verse) + self.select_layout.addRow(translate('BiblesPlugin.MediaItem', 'From:'), self.from_layout) + self.to_layout = QtWidgets.QHBoxLayout(self.select_tab) + self.to_chapter = QtWidgets.QComboBox(self.select_tab) + self.to_layout.addWidget(self.to_chapter) + self.to_verse = QtWidgets.QComboBox(self.select_tab) + self.to_layout.addWidget(self.to_verse) + self.select_layout.addRow(translate('BiblesPlugin.MediaItem', 'To:'), self.to_layout) + 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.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') + self.general_bible_layout.addRow(translate('BiblesPlugin.MediaItem', 'Second:'), self.second_combo_box) + 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_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.setIcon(self.clear_icon) + self.lock_button = QtWidgets.QToolButton(self) + self.lock_button.setIcon(self.unlock_icon) + self.lock_button.setCheckable(True) + self.search_button_layout.addWidget(self.clear_button) + self.search_button_layout.addWidget(self.lock_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) + + def setupUi(self): + super().setupUi() + + sort_model = QtCore.QSortFilterProxyModel(self.select_book_combo_box) + model = self.select_book_combo_box.model() + # Reparent the combo box model to the sort proxy, otherwise it will be deleted when we change the comobox's + # model + model.setParent(sort_model) + sort_model.setSourceModel(model) + self.select_book_combo_box.setModel(sort_model) + + # Signals & Slots # Combo Boxes - self.quickVersionComboBox.activated.connect(self.update_auto_completer) - self.quickSecondComboBox.activated.connect(self.update_auto_completer) - self.advancedVersionComboBox.activated.connect(self.on_advanced_version_combo_box) - self.advancedSecondComboBox.activated.connect(self.on_advanced_second_combo_box) - self.advanced_book_combo_box.activated.connect(self.on_advanced_book_combo_box) - self.advanced_from_chapter.activated.connect(self.on_advanced_from_chapter) - self.advanced_from_verse.activated.connect(self.on_advanced_from_verse) - self.advanced_to_chapter.activated.connect(self.on_advanced_to_chapter) - self.quick_search_edit.searchTypeChanged.connect(self.update_auto_completer) - self.quickVersionComboBox.activated.connect(self.update_auto_completer) - self.quickStyleComboBox.activated.connect(self.on_quick_style_combo_box_changed) - self.advancedStyleComboBox.activated.connect(self.on_advanced_style_combo_box_changed) + self.select_book_combo_box.activated.connect(self.on_advanced_book_combo_box) + self.from_chapter.activated.connect(self.on_from_chapter_activated) + self.from_verse.activated.connect(self.on_from_verse) + self.to_chapter.activated.connect(self.on_to_chapter) + self.version_combo_box.currentIndexChanged.connect(self.on_version_combo_box_index_changed) + self.version_combo_box.currentIndexChanged.connect(self.update_auto_completer) + self.second_combo_box.currentIndexChanged.connect(self.on_second_combo_box_index_changed) + self.second_combo_box.currentIndexChanged.connect(self.update_auto_completer) + self.style_combo_box.currentIndexChanged.connect(self.on_style_combo_box_index_changed) + self.search_edit.searchTypeChanged.connect(self.update_auto_completer) # Buttons - self.advancedClearButton.clicked.connect(self.on_advanced_clear_button_clicked) - self.quickClearButton.clicked.connect(self.on_clear_button_clicked) - self.advancedSearchButton.clicked.connect(self.on_advanced_search_button) - self.quickSearchButton.clicked.connect(self.on_quick_search_button) + 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.search_button.clicked.connect(self.on_search_button_clicked) # Other stuff - self.quick_search_edit.returnPressed.connect(self.on_quick_search_button) + self.search_edit.returnPressed.connect(self.on_search_button_clicked) 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(): - self.quick_search_edit.setFocus() - self.quick_search_edit.selectAll() - else: - self.advanced_book_combo_box.setFocus() - - def config_update(self): - log.debug('config_update') - if Settings().value(self.settings_section + '/second bibles'): - self.quickSecondLabel.setVisible(True) - self.quickSecondComboBox.setVisible(True) - self.advancedSecondLabel.setVisible(True) - self.advancedSecondComboBox.setVisible(True) - self.quickSecondLabel.setVisible(True) - self.quickSecondComboBox.setVisible(True) - else: - self.quickSecondLabel.setVisible(False) - self.quickSecondComboBox.setVisible(False) - self.advancedSecondLabel.setVisible(False) - self.advancedSecondComboBox.setVisible(False) - self.quickSecondLabel.setVisible(False) - self.quickSecondComboBox.setVisible(False) - self.quickStyleComboBox.setCurrentIndex(self.settings.layout_style) - self.advancedStyleComboBox.setCurrentIndex(self.settings.layout_style) + self.search_edit.textChanged.connect(self.on_search_edit_text_changed) def retranslateUi(self): log.debug('retranslateUi') - self.quick_search_label.setText(translate('BiblesPlugin.MediaItem', 'Find:')) - self.quickVersionLabel.setText('{version}:'.format(version=UiStrings().Version)) - self.quickSecondLabel.setText(translate('BiblesPlugin.MediaItem', 'Second:')) - self.quickStyleLabel.setText(UiStrings().LayoutStyle) - 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) - self.advanced_book_label.setText(translate('BiblesPlugin.MediaItem', 'Book:')) - self.advanced_chapter_label.setText(translate('BiblesPlugin.MediaItem', 'Chapter:')) - self.advanced_verse_label.setText(translate('BiblesPlugin.MediaItem', 'Verse:')) - self.advanced_from_label.setText(translate('BiblesPlugin.MediaItem', 'From:')) - self.advanced_to_label.setText(translate('BiblesPlugin.MediaItem', 'To:')) - self.advancedVersionLabel.setText('{version}:'.format(version=UiStrings().Version)) - self.advancedSecondLabel.setText(translate('BiblesPlugin.MediaItem', 'Second:')) - self.advancedStyleLabel.setText(UiStrings().LayoutStyle) - 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) + self.chapter_label.setText(translate('BiblesPlugin.MediaItem', 'Chapter:')) + self.verse_label.setText(translate('BiblesPlugin.MediaItem', 'Verse:')) + 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.search_button.setText(UiStrings().Search) + + def on_focus(self): + """ + Set focus on the appropriate widget when BibleMediaItem receives focus + + Reimplements MediaManagerItem.on_focus() + + :return: None + """ + if self.search_tab.isVisible(): + self.search_edit.setFocus() + self.search_edit.selectAll() + else: + self.select_book_combo_box.setFocus() + + def config_update(self): + """ + Change the visible widgets when the config changes + + :return: None + """ + log.debug('config_update') + visible = Settings().value('{settings_section}/second bibles'.format(settings_section=self.settings_section)) + self.general_bible_layout.labelForField(self.second_combo_box).setVisible(visible) + self.second_combo_box.setVisible(visible) def initialise(self): + """ + Called to complete initialisation that could not be completed in the constructor. + + :return: None + """ log.debug('bible manager initialise') self.plugin.manager.media = self - self.load_bibles() - self.quick_search_edit.set_search_types([ + self.populate_bible_combo_boxes() + self.search_edit.set_search_types([ (BibleSearch.Combined, ':/bibles/bibles_search_combined.png', translate('BiblesPlugin.MediaItem', 'Text or Reference'), translate('BiblesPlugin.MediaItem', 'Text or Reference...')), @@ -328,164 +275,107 @@ class BibleMediaItem(MediaManagerItem): translate('BiblesPlugin.MediaItem', 'Text Search'), translate('BiblesPlugin.MediaItem', 'Search Text...')) ]) - if Settings().value(self.settings_section + '/reset to combined quick search'): - self.quick_search_edit.set_current_search_type(BibleSearch.Combined) + if Settings().value( + '{settings_section}/reset to combined quick search'.format(settings_section=self.settings_section)): + self.search_edit.set_current_search_type(BibleSearch.Combined) self.config_update() log.debug('bible manager initialise complete') - def load_bibles(self): - log.debug('Loading Bibles') - self.quickVersionComboBox.clear() - self.quickSecondComboBox.clear() - self.advancedVersionComboBox.clear() - self.advancedSecondComboBox.clear() - self.quickSecondComboBox.addItem('') - self.advancedSecondComboBox.addItem('') - # Get all bibles and sort the list. - bibles = list(self.plugin.manager.get_bibles().keys()) - bibles = [_f for _f in bibles if _f] - bibles.sort(key=get_locale_key) - # Load the bibles into the combo boxes. - self.quickVersionComboBox.addItems(bibles) - self.quickSecondComboBox.addItems(bibles) - self.advancedVersionComboBox.addItems(bibles) - self.advancedSecondComboBox.addItems(bibles) - # set the default value - bible = Settings().value(self.settings_section + '/advanced bible') - if bible in bibles: - find_and_set_in_combo_box(self.advancedVersionComboBox, bible) - self.initialise_advanced_bible(str(bible)) - elif bibles: - self.initialise_advanced_bible(bibles[0]) - bible = Settings().value(self.settings_section + '/quick bible') - find_and_set_in_combo_box(self.quickVersionComboBox, bible) + def populate_bible_combo_boxes(self): + """ + Populate the bible combo boxes with the list of bibles that have been loaded - def reload_bibles(self, process=False): + :return: None + """ + log.debug('Loading Bibles') + self.version_combo_box.clear() + self.second_combo_box.clear() + self.second_combo_box.addItem('', None) + # Get all bibles and sort the list. + bibles = self.plugin.manager.get_bibles() + bibles = [(_f, bibles[_f]) for _f in bibles if _f] + bibles.sort(key=lambda k: get_locale_key(k[0])) + for bible in bibles: + self.version_combo_box.addItem(bible[0], bible[1]) + self.second_combo_box.addItem(bible[0], bible[1]) + # set the default value + bible = Settings().value('{settings_section}/primary bible'.format(settings_section=self.settings_section)) + find_and_set_in_combo_box(self.version_combo_box, bible) + + def reload_bibles(self): + """ + Reload the bibles and update the combo boxes + + :return: None + """ log.debug('Reloading Bibles') self.plugin.manager.reload_bibles() - self.load_bibles() - # If called from first time wizard re-run, process any new bibles. - if process: - self.plugin.app_startup() - self.update_auto_completer() + self.populate_bible_combo_boxes() - def initialise_advanced_bible(self, bible, last_book_id=None): + def get_common_books(self, first_bible, second_bible=None): + """ + Return a list of common books between two bibles. + + :param first_bible: The first bible (BibleDB) + :param second_bible: The second bible. (Optional, BibleDB + :return: A list of common books between the two bibles. Or if only one bible is supplied a list of that bibles + books (list of Book objects) + """ + if not second_bible: + return first_bible.get_books() + book_data = [] + for book in first_bible.get_books(): + for second_book in second_bible.get_books(): + if book.book_reference_id == second_book.book_reference_id: + book_data.append(book) + return book_data + + def initialise_advanced_bible(self, last_book=None): """ This initialises the given bible, which means that its book names and their chapter numbers is added to the - combo boxes on the 'Advanced Search' Tab. This is not of any importance of the 'Quick Search' Tab. + combo boxes on the 'Select' Tab. This is not of any importance of the 'Search' Tab. - :param bible: The bible to initialise (unicode). :param last_book_id: The "book reference id" of the book which is chosen at the moment. (int) + :return: None """ - log.debug('initialise_advanced_bible {bible}, {ref}'.format(bible=bible, ref=last_book_id)) - book_data = self.plugin.manager.get_books(bible) - second_bible = self.advancedSecondComboBox.currentText() - if second_bible != '': - second_book_data = self.plugin.manager.get_books(second_bible) - book_data_temp = [] - for book in book_data: - for second_book in second_book_data: - if book['book_reference_id'] == second_book['book_reference_id']: - book_data_temp.append(book) - book_data = book_data_temp - self.advanced_book_combo_box.clear() - first = True - initialise_chapter_verse = False - language_selection = self.plugin.manager.get_language_selection(bible) - book_names = BibleStrings().BookNames + log.debug('initialise_advanced_bible {bible}, {ref}'.format(bible=self.bible, ref=last_book)) + self.select_book_combo_box.clear() + if self.bible is None: + return + book_data = self.get_common_books(self.bible, self.second_bible) + language_selection = self.plugin.manager.get_language_selection(self.bible.name) + self.select_book_combo_box.model().setDynamicSortFilter(False) for book in book_data: - row = self.advanced_book_combo_box.count() - if language_selection == LanguageSelection.Bible: - self.advanced_book_combo_box.addItem(book['name']) - elif language_selection == LanguageSelection.Application: - data = BiblesResourcesDB.get_book_by_id(book['book_reference_id']) - self.advanced_book_combo_box.addItem(book_names[data['abbreviation']]) - elif language_selection == LanguageSelection.English: - data = BiblesResourcesDB.get_book_by_id(book['book_reference_id']) - self.advanced_book_combo_box.addItem(data['name']) - self.advanced_book_combo_box.setItemData(row, book['book_reference_id']) - if first: - first = False - first_book = book - initialise_chapter_verse = True - if last_book_id and last_book_id == int(book['book_reference_id']): - index = self.advanced_book_combo_box.findData(book['book_reference_id']) - if index == -1: - # Not Found. - index = 0 - self.advanced_book_combo_box.setCurrentIndex(index) - initialise_chapter_verse = False - if initialise_chapter_verse: - self.initialise_chapter_verse(bible, first_book['name'], first_book['book_reference_id']) - - def initialise_chapter_verse(self, bible, book, book_ref_id): - log.debug('initialise_chapter_verse {bible}, {book}, {ref}'.format(bible=bible, book=book, ref=book_ref_id)) - book = self.plugin.manager.get_book_by_id(bible, book_ref_id) - self.chapter_count = self.plugin.manager.get_chapter_count(bible, book) - verse_count = self.plugin.manager.get_verse_count_by_book_ref_id(bible, book_ref_id, 1) - if verse_count == 0: - self.advancedSearchButton.setEnabled(False) - log.warning('Not enough chapters in %s', book_ref_id) - critical_error_message_box(message=translate('BiblesPlugin.MediaItem', 'Bible not fully loaded.')) - else: - self.advancedSearchButton.setEnabled(True) - self.adjust_combo_box(1, self.chapter_count, self.advanced_from_chapter) - self.adjust_combo_box(1, self.chapter_count, self.advanced_to_chapter) - self.adjust_combo_box(1, verse_count, self.advanced_from_verse) - self.adjust_combo_box(1, verse_count, self.advanced_to_verse) + self.select_book_combo_box.addItem(book.get_name(language_selection), book.book_reference_id) + self.select_book_combo_box.model().setDynamicSortFilter(True) + if last_book: + index = self.select_book_combo_box.findData(last_book) + self.select_book_combo_box.setCurrentIndex(index if index != -1 else 0) + self.on_advanced_book_combo_box() 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 reference or combined search, in text search the completion list is removed. + + :return: None """ - log.debug('update_auto_completer') - # 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' (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: - book_data = bibles[bible].get_books() - second_bible = self.quickSecondComboBox.currentText() - if second_bible != '': - second_book_data = bibles[second_bible].get_books() - book_data_temp = [] - for book in book_data: - for second_book in second_book_data: - if book.book_reference_id == second_book.book_reference_id: - book_data_temp.append(book) - book_data = book_data_temp - language_selection = self.plugin.manager.get_language_selection(bible) - if language_selection == LanguageSelection.Bible: - books = [book.name + ' ' for book in book_data] - elif language_selection == LanguageSelection.Application: - book_names = BibleStrings().BookNames - for book in book_data: - data = BiblesResourcesDB.get_book_by_id(book.book_reference_id) - books.append(str(book_names[data['abbreviation']]) + ' ') - elif language_selection == LanguageSelection.English: - for book in book_data: - data = BiblesResourcesDB.get_book_by_id(book.book_reference_id) - books.append(data['name'] + ' ') + if self.search_edit.current_search_type() is not BibleSearch.Text: + if self.bible: + book_data = self.get_common_books(self.bible, self.second_bible) + language_selection = self.plugin.manager.get_language_selection(self.bible.name) + books = [book.get_name(language_selection) for book in book_data] books.sort(key=get_locale_key) - set_case_insensitive_completer(books, self.quick_search_edit) - - def on_second_bible_combobox_index_changed(self, selection): - """ - Activate the style combobox only when no second bible is selected - """ - if selection == 0: - self.quickStyleComboBox.setEnabled(True) - self.advancedStyleComboBox.setEnabled(True) - else: - self.quickStyleComboBox.setEnabled(False) - self.advancedStyleComboBox.setEnabled(False) + set_case_insensitive_completer(books, self.search_edit) def on_import_click(self): + """ + Create, if not already, the `BibleImportForm` and execute it + + :return: None + """ if not hasattr(self, 'import_wizard'): self.import_wizard = BibleImportForm(self, self.plugin.manager, self.plugin) # If the import was not cancelled then reload. @@ -493,141 +383,199 @@ class BibleMediaItem(MediaManagerItem): self.reload_bibles() def on_edit_click(self): - if self.quickTab.isVisible(): - bible = self.quickVersionComboBox.currentText() - elif self.advancedTab.isVisible(): - bible = self.advancedVersionComboBox.currentText() - if bible: + """ + Load the EditBibleForm and reload the bibles if the user accepts it + + :return: None + """ + if self.bible: self.edit_bible_form = EditBibleForm(self, self.main_window, self.plugin.manager) - self.edit_bible_form.load_bible(bible) + self.edit_bible_form.load_bible(self.bible.name) if self.edit_bible_form.exec(): self.reload_bibles() def on_delete_click(self): """ - When the delete button is pressed + Confirm that the user wants to delete the main bible + + :return: None """ - bible = None - if self.quickTab.isVisible(): - bible = self.quickVersionComboBox.currentText() - elif self.advancedTab.isVisible(): - bible = self.advancedVersionComboBox.currentText() - if bible: + if self.bible: if QtWidgets.QMessageBox.question( - self, UiStrings().ConfirmDelete, - translate('BiblesPlugin.MediaItem', - 'Are you sure you want to completely delete "{bible}" Bible ' - 'from OpenLP?\n\nYou will need to re-import this Bible to use it ' - 'again.').format(bible=bible), - QtWidgets.QMessageBox.StandardButtons(QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No), - QtWidgets.QMessageBox.Yes) == QtWidgets.QMessageBox.No: + self, UiStrings().ConfirmDelete, + translate('BiblesPlugin.MediaItem', + 'Are you sure you want to completely delete "{bible}" Bible from OpenLP?\n\n' + 'You will need to re-import this Bible to use it again.').format(bible=self.bible.name), + defaultButton=QtWidgets.QMessageBox.No) == QtWidgets.QMessageBox.No: return - self.plugin.manager.delete_bible(bible) + self.plugin.manager.delete_bible(self.bible.name) self.reload_bibles() def on_search_tab_bar_current_changed(self, index): - if index == 0: - self.advancedTab.setVisible(False) - self.quickTab.setVisible(True) - self.quick_search_edit.setFocus() + """ + Show the selected tab and set focus to it + + :param index: The tab selected (int) + :return: None + """ + search_tab = index == 0 + self.search_tab.setVisible(search_tab) + self.select_tab.setVisible(not search_tab) + self.on_focus() + + def on_book_order_button_toggled(self, checked): + """ + Change the sort order of the book names + + :param checked: Indicates if the button is checked or not (Bool) + :return: None + """ + if checked: + self.select_book_combo_box.model().sort(0) else: - self.quickTab.setVisible(False) - self.advancedTab.setVisible(True) - self.advanced_book_combo_box.setFocus() + # -1 Removes the sorting, and returns the items to the order they were added in + self.select_book_combo_box.model().sort(-1) def on_clear_button_clicked(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.quick_search_edit.clear() - self.quick_search_edit.setFocus() + """ + Clear the list_view and the search_edit - def on_advanced_clear_button_clicked(self): - # The same as the on_clear_button_clicked, but gives focus to Book name field in "Select" (advanced). + :return: None + """ self.list_view.clear() - self.advanced_book_combo_box.setFocus() + self.search_edit.clear() + self.on_focus() def on_lock_button_toggled(self, checked): """ Toggle the lock button, if Search tab is used, set focus to search field. - :param checked: The state of the toggle button. bool + + :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) - if self.quickTab.isVisible(): - self.quick_search_edit.setFocus() - def on_quick_style_combo_box_changed(self): - self.settings.layout_style = self.quickStyleComboBox.currentIndex() - self.advancedStyleComboBox.setCurrentIndex(self.settings.layout_style) - self.settings.layout_style_combo_box.setCurrentIndex(self.settings.layout_style) - Settings().setValue(self.settings_section + '/verse layout style', self.settings.layout_style) + def on_style_combo_box_index_changed(self, index): + """ + Change the layout style and save the setting - def on_advanced_style_combo_box_changed(self): - self.settings.layout_style = self.advancedStyleComboBox.currentIndex() - self.quickStyleComboBox.setCurrentIndex(self.settings.layout_style) - self.settings.layout_style_combo_box.setCurrentIndex(self.settings.layout_style) - Settings().setValue(self.settings_section + '/verse layout style', self.settings.layout_style) + :param index: The index of the current item in the combobox (int) + :return: None + """ + # TODO: Change layout_style to a property + self.settings.layout_style = index + self.settings.layout_style_combo_box.setCurrentIndex(index) + Settings().setValue('{section}/verse layout style'.format(section=self.settings_section), index) - def on_advanced_version_combo_box(self): - Settings().setValue(self.settings_section + '/advanced bible', self.advancedVersionComboBox.currentText()) - self.initialise_advanced_bible( - self.advancedVersionComboBox.currentText(), - self.advanced_book_combo_box.itemData(int(self.advanced_book_combo_box.currentIndex()))) + def on_version_combo_box_index_changed(self): + """ + Update the main bible and save it to settings - def on_advanced_second_combo_box(self): - self.initialise_advanced_bible( - self.advancedVersionComboBox.currentText(), - self.advanced_book_combo_box.itemData(int(self.advanced_book_combo_box.currentIndex()))) + :return: None + """ + self.bible = self.version_combo_box.currentData() + if self.bible is not None: + Settings().setValue('{section}/primary bible'.format(section=self.settings_section), self.bible.name) + self.initialise_advanced_bible(self.select_book_combo_box.currentData()) + + def on_second_combo_box_index_changed(self, selection): + """ + Update the second bible. If changing from single to dual bible modes as if the user wants to clear the search + results, if not revert to the previously selected bible + + :return: None + """ + new_selection = self.second_combo_box.currentData() + if self.list_view.count(): + # 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?'), + parent=self, question=True) == QtWidgets.QMessageBox.Yes: + self.list_view.clear(override_lock=True) + else: + self.second_combo_box.setCurrentIndex(self.second_combo_box.findData(self.second_bible)) + return + self.second_bible = new_selection + if new_selection == None: + self.style_combo_box.setEnabled(True) + else: + self.style_combo_box.setEnabled(False) + self.initialise_advanced_bible(self.select_book_combo_box.currentData()) def on_advanced_book_combo_box(self): - item = int(self.advanced_book_combo_box.currentIndex()) - self.initialise_chapter_verse( - self.advancedVersionComboBox.currentText(), - self.advanced_book_combo_box.currentText(), - self.advanced_book_combo_box.itemData(item)) + """ + Update the verse selection boxes - def on_advanced_from_verse(self): - chapter_from = int(self.advanced_from_chapter.currentText()) - chapter_to = int(self.advanced_to_chapter.currentText()) + :return: None + """ + book_ref_id = self.select_book_combo_box.currentData() + book = self.plugin.manager.get_book_by_id(self.bible.name, book_ref_id) + self.chapter_count = self.plugin.manager.get_chapter_count(self.bible.name, book) + verse_count = self.plugin.manager.get_verse_count_by_book_ref_id(self.bible.name, book_ref_id, 1) + if verse_count == 0: + self.search_button.setEnabled(False) + 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) + 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) + self.adjust_combo_box(1, verse_count, self.to_verse) + + def on_from_chapter_activated(self): + """ + Update the verse selection boxes + + :return: None + """ + book_ref_id = self.select_book_combo_box.currentData() + chapter_from = self.from_chapter.currentData() + chapter_to = self.to_chapter.currentData() + verse_count = self.plugin.manager.get_verse_count_by_book_ref_id(self.bible.name, book_ref_id, chapter_from) + self.adjust_combo_box(1, verse_count, self.from_verse) + if chapter_from >= chapter_to: + self.adjust_combo_box(1, verse_count, self.to_verse, chapter_from == chapter_to) + self.adjust_combo_box(chapter_from, self.chapter_count, self.to_chapter, chapter_from < chapter_to) + + def on_from_verse(self): + """ + Update the verse selection boxes + + :return: None + """ + chapter_from = self.from_chapter.currentData() + chapter_to = self.to_chapter.currentData() if chapter_from == chapter_to: - bible = self.advancedVersionComboBox.currentText() - book_ref_id = self.advanced_book_combo_box.itemData(int(self.advanced_book_combo_box.currentIndex())) - verse_from = int(self.advanced_from_verse.currentText()) - verse_count = self.plugin.manager.get_verse_count_by_book_ref_id(bible, book_ref_id, chapter_to) - self.adjust_combo_box(verse_from, verse_count, self.advanced_to_verse, True) + book_ref_id = self.select_book_combo_box.currentData() + verse_from = self.from_verse.currentData() + verse_count = self.plugin.manager.get_verse_count_by_book_ref_id(self.bible.name, book_ref_id, chapter_to) + self.adjust_combo_box(verse_from, verse_count, self.to_verse, True) - def on_advanced_to_chapter(self): - bible = self.advancedVersionComboBox.currentText() - book_ref_id = self.advanced_book_combo_box.itemData(int(self.advanced_book_combo_box.currentIndex())) - chapter_from = int(self.advanced_from_chapter.currentText()) - chapter_to = int(self.advanced_to_chapter.currentText()) - verse_from = int(self.advanced_from_verse.currentText()) - verse_to = int(self.advanced_to_verse.currentText()) - verse_count = self.plugin.manager.get_verse_count_by_book_ref_id(bible, book_ref_id, chapter_to) + def on_to_chapter(self): + """ + Update the verse selection boxes + + :return: None + """ + book_ref_id = self.select_book_combo_box.currentData() + chapter_from = self.from_chapter.currentData() + chapter_to = self.to_chapter.currentData() + verse_from = self.from_verse.currentData() + verse_to = self.to_verse.currentData() + verse_count = self.plugin.manager.get_verse_count_by_book_ref_id(self.bible.name, book_ref_id, chapter_to) if chapter_from == chapter_to and verse_from > verse_to: - self.adjust_combo_box(verse_from, verse_count, self.advanced_to_verse) + self.adjust_combo_box(verse_from, verse_count, self.to_verse) else: - self.adjust_combo_box(1, verse_count, self.advanced_to_verse) - - def on_advanced_from_chapter(self): - bible = self.advancedVersionComboBox.currentText() - book_ref_id = self.advanced_book_combo_box.itemData( - int(self.advanced_book_combo_box.currentIndex())) - chapter_from = int(self.advanced_from_chapter.currentText()) - chapter_to = int(self.advanced_to_chapter.currentText()) - verse_count = self.plugin.manager.get_verse_count_by_book_ref_id(bible, book_ref_id, chapter_from) - self.adjust_combo_box(1, verse_count, self.advanced_from_verse) - if chapter_from > chapter_to: - self.adjust_combo_box(1, verse_count, self.advanced_to_verse) - self.adjust_combo_box(chapter_from, self.chapter_count, self.advanced_to_chapter) - elif chapter_from == chapter_to: - self.adjust_combo_box(chapter_from, self.chapter_count, self.advanced_to_chapter) - self.adjust_combo_box(1, verse_count, self.advanced_to_verse, True) - else: - self.adjust_combo_box(chapter_from, self.chapter_count, self.advanced_to_chapter, True) + self.adjust_combo_box(1, verse_count, self.to_verse) def adjust_combo_box(self, range_from, range_to, combo, restore=False): """ @@ -640,380 +588,196 @@ class BibleMediaItem(MediaManagerItem): """ log.debug('adjust_combo_box {box}, {start}, {end}'.format(box=combo, start=range_from, end=range_to)) if restore: - old_text = combo.currentText() + old_selection = combo.currentData() combo.clear() - combo.addItems(list(map(str, list(range(range_from, range_to + 1))))) - if restore and combo.findText(old_text) != -1: - combo.setCurrentIndex(combo.findText(old_text)) + for item in range(range_from, range_to + 1): + combo.addItem(str(item), item) + if restore: + index = combo.findData(old_selection) + combo.setCurrentIndex(index if index != -1 else 0) - def on_advanced_search_button(self): + def on_search_button_clicked(self): """ - Does an advanced search and saves the search results. + Call the correct search function depending on which tab the user is using + + :return: None """ - log.debug('Advanced Search Button clicked') - self.advancedSearchButton.setEnabled(False) - self.application.process_events() - bible = self.advancedVersionComboBox.currentText() - second_bible = self.advancedSecondComboBox.currentText() - book = self.advanced_book_combo_box.currentText() - book_ref_id = self.advanced_book_combo_box.itemData(int(self.advanced_book_combo_box.currentIndex())) - chapter_from = self.advanced_from_chapter.currentText() - chapter_to = self.advanced_to_chapter.currentText() - verse_from = self.advanced_from_verse.currentText() - verse_to = self.advanced_to_verse.currentText() - verse_separator = get_reference_separator('sep_v_display') - range_separator = get_reference_separator('sep_r_display') - verse_range = chapter_from + verse_separator + verse_from + range_separator + chapter_to + \ - verse_separator + verse_to - verse_text = '{book} {verse}'.format(book=book, verse=verse_range) + if not self.bible: + self.main_window.information_message(UiStrings().BibleNoBiblesTitle, UiStrings().BibleNoBibles) + return + self.search_button.setEnabled(False) self.application.set_busy_cursor() - self.search_results = self.plugin.manager.get_verses(bible, verse_text, book_ref_id) - if second_bible: - self.second_search_results = self.plugin.manager.get_verses(second_bible, verse_text, book_ref_id) - if not self.advancedLockButton.isChecked(): - self.list_view.clear() - if self.list_view.count() != 0: - self.__check_second_bible(bible, second_bible) - elif self.search_results: - self.display_results(bible, second_bible) - self.advancedSearchButton.setEnabled(True) + self.application.process_events() + if self.search_tab.isVisible(): + self.text_search() + elif self.select_tab.isVisible(): + self.select_search() + self.search_button.setEnabled(True) self.application.set_normal_cursor() - def on_quick_reference_search(self): + def select_search(self): + """ + Preform a search using the passage selected on the `Select` tab + + :return: None + """ + verse_range = self.plugin.manager.process_verse_range( + self.select_book_combo_box.currentData(), self.from_chapter.currentData(), self.from_verse.currentData(), + self.to_chapter.currentData(), self.to_verse.currentData()) + self.search_results = self.plugin.manager.get_verses(self.bible.name, verse_range, False) + if self.second_bible: + self.second_search_results = self.plugin.manager.get_verses(self.second_bible.name, verse_range, False) + self.display_results() + + def text_reference_search(self, search_text): """ 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) + This search is called on def text_search by Reference and Combined Searches. - def on_quick_text_search(self): + :return: None + """ + 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): """ We are doing a 'Text Search'. - This search is called on def on_quick_search_button by Quick Text and Combined Searches. + This search is called on def text_search by 'Search' 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. + self.search_results = self.plugin.manager.verse_search(self.bible.name, text) + if self.second_bible and self.search_results: + filtered_search_results = [] + not_found_count = 0 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. + second_verse = self.second_bible.get_verses( + [(verse.book.book_reference_id, verse.chapter, verse.verse, verse.verse)], False) + if second_verse: + filtered_search_results.append(verse) + self.second_search_results += second_verse + else: + log.debug('Verse "{name} {chapter:d}:{verse:d}" not found in Second Bible "{bible_name}"'.format( + name=verse.book.name, chapter=verse.chapter, + 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: 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) + translate('BiblesPlugin.MediaItem', 'Verses not found'), + translate('BiblesPlugin.MediaItem', + 'The second Bible "{second_name}" does not contain all the verses that are in the main ' + 'Bible "{name}".\nOnly verses found in both Bibles will be shown.\n\n' + '{count:d} verses have not been included in the results.') + .format(second_name=self.second_bible.name, name=self.bible.name, count=not_found_count)) + self.display_results() - def on_quick_text_search_while_typing(self): + def text_search(self, search_while_type=False): """ - 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): - """ - This triggers the proper Quick search based on which search type is used. + This triggers the proper 'Search' 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) - self.application.process_events() - bible = self.quickVersionComboBox.currentText() - 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'. (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')} + log.debug('text_search called') + text = self.search_edit.text() + if text == '': + self.list_view.clear() + return + self.list_view.clear(search_while_typing=search_while_type) + 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: self.main_window.information_message( translate('BiblesPlugin.BibleManager', 'Scripture Reference Error'), - translate('BiblesPlugin.BibleManager', 'OpenLP couldn’t find anything ' - 'with your search.

' - 'Please make sure that your reference follows ' - 'one of these patterns:


%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', - 'OpenLP couldn’t find anything with your' - ' search.

If you tried to search' - ' with Scripture Reference, please make
sure' - ' that your reference follows one of these' - ' patterns:

%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: - self.__check_second_bible(bible, second_bible) - elif self.search_results: - self.display_results(bible, second_bible) - self.quickSearchButton.setEnabled(True) - self.application.set_normal_cursor() + translate('BiblesPlugin.BibleManager', + 'The reference you typed is invalid!

' + 'Please make sure that your reference follows one of these patterns:


%s') + % UiStrings().BibleScriptureError % get_reference_separators()) + elif self.search_edit.current_search_type() == BibleSearch.Combined and get_reference_match('full').match(text): + # Valid reference found. Do reference search. + 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): + self.on_text_search(text) + self.display_results() - 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.application.set_normal_cursor() - def on_search_text_edit_changed(self): + def on_search_edit_text_changed(self): """ - If search automatically while typing is enabled, perform the search and list results when conditions are met. + If 'search_as_you_type' is enabled, start a timer when the search_edit emits a textChanged signal. This is to + prevent overloading the system by submitting too many search requests in a short space of time. + + :return: None """ 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() - else: - if limit == 3 and (len(text) < limit or len(count_space_digit_reference) == 0): - if not self.quickLockButton.isChecked(): - self.list_view.clear() - elif limit == 8 and (len(text) < limit or len(count_two_chars_text) < 2): - if not self.quickLockButton.isChecked(): - self.list_view.clear(search_while_typing=True) - 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) + if not self.search_timer.isActive(): + self.search_timer.start() - def display_results(self, bible, second_bible=''): + def on_search_timer_timeout(self): """ - Displays the search results in the media manager. All data needed for further action is saved for/in each row. + Perform a search when the search timer timeouts. The search timer is used for 'search_as_you_type' so that we + don't overload the system buy submitting too many search requests in a short space of time. + + :return: None """ - items = self.build_display_results(bible, second_bible, self.search_results) - for bible_verse in items: - self.list_view.addItem(bible_verse) + self.text_search(True) + + def display_results(self): + """ + Add the search results to the media manager list. + + :return: None + """ + self.list_view.clear() + 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.search_results = {} - self.second_search_results = {} + self.search_results = [] + self.second_search_results = [] def build_display_results(self, bible, second_bible, search_results): """ Displays the search results in the media manager. All data needed for further action is saved for/in each row. """ - verse_separator = get_reference_separator('sep_v_display') - version = self.plugin.manager.get_meta_data(bible, 'name').value - copyright = self.plugin.manager.get_meta_data(bible, 'copyright').value - permissions = self.plugin.manager.get_meta_data(bible, 'permissions').value + verse_separator = get_reference_separators()['verse'] + version = self.plugin.manager.get_meta_data(self.bible.name, 'name').value + copyright = self.plugin.manager.get_meta_data(self.bible.name, 'copyright').value + permissions = self.plugin.manager.get_meta_data(self.bible.name, 'permissions').value + second_name = '' second_version = '' second_copyright = '' second_permissions = '' if second_bible: - second_version = self.plugin.manager.get_meta_data(second_bible, 'name').value - second_copyright = self.plugin.manager.get_meta_data(second_bible, 'copyright').value - second_permissions = self.plugin.manager.get_meta_data(second_bible, 'permissions').value + second_name = second_bible.name + second_version = self.plugin.manager.get_meta_data(self.second_bible.name, 'name').value + second_copyright = self.plugin.manager.get_meta_data(self.second_bible.name, 'copyright').value + second_permissions = self.plugin.manager.get_meta_data(self.second_bible.name, 'permissions').value items = [] - language_selection = self.plugin.manager.get_language_selection(bible) + language_selection = self.plugin.manager.get_language_selection(self.bible.name) for count, verse in enumerate(search_results): - book = None - if language_selection == LanguageSelection.Bible: - book = verse.book.name - elif language_selection == LanguageSelection.Application: - book_names = BibleStrings().BookNames - data = BiblesResourcesDB.get_book_by_id(verse.book.book_reference_id) - book = str(book_names[data['abbreviation']]) - elif language_selection == LanguageSelection.English: - data = BiblesResourcesDB.get_book_by_id(verse.book.book_reference_id) - book = data['name'] data = { - 'book': book, + 'book': verse.book.get_name(language_selection), 'chapter': verse.chapter, 'verse': verse.verse, - 'bible': bible, + 'bible': self.bible.name, 'version': version, 'copyright': copyright, 'permissions': permissions, 'text': verse.text, - 'second_bible': second_bible, + 'second_bible': second_name, 'second_version': second_version, 'second_copyright': second_copyright, 'second_permissions': second_permissions, 'second_text': '' } + if second_bible: try: data['second_text'] = self.second_search_results[count].text @@ -1023,20 +787,10 @@ class BibleMediaItem(MediaManagerItem): except TypeError: log.exception('The second_search_results does not have this book.') break - bible_text = ('{book} {chapter:d}{sep}{verse:d} ' - '({version1}, {version2})').format(book=book, - chapter=verse.chapter, - sep=verse_separator, - verse=verse.verse, - version1=version, - version2=second_version) + bible_text = '{book} {chapter:d}{sep}{verse:d} ({version}, {second_version})' else: - bible_text = '{book} {chapter:d}{sep}{verse:d} ({version})'.format(book=book, - chapter=verse.chapter, - sep=verse_separator, - verse=verse.verse, - version=version) - bible_verse = QtWidgets.QListWidgetItem(bible_text) + 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) return items @@ -1060,63 +814,42 @@ class BibleMediaItem(MediaManagerItem): if not items: return False bible_text = '' - old_item = None old_chapter = -1 - raw_slides = [] - raw_title = [] + raw_slides = [''] verses = VerseReferenceList() for bitem in items: - book = self._decode_qt_object(bitem, 'book') - chapter = int(self._decode_qt_object(bitem, 'chapter')) - verse = int(self._decode_qt_object(bitem, 'verse')) - bible = self._decode_qt_object(bitem, 'bible') - version = self._decode_qt_object(bitem, 'version') - copyright = self._decode_qt_object(bitem, 'copyright') - permissions = self._decode_qt_object(bitem, 'permissions') - text = self._decode_qt_object(bitem, 'text') - second_bible = self._decode_qt_object(bitem, 'second_bible') - second_version = self._decode_qt_object(bitem, 'second_version') - second_copyright = self._decode_qt_object(bitem, 'second_copyright') - second_permissions = self._decode_qt_object(bitem, 'second_permissions') - second_text = self._decode_qt_object(bitem, 'second_text') - verses.add(book, chapter, verse, version, copyright, permissions) - verse_text = self.format_verse(old_chapter, chapter, verse) - if second_bible: - bible_text = '{verse}{text1}\n\n{verse} {text2}'.format(verse=verse_text, - text1=text, - text2=second_text) + data = bitem.data(QtCore.Qt.UserRole) + verses.add( + data['book'], data['chapter'], data['verse'], data['version'], data['copyright'], data['permissions']) + verse_text = self.format_verse(old_chapter, data['chapter'], data['verse']) + # We only support 'Verse Per Slide' when using a scond bible + if data['second_bible']: + bible_text = '{data[verse]}{data[text]}\n\n{data[verse]}{data[second_text]}'.format(data=data) raw_slides.append(bible_text.rstrip()) bible_text = '' # If we are 'Verse Per Slide' then create a new slide. elif self.settings.layout_style == LayoutStyle.VersePerSlide: - bible_text = '{verse}{text}'.format(verse=verse_text, text=text) + bible_text = '{data[verse]}{data[text]}'.format(data=data) raw_slides.append(bible_text.rstrip()) bible_text = '' # If we are 'Verse Per Line' then force a new line. elif self.settings.layout_style == LayoutStyle.VersePerLine: - bible_text = '{bible}{verse}{text}\n'.format(bible=bible_text, verse=verse_text, text=text) + bible_text = '{bible} {verse}{data[text]}\n'.format(bible=bible_text, verse=verse_text, data=data) # We have to be 'Continuous'. else: - bible_text = '{bible} {verse}{text}\n'.format(bible=bible_text, verse=verse_text, text=text) + bible_text = '{bible} {verse}{data[text]}'.format(bible=bible_text, verse=verse_text, data=data) bible_text = bible_text.strip(' ') - if not old_item: - start_item = bitem - elif self.check_title(bitem, old_item): - raw_title.append(self.format_title(start_item, old_item)) - start_item = bitem - old_item = bitem - old_chapter = chapter + old_chapter = data['chapter'] # Add footer service_item.raw_footer.append(verses.format_verses()) - if second_bible: - verses.add_version(second_version, second_copyright, second_permissions) + if data['second_bible']: + verses.add_version(data['second_version'], data['second_copyright'], data['second_permissions']) service_item.raw_footer.append(verses.format_versions()) - raw_title.append(self.format_title(start_item, bitem)) # If there are no more items we check whether we have to add bible_text. if bible_text: raw_slides.append(bible_text.lstrip()) # Service Item: Capabilities - if self.settings.layout_style == LayoutStyle.Continuous and not second_bible: + if self.settings.layout_style == LayoutStyle.Continuous and not data['second_bible']: # Split the line but do not replace line breaks in renderer. service_item.add_capability(ItemCapabilities.NoLineBreaks) service_item.add_capability(ItemCapabilities.CanPreview) @@ -1126,77 +859,12 @@ class BibleMediaItem(MediaManagerItem): # Service Item: Title service_item.title = '{verse} {version}'.format(verse=verses.format_verses(), version=verses.format_versions()) # Service Item: Theme - if not self.settings.bible_theme: - service_item.theme = None - else: + if self.settings.bible_theme: service_item.theme = self.settings.bible_theme for slide in raw_slides: service_item.add_from_text(slide) return True - def format_title(self, start_bitem, old_bitem): - """ - 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 - want to add Genesis 1:1-6 as well as Daniel 2:14. - - :param start_bitem: The first item of a range. - :param old_bitem: The last item of a range. - """ - verse_separator = get_reference_separator('sep_v_display') - range_separator = get_reference_separator('sep_r_display') - old_chapter = self._decode_qt_object(old_bitem, 'chapter') - old_verse = self._decode_qt_object(old_bitem, 'verse') - start_book = self._decode_qt_object(start_bitem, 'book') - start_chapter = self._decode_qt_object(start_bitem, 'chapter') - start_verse = self._decode_qt_object(start_bitem, 'verse') - start_bible = self._decode_qt_object(start_bitem, 'bible') - start_second_bible = self._decode_qt_object(start_bitem, 'second_bible') - if start_second_bible: - bibles = '{bible1}, {bible2}'.format(bible1=start_bible, bible2=start_second_bible) - else: - bibles = start_bible - if start_chapter == old_chapter: - if start_verse == old_verse: - verse_range = start_chapter + verse_separator + start_verse - else: - verse_range = start_chapter + verse_separator + start_verse + range_separator + old_verse - else: - verse_range = start_chapter + verse_separator + start_verse + \ - range_separator + old_chapter + verse_separator + old_verse - return '{book} {verse} ({bible})'.format(book=start_book, verse=verse_range, bible=bibles) - - def check_title(self, bitem, old_bitem): - """ - This method checks if we are at the end of an verse range. If that is the case, we return True, otherwise False. - E. g. if we added Genesis 1:1-6, but the next verse is Daniel 2:14, we return True. - - :param bitem: The item we are dealing with at the moment. - :param old_bitem: The item we were previously dealing with. - """ - # Get all the necessary meta data. - book = self._decode_qt_object(bitem, 'book') - chapter = int(self._decode_qt_object(bitem, 'chapter')) - verse = int(self._decode_qt_object(bitem, 'verse')) - bible = self._decode_qt_object(bitem, 'bible') - second_bible = self._decode_qt_object(bitem, 'second_bible') - old_book = self._decode_qt_object(old_bitem, 'book') - old_chapter = int(self._decode_qt_object(old_bitem, 'chapter')) - old_verse = int(self._decode_qt_object(old_bitem, 'verse')) - old_bible = self._decode_qt_object(old_bitem, 'bible') - old_second_bible = self._decode_qt_object(old_bitem, 'second_bible') - if old_bible != bible or old_second_bible != second_bible or old_book != book: - # The bible, second bible or book has changed. - return True - elif old_verse + 1 != verse and old_chapter == chapter: - # We are still in the same chapter, but a verse has been skipped. - return True - elif old_chapter + 1 == chapter and (verse != 1 or old_verse != - self.plugin.manager.get_verse_count(old_bible, old_book, old_chapter)): - # We are in the following chapter, but the last verse was not the last verse of the chapter or the current - # verse is not the first one of the chapter. - return True - return False - def format_verse(self, old_chapter, chapter, verse): """ Formats and returns the text, each verse starts with, for the given chapter and verse. The text is either @@ -1207,28 +875,29 @@ class BibleMediaItem(MediaManagerItem): :param old_chapter: The previous verse's chapter number (int). :param chapter: The chapter number (int). :param verse: The verse number (int). + :return: An empty or formatted string """ - verse_separator = get_reference_separator('sep_v_display') if not self.settings.is_verse_number_visible: return '' + verse_separator = get_reference_separators()['verse'] if not self.settings.show_new_chapters or old_chapter != chapter: - verse_text = str(chapter) + verse_separator + str(verse) + verse_text = '{chapter}{sep}{verse}'.format(chapter=chapter, sep=verse_separator, verse=verse) else: - verse_text = str(verse) - if self.settings.display_style == DisplayStyle.Round: - return '{{su}}({verse}){{/su}} '.format(verse=verse_text) - if self.settings.display_style == DisplayStyle.Curly: - return '{{su}}{{{verse}}}{{/su}} '.format(verse=verse_text) - if self.settings.display_style == DisplayStyle.Square: - return '{{su}}[{verse}]{{/su}} '.format(verse=verse_text) - return '{{su}}{verse}{{/su}} '.format(verse=verse_text) + verse_text = verse + bracket = { + DisplayStyle.NoBrackets: ('', ''), + DisplayStyle.Round: ('(', ')'), + DisplayStyle.Curly: ('{', '}'), + DisplayStyle.Square: ('[', ']') + }[self.settings.display_style] + return '{{su}}{bracket[0]}{verse_text}{bracket[1]}{{/su}} '.format(verse_text=verse_text, bracket=bracket) def search(self, string, showError): """ Search for some Bible verses (by reference). """ - bible = self.quickVersionComboBox.currentText() - search_results = self.plugin.manager.get_verses(bible, string, False, showError) + 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: verse_text = ' '.join([verse.text for verse in search_results]) return [[string, verse_text]] @@ -1238,7 +907,6 @@ class BibleMediaItem(MediaManagerItem): """ Create a media item from an item id. """ - bible = self.quickVersionComboBox.currentText() - search_results = self.plugin.manager.get_verses(bible, item_id, False) - items = self.build_display_results(bible, '', search_results) - return items + 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) From 61c76c39e61f7a45e46cd8b1b892552b7737cfa2 Mon Sep 17 00:00:00 2001 From: Philip Ridout Date: Sun, 18 Dec 2016 14:01:05 +0000 Subject: [PATCH 05/26] Tests --- .../openlp_plugins/bibles/test_mediaitem.py | 1480 +++++++++++++++-- 1 file changed, 1341 insertions(+), 139 deletions(-) diff --git a/tests/functional/openlp_plugins/bibles/test_mediaitem.py b/tests/functional/openlp_plugins/bibles/test_mediaitem.py index d4a6eee39..25107c89e 100644 --- a/tests/functional/openlp_plugins/bibles/test_mediaitem.py +++ b/tests/functional/openlp_plugins/bibles/test_mediaitem.py @@ -23,11 +23,86 @@ 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 unittest.mock import MagicMock, call, patch + +from PyQt5 import QtCore, QtWidgets + 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 + + +class TestBibleMediaItemModulefunctions(TestCase): + """ + Test the module functions in :mod:`openlp.plugins.bibles.lib.mediaitem` + """ + + def test_valid_text_search(self): + """ + Test the compiled VALID_TEXT_SEARCH regex expression + """ + # GIVEN: Some test data and some expected results + test_data = [('a a a', None), ('a ab a', None), ('a abc a', ((2, 5),)), ('aa 123 aa', ((3, 6),))] + for data, expected_result in test_data: + + # WHEN: Calling search on the compiled regex expression + result = VALID_TEXT_SEARCH.search(data) + + # THEN: The expected result should be returned + if expected_result is None: + self.assertIsNone(result, expected_result) + else: + self.assertEqual(result.regs, expected_result) + + def test_get_reference_separators(self): + """ + Test the module function get_reference_separators + """ + # GIVEN: A mocked get_reference_separator from the :mod:`openlp.plugins.bibles.lib` module + with patch('openlp.plugins.bibles.lib.mediaitem.get_reference_separator') as mocked_get_reference_separator: + + # WHEN: Calling get_reference_separators + result = get_reference_separators() + + # THEN: The result should contain the 'verse', 'range', 'list' keys and get_reference_separator should have + # been called with the expected values. + self.assertTrue(all (key in result for key in ('verse', 'range', 'list'))) + mocked_get_reference_separator.assert_has_calls( + [call('sep_v_display'), call('sep_r_display'), call('sep_l_display')]) + + def test_bible_search_enum(self): + """ + Test that the :class:`BibleSearch` class contains the expected enumerations + """ + # GIVEN: The BibleSearch class + # WHEN: Testing its attributes + # THEN: The BibleSearch class should have the following enumrations + self.assertTrue(hasattr(BibleSearch, 'Combined')) + self.assertTrue(hasattr(BibleSearch, 'Reference')) + self.assertTrue(hasattr(BibleSearch, 'Text')) + + def test_bible_media_item_subclass(self): + """ + Test that the :class:`BibleMediaItem` class is a subclass of the :class:`MediaManagerItem` class + """ + # GIVEN: The :class:`BibleMediaItem` + # WHEN: Checking if it is a subclass of MediaManagerItem + # THEN: BibleMediaItem should be a subclass of MediaManagerItem + self.assertTrue(issubclass(BibleMediaItem, MediaManagerItem)) + + def test_bible_media_item_signals(self): + """ + Test that the :class:`BibleMediaItem` class has the expected signals + """ + # GIVEN: The :class:`BibleMediaItem` + # THEN: The :class:`BibleMediaItem` should contain the following pyqtSignal's + self.assertTrue(hasattr(BibleMediaItem, 'bibles_go_live')) + self.assertTrue(hasattr(BibleMediaItem, 'bibles_add_to_service')) + self.assertTrue(isinstance(BibleMediaItem.bibles_go_live, QtCore.pyqtSignal)) + self.assertTrue(isinstance(BibleMediaItem.bibles_add_to_service, QtCore.pyqtSignal)) + class TestMediaItem(TestCase, TestMixin): """ @@ -38,189 +113,1316 @@ class TestMediaItem(TestCase, TestMixin): """ Set up the components need for all tests. """ - with patch('openlp.plugins.bibles.lib.mediaitem.MediaManagerItem._setup'),\ - patch('openlp.plugins.bibles.lib.mediaitem.BibleMediaItem.setup_item'): - self.media_item = BibleMediaItem(None, MagicMock()) - self.setup_application() - self.mocked_main_window = MagicMock() + log_patcher = patch('openlp.plugins.bibles.lib.mediaitem.log') + self.addCleanup(log_patcher.stop) + self.mocked_log = log_patcher.start() + + qtimer_patcher = patch('openlp.plugins.bibles.lib.mediaitem.QtCore.QTimer') + self.addCleanup(qtimer_patcher.stop) + self.mocked_qtimer = qtimer_patcher.start() + + self.mocked_settings_instance = MagicMock() + self.mocked_settings_instance.value.side_effect = lambda key: self.setting_values[key] + settings_patcher = patch( + 'openlp.plugins.bibles.lib.mediaitem.Settings', return_value=self.mocked_settings_instance) + self.addCleanup(settings_patcher.stop) + self.mocked_settings = settings_patcher.start() + Registry.create() + + #self.setup_application() + self.mocked_application = MagicMock() + Registry().register('application', self.mocked_application) + self.mocked_main_window = MagicMock() Registry().register('main_window', self.mocked_main_window) - def test_display_results_no_results(self): + self.mocked_plugin = MagicMock() + with patch('openlp.plugins.bibles.lib.mediaitem.build_icon'), \ + patch('openlp.plugins.bibles.lib.mediaitem.MediaManagerItem._setup'), \ + patch('openlp.plugins.bibles.lib.mediaitem.BibleMediaItem.setup_item'): + self.media_item = BibleMediaItem(None, self.mocked_plugin) + + self.media_item.settings_section = 'bibles' + + 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}) + self.mocked_book_3 = MagicMock(**{'get_name.return_value': 'Book 3', 'book_reference_id': 3}) + self.mocked_book_4 = MagicMock(**{'get_name.return_value': 'Book 4', 'book_reference_id': 4}) + + self.book_list_1 = [self.mocked_book_1, self.mocked_book_2, self.mocked_book_3] + self.book_list_2 = [self.mocked_book_2, self.mocked_book_3, self.mocked_book_4] + self.mocked_bible_1 = MagicMock(**{'get_books.return_value': self.book_list_1}) + self.mocked_bible_1.name = 'Bible 1' + self.mocked_bible_2 = MagicMock(**{'get_books.return_value': self.book_list_2}) + self.mocked_bible_2.name = 'Bible 2' + + + + def test_media_item_instance(self): """ - Test the display_results method when called with a single bible, returning no results + When creating an instance of C test that it is also an instance of + :class:`MediaManagerItem` """ + # GIVEN: An instance of :class:`BibleMediaItem` + # WEHN: Checking its class + # THEN: It should be a subclass of :class:`MediaManagerItem` + self.assertTrue(isinstance(self.media_item, MediaManagerItem)) - # GIVEN: A mocked build_display_results which returns an empty list - with patch('openlp.plugins.bibles.lib.BibleMediaItem.build_display_results', **{'return_value': []}) \ - as mocked_build_display_results: - mocked_list_view = MagicMock() - self.media_item.search_results = 'results' - self.media_item.list_view = mocked_list_view - - # WHEN: Calling display_results with a single bible version - self.media_item.display_results('NIV') - - # THEN: No items should be added to the list, and select all should have been called. - mocked_build_display_results.assert_called_once_with('NIV', '', 'results') - self.assertFalse(mocked_list_view.addItem.called) - mocked_list_view.selectAll.assert_called_once_with() - self.assertEqual(self.media_item.search_results, {}) - self.assertEqual(self.media_item.second_search_results, {}) - - def test_display_results_two_bibles_no_results(self): + def test_steup_item(self): """ - Test the display_results method when called with two bibles, returning no results + Test the setup_item method """ + # Could have tested the connection of the custom signals, however they're class vairables, and I could not find + # a way to properly test them. - # GIVEN: A mocked build_display_results which returns an empty list - with patch('openlp.plugins.bibles.lib.BibleMediaItem.build_display_results', **{'return_value': []}) \ - as mocked_build_display_results: - mocked_list_view = MagicMock() - self.media_item.search_results = 'results' - self.media_item.list_view = mocked_list_view + # GIVEN: A mocked Registry.register_function method and an instance of BibleMediaItem + with patch.object(Registry(), 'register_function') as mocked_register_function: - # WHEN: Calling display_results with two single bible versions - self.media_item.display_results('NIV', 'GNB') + # WHEN: Calling setup_itme + self.media_item.setup_item() - # THEN: build_display_results should have been called with two bible versions. - # No items should be added to the list, and select all should have been called. - mocked_build_display_results.assert_called_once_with('NIV', 'GNB', 'results') - self.assertFalse(mocked_list_view.addItem.called) - mocked_list_view.selectAll.assert_called_once_with() - self.assertEqual(self.media_item.search_results, {}) - self.assertEqual(self.media_item.second_search_results, {}) - - def test_display_results_returns_lots_of_results(self): - """ - Test the display_results method a large number of results (> 100) are returned - """ - - # GIVEN: A mocked build_display_results which returns a large list of results - long_list = list(range(100)) - with patch('openlp.plugins.bibles.lib.BibleMediaItem.build_display_results', **{'return_value': long_list})\ - as mocked_build_display_results: - mocked_list_view = MagicMock() - self.media_item.search_results = 'results' - self.media_item.list_view = mocked_list_view - - # WHEN: Calling display_results - self.media_item.display_results('NIV', 'GNB') - - # THEN: addItem should have been called 100 times, and the lsit items should not be selected. - mocked_build_display_results.assert_called_once_with('NIV', 'GNB', 'results') - self.assertEqual(mocked_list_view.addItem.call_count, 100) - mocked_list_view.selectAll.assert_called_once_with() - self.assertEqual(self.media_item.search_results, {}) - self.assertEqual(self.media_item.second_search_results, {}) + # THEN: Registry.register_function method should have been called with the reload_bibles method + mocked_register_function.assert_called_once_with('bibles_load_list', self.media_item.reload_bibles) def test_required_icons(self): """ Test that all the required icons are set properly. """ - # GIVEN: Mocked icons that need to be called. - self.media_item.has_import_icon = MagicMock() - self.media_item.has_new_icon = MagicMock() - self.media_item.has_edit_icon = MagicMock() - self.media_item.has_delete_icon = MagicMock() - self.media_item.add_to_service_item = MagicMock() - - # WHEN: self.media_item.required_icons is called + # GIVEN: An instance of :class:`MediaManagerItem` + # WHEN: required_icons is called self.media_item.required_icons() - # THEN: On windows it should return True, on other platforms False + # THEN: The correct icons should be set self.assertTrue(self.media_item.has_import_icon, 'Check that the icon is as True.') self.assertFalse(self.media_item.has_new_icon, 'Check that the icon is called as False.') self.assertTrue(self.media_item.has_edit_icon, 'Check that the icon is called as True.') self.assertTrue(self.media_item.has_delete_icon, 'Check that the icon is called as True.') self.assertFalse(self.media_item.add_to_service_item, 'Check that the icon is called as False') - def test_on_quick_search_button_general(self): + # TODO: Test add_end_header_bar + # TODO: Test setupUi + + def test_on_focus_search_tab_visible(self): """ - Test that general things, which should be called on all Quick searches are called. + Test the correct widget gets focus when the BibleMediaItem receives focus """ + # GIVEN: An instance of :class:`MediaManagerItem` and a mocked out search_tab and search_edit + self.media_item.search_tab = MagicMock(**{'isVisible.return_value': True}) + self.media_item.search_edit = MagicMock() - # 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.app.set_normal_cursor = MagicMock() + # WHEN: Calling on_focus + self.media_item.on_focus() - # WHEN: on_quick_search_button is called - self.media_item.on_quick_search_button() + # THEN: setFocus and selectAll should have been called on search_edit + self.assertEqual(self.media_item.search_edit.mock_calls, [call.setFocus(), call.selectAll()]) - # 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.app.set_normal_cursor.call_count, 'Normal cursor should had been called once') + def test_on_focus_search_tab_not_visible(self): + """ + Test the correct widget gets focus when the BibleMediaItem receives focus + """ + # GIVEN: An instance of :class:`MediaManagerItem` and a mocked out search_tab and select_book_combo_box + self.media_item.search_tab = MagicMock(**{'isVisible.return_value': False}) + self.media_item.select_book_combo_box = MagicMock() + + # WHEN: Calling on_focus + self.media_item.on_focus() + + # THEN: setFocus should have been called on select_book_combo_box + self.assertTrue(self.media_item.select_book_combo_box.setFocus.called) + + def test_config_update_show_second_bible(self): + """ + Test the config update method + """ + # GIVEN: An instance of :class:`MediaManagerItem` and mocked out settings class with known values + self.setting_values = {'bibles/second bibles': True} + self.media_item.general_bible_layout = MagicMock() + self.media_item.second_combo_box = MagicMock() + + # WHEN: Calling config_update() + self.media_item.config_update() + + # THEN: second_combo_box() should be set visible + self.media_item.second_combo_box.setVisible.assert_called_once_with(True) + + def test_config_update_hide_second_bible(self): + """ + Test the config update method + """ + # GIVEN: An instance of :class:`MediaManagerItem` and mocked out settings class with known values + self.setting_values = {'bibles/second bibles': False} + self.media_item.general_bible_layout = MagicMock() + self.media_item.second_combo_box = MagicMock() + + # WHEN: Calling config_update() + self.media_item.config_update() + + # THEN: second_combo_box() should hidden + self.media_item.second_combo_box.setVisible.assert_called_once_with(False) + + def test_initalise(self): + """ + Test the initalise method + """ + # GIVEN: An instance of :class:`MediaManagerItem` and mocked out settings class with known values + self.setting_values = {'bibles/reset to combined quick search': False} + with patch.object(self.media_item, 'populate_bible_combo_boxes'), patch.object(self.media_item, 'config_update'): + self.media_item.search_edit = MagicMock() + + # WHEN: Calling initialise() + self.media_item.initialise() + + # THEN: The search_edit search types should have been set. + self.assertTrue(self.media_item.search_edit.set_search_types.called) + self.assertFalse(self.media_item.search_edit.set_current_search_type.called) + + def test_initalise_reset_search_type(self): + """ + Test the initalise method + """ + # GIVEN: An instance of :class:`MediaManagerItem` and mocked out settings class with known values + self.setting_values = {'bibles/reset to combined quick search': True} + with patch.object(self.media_item, 'populate_bible_combo_boxes'), patch.object(self.media_item, 'config_update'): + self.media_item.search_edit = MagicMock() + + # WHEN: Calling initialise() + self.media_item.initialise() + + # THEN: The search_edit search types should have been set and that the current search type should be set to + # 'Combined' + self.assertTrue(self.media_item.search_edit.set_search_types.called) + self.media_item.search_edit.set_current_search_type.assert_called_once_with(BibleSearch.Combined) + + def test_populate_bible_combo_boxes(self): + """ + Test populate_bible_combo_boxes method + """ + # GIVEN: An instance of :class:`MediaManagerItem` and mocked out settings class with known values + bible_1 = MagicMock() + bible_2 = MagicMock() + bible_3 = MagicMock() + self.setting_values = {'bibles/primary bible': bible_2} + self.media_item.version_combo_box = MagicMock() + self.media_item.second_combo_box = MagicMock() + self.mocked_plugin.manager.get_bibles.return_value = \ + {'Bible 2': bible_2, 'Bible 1': bible_1, 'Bible 3': bible_3} + with patch('openlp.plugins.bibles.lib.mediaitem.get_locale_key', side_effect=lambda x: x), \ + patch('openlp.plugins.bibles.lib.mediaitem.find_and_set_in_combo_box'): + + # WHEN: Calling populate_bible_combo_boxes + self.media_item.populate_bible_combo_boxes() + + # THEN: The bible combo boxes should be filled with the bible names and data, in a sorted order. + self.media_item.version_combo_box.addItem.assert_has_calls( + [call('Bible 1', bible_1), call('Bible 2', bible_2), call('Bible 3', bible_3)]) + self.media_item.second_combo_box.addItem.assert_has_calls( + [call('', None), call('Bible 1', bible_1), call('Bible 2', bible_2), call('Bible 3', bible_3)]) + + def test_reload_bibles(self): + """ + Test reload_bibles + """ + # GIVEN: An instance of :class:`MediaManagerItem` and mocked out settings class with known values + with patch.object(self.media_item, 'populate_bible_combo_boxes') as mocked_populate_bible_combo_boxes: + # WHEN: Calling reload_bibles() + self.media_item.reload_bibles() + + # THEN: The manager reload_bibles method should have been called and the bible combo boxes updated + self.mocked_plugin.manager.reload_bibles.assert_called_once_with() + mocked_populate_bible_combo_boxes.assert_called_once_with() + + def test_get_common_books_no_second_book(self): + """ + Test get_common_books when called with out a second bible + """ + # GIVEN: An instance of :class:`MediaManagerItem` and a mocked first bible + # WHEN: Calling get_common_books with only one bible + result = self.media_item.get_common_books(self.mocked_bible_1) + + # THEN: The book of the bible should be returned + self.assertEqual(result, self.book_list_1) + + def test_get_common_books_second_book(self): + """ + Test get_common_books when called with a second bible + """ + # GIVEN: An instance of :class:`MediaManagerItem` and two mocked bibles with differing books + # WHEN: Calling get_common_books with two bibles + result = self.media_item.get_common_books(self.mocked_bible_1, self.mocked_bible_2) + + # THEN: Only the books contained in both bibles should be returned + self.assertEqual(result, [self.mocked_book_2, self.mocked_book_3]) + + def test_initialise_advanced_bible_no_bible(self): + """ + Test initialise_advanced_bible when there is no main bible + """ + # GIVEN: An instance of :class:`MediaManagerItem` + self.media_item.select_book_combo_box = MagicMock() + with patch.object(self.media_item, 'get_common_books') as mocked_get_common_books: + + # WHEN: Calling initialise_advanced_bible() when there is no main bible + self.media_item.bible = None + result = self.media_item.initialise_advanced_bible() + + # THEN: initialise_advanced_bible should return with put calling get_common_books + self.assertIsNone(result) + mocked_get_common_books.assert_not_called() + + def test_initialise_advanced_bible_add_books_with_last_id_found(self): + """ + Test initialise_advanced_bible when the last_id argument is supplied and it is found in the list + """ + # GIVEN: An instance of :class:`MediaManagerItem` and a mocked_book_combo_box which simulates data being found + # in the list + self.media_item.select_book_combo_box = MagicMock(**{'findData.return_value':2}) + with patch.object(self.media_item, 'get_common_books', return_value=self.book_list_1), \ + patch.object(self.media_item, 'on_advanced_book_combo_box'): + + # WHEN: Calling initialise_advanced_bible() with the last_id argument set + self.media_item.bible = MagicMock() + self.media_item.initialise_advanced_bible(10) + + # THEN: The books should be added to the combo box, and the chosen book should be reselected + self.media_item.select_book_combo_box.addItem.assert_has_calls( + [call('Book 1', 1), call('Book 2', 2), call('Book 3', 3)]) + self.media_item.select_book_combo_box.setCurrentIndex.assert_called_once_with(2) + + def test_initialise_advanced_bible_add_books_with_last_id_not_found(self): + """ + Test initialise_advanced_bible when the last_id argument is supplied and it is not found in the list + """ + # GIVEN: An instance of :class:`MediaManagerItem` and a mocked_book_combo_box which simulates data not being + # found in the list + self.media_item.select_book_combo_box = MagicMock(**{'findData.return_value':-1}) + with patch.object(self.media_item, 'get_common_books', return_value=self.book_list_1), \ + patch.object(self.media_item, 'on_advanced_book_combo_box'): + + # WHEN: Calling initialise_advanced_bible() with the last_id argument set + self.media_item.bible = MagicMock() + self.media_item.initialise_advanced_bible(10) + + # THEN: The books should be added to the combo box, and the first book should be selected + self.media_item.select_book_combo_box.addItem.assert_has_calls( + [call('Book 1', 1), call('Book 2', 2), call('Book 3', 3)]) + self.media_item.select_book_combo_box.setCurrentIndex.assert_called_once_with(0) + + def test_update_auto_completer_search_no_bible(self): + """ + Test update_auto_completer when there is no main bible selected and the search_edit type is + 'BibleSearch.Reference' + """ + # GIVEN: An instance of :class:`MediaManagerItem` and a mocked search_edit + mocked_search_edit = MagicMock(**{'current_search_type.return_value': BibleSearch.Reference}) + self.media_item.search_edit = mocked_search_edit + self.media_item.bible = None + with patch.object(self.media_item, 'get_common_books') as mocked_get_common_books, \ + patch('openlp.plugins.bibles.lib.mediaitem.set_case_insensitive_completer') \ + as mocked_set_case_insensitive_completer: + + # WHEN: Calling update_auto_completer + self.media_item.update_auto_completer() + + # THEN: get_common_books should not have been called. set_case_insensitive_completer should have been called + # with an empty list + mocked_get_common_books.assert_not_called() + mocked_set_case_insensitive_completer.assert_called_once_with([], mocked_search_edit) + + def test_update_auto_completer_search_reference_type(self): + """ + Test update_auto_completer when a main bible is selected and the search_edit type is 'BibleSearch.Reference' + """ + # GIVEN: An instance of :class:`MediaManagerItem` and a mocked search_edit + mocked_search_edit = MagicMock(**{'current_search_type.return_value': BibleSearch.Reference}) + self.media_item.search_edit = mocked_search_edit + self.media_item.bible = MagicMock() + with patch.object(self.media_item, 'get_common_books', return_value=self.book_list_1), \ + patch('openlp.plugins.bibles.lib.mediaitem.get_locale_key', side_effect=lambda x: x), \ + patch('openlp.plugins.bibles.lib.mediaitem.set_case_insensitive_completer') \ + as mocked_set_case_insensitive_completer: + + # WHEN: Calling update_auto_completer + self.media_item.update_auto_completer() + + # THEN: set_case_insensitive_completer should have been called with the names of the books in order + mocked_set_case_insensitive_completer.assert_called_once_with( + ['Book 1', 'Book 2', 'Book 3'], mocked_search_edit) + + def test_update_auto_completer_search_combined_type(self): + """ + Test update_auto_completer when a main bible is selected and the search_edit type is 'BibleSearch.Combined' + """ + # GIVEN: An instance of :class:`MediaManagerItem` and a mocked search_edit + mocked_search_edit = MagicMock(**{'current_search_type.return_value': BibleSearch.Combined}) + self.media_item.search_edit = mocked_search_edit + self.media_item.bible = MagicMock() + with patch.object(self.media_item, 'get_common_books', return_value=self.book_list_1), \ + patch('openlp.plugins.bibles.lib.mediaitem.get_locale_key', side_effect=lambda x: x), \ + patch('openlp.plugins.bibles.lib.mediaitem.set_case_insensitive_completer') \ + as mocked_set_case_insensitive_completer: + # WHEN: Calling update_auto_completer + self.media_item.update_auto_completer() + + # THEN: set_case_insensitive_completer should have been called with the names of the books in order + mocked_set_case_insensitive_completer.assert_called_once_with( + ['Book 1', 'Book 2', 'Book 3'], mocked_search_edit) + + def test_on_import_click_no_import_wizzard_attr(self): + """ + Test on_import_click when media_item does not have the `import_wizard` attribute. And the wizard was canceled. + """ + # GIVEN: An instance of :class:`MediaManagerItem` and a mocked BibleImportForm + mocked_bible_import_form_instance = MagicMock(**{'exec.return_value': False}) + with patch('openlp.plugins.bibles.lib.mediaitem.BibleImportForm', + return_value=mocked_bible_import_form_instance) as mocked_bible_import_form, \ + patch.object(self.media_item, 'reload_bibles') as mocked_reload_bibles: + + # WHEN: Calling on_import_click + self.media_item.on_import_click() + + # THEN: BibleImport wizard should have been instianted and reload_bibles should not have been called + self.assertTrue(mocked_bible_import_form.called) + self.assertFalse(mocked_reload_bibles.called) + + def test_on_import_click_wizzard_not_canceled(self): + """ + Test on_import_click when the media item has the import_wizzard attr set and wizard completes sucessfully. + """ + # GIVEN: An instance of :class:`MediaManagerItem` and a mocked import_wizard + mocked_import_wizard = MagicMock(**{'exec.return_value': True}) + self.media_item.import_wizard = mocked_import_wizard + + with patch.object(self.media_item, 'reload_bibles') as mocked_reload_bibles: + + # WHEN: Calling on_import_click + self.media_item.on_import_click() + + # THEN: BibleImport wizard should have been instianted and reload_bibles should not have been called + self.assertFalse(mocked_import_wizard.called) + self.assertTrue(mocked_reload_bibles.called) + + def test_on_edit_click_no_bible(self): + """ + Test on_edit_click when there is no main bible selected + """ + # GIVEN: An instance of :class:`MediaManagerItem` + with patch('openlp.plugins.bibles.lib.mediaitem.EditBibleForm') as mocked_edit_bible_form: + + # WHEN: A main bible is not selected and on_edit_click is called + self.media_item.bible = None + self.media_item.on_edit_click() + + # THEN: EditBibleForm should not have been instianted + self.assertFalse(mocked_edit_bible_form.called) + + def test_on_edit_click_user_cancel_edit_form(self): + """ + Test on_edit_click when the user cancels the EditBibleForm + """ + # GIVEN: An instance of :class:`MediaManagerItem` and a mocked EditBibleForm which returns False when exec is + # called + self.media_item.bible = MagicMock() + mocked_edit_bible_form_instance = MagicMock(**{'exec.return_value': False}) + with patch('openlp.plugins.bibles.lib.mediaitem.EditBibleForm', return_value=mocked_edit_bible_form_instance) \ + as mocked_edit_bible_form, \ + patch.object(self.media_item, 'reload_bibles') as mocked_reload_bibles: + + # WHEN: on_edit_click is called, and the user cancels the EditBibleForm + self.media_item.on_edit_click() + + # THEN: EditBibleForm should have been been instianted but reload_bibles should not have been called + self.assertTrue(mocked_edit_bible_form.called) + self.assertFalse(mocked_reload_bibles.called) + + def test_on_edit_click_user_accepts_edit_form(self): + """ + Test on_edit_click when the user accepts the EditBibleForm + """ + # GIVEN: An instance of :class:`MediaManagerItem` and a mocked EditBibleForm which returns True when exec is + # called + self.media_item.bible = MagicMock() + mocked_edit_bible_form_instance = MagicMock(**{'exec.return_value': True}) + with patch('openlp.plugins.bibles.lib.mediaitem.EditBibleForm', + return_value=mocked_edit_bible_form_instance) \ + as mocked_edit_bible_form, \ + patch.object(self.media_item, 'reload_bibles') as mocked_reload_bibles: + + # WHEN: on_edit_click is called, and the user accpets the EditBibleForm + self.media_item.on_edit_click() + + # THEN: EditBibleForm should have been been instianted and reload_bibles should have been called + self.assertTrue(mocked_edit_bible_form.called) + self.assertTrue(mocked_reload_bibles.called) + + def test_on_delete_click_no_bible(self): + """ + Test on_delete_click when there is no main bible selected + """ + # GIVEN: An instance of :class:`MediaManagerItem` + with patch('openlp.plugins.bibles.lib.mediaitem.QtWidgets.QMessageBox') as mocked_qmessage_box: + + # WHEN: A main bible is not selected and on_delete_click is called + self.media_item.bible = None + self.media_item.on_delete_click() + + # THEN: QMessageBox.question should not have been called + self.assertFalse(mocked_qmessage_box.question.called) + + def test_on_delete_click_response_no(self): + """ + Test on_delete_click when the user selects no from the message box + """ + # GIVEN: An instance of :class:`MediaManagerItem` and a QMessageBox which reutrns QtWidgets.QMessageBox.No + self.media_item.bible = MagicMock() + with patch('openlp.plugins.bibles.lib.mediaitem.QtWidgets.QMessageBox.question', + return_value=QtWidgets.QMessageBox.No) as mocked_qmessage_box: + + # WHEN: on_delete_click is called + self.media_item.on_delete_click() + + # THEN: QMessageBox.question should have been called, but the delete_bible should not have been called + self.assertTrue(mocked_qmessage_box.called) + self.assertFalse(self.mocked_plugin.manager.delete_bible.called) + + def test_on_delete_click_response_yes(self): + """ + Test on_delete_click when the user selects yes from the message box + """ + # GIVEN: An instance of :class:`MediaManagerItem` and a QMessageBox which reutrns QtWidgets.QMessageBox.Yes + self.media_item.bible = MagicMock() + with patch('openlp.plugins.bibles.lib.mediaitem.QtWidgets.QMessageBox.question', + return_value=QtWidgets.QMessageBox.Yes) as mocked_qmessage_box, \ + patch.object(self.media_item, 'reload_bibles'): + + # WHEN: on_delete_click is called + self.media_item.on_delete_click() + + # THEN: QMessageBox.question should and delete_bible should not have been called + self.assertTrue(mocked_qmessage_box.called) + self.assertTrue(self.mocked_plugin.manager.delete_bible.called) + + def test_on_search_tab_bar_current_changed_search_tab_selected(self): + """ + Test on_search_tab_bar_current_changed when the search_tab is selected + """ + # GIVEN: An instance of :class:`MediaManagerItem` and mocked out search_tab and select_tab + self.media_item.search_tab = MagicMock() + self.media_item.select_tab = MagicMock() + with patch.object(self.media_item, 'on_focus'): + + # WHEN: The search_tab has been selected + self.media_item.on_search_tab_bar_current_changed(0) + + # THEN: search_tab should be setVisible and select_tab should be hidder + self.media_item.search_tab.setVisible.assert_called_once_with(True) + self.media_item.select_tab.setVisible.assert_called_once_with(False) + + def test_on_search_tab_bar_current_changed_select_tab_selected(self): + """ + Test on_search_tab_bar_current_changed when the select_tab is selected + """ + # GIVEN: An instance of :class:`MediaManagerItem` and mocked out search_tab and select_tab + self.media_item.search_tab = MagicMock() + self.media_item.select_tab = MagicMock() + with patch.object(self.media_item, 'on_focus'): + + # WHEN: The select_tab has been selected + self.media_item.on_search_tab_bar_current_changed(1) + + # THEN: search_tab should be setVisible and select_tab should be hidder + self.media_item.search_tab.setVisible.assert_called_once_with(False) + self.media_item.select_tab.setVisible.assert_called_once_with(True) + + def test_on_book_order_button_toggled_checked(self): + """ + Test that 'on_book_order_button_toggled' changes the order of the book list + """ + self.media_item.select_book_combo_box = MagicMock() + + # WHEN: When the book_order_button is checked + self.media_item.on_book_order_button_toggled(True) + + # THEN: The select_book_combo_box model should have been sorted + self.media_item.select_book_combo_box.model().sort.assert_called_once_with(0) + + def test_on_book_order_button_toggled_un_checked(self): + """ + Test that 'on_book_order_button_toggled' changes the order of the book list + """ + self.media_item.select_book_combo_box = MagicMock() + + # WHEN: When the book_order_button is un-checked + self.media_item.on_book_order_button_toggled(False) + + # 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): """ - Test that the on_clear_button_clicked works properly. (Used by Bible search tab) + Test on_clear_button_clicked """ - # GIVEN: Mocked list_view, check_search_results & quick_search_edit. + # 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.quick_search_edit = MagicMock() + self.media_item.search_edit = MagicMock() + with patch.object(self.media_item, 'on_focus'): - # WHEN: on_clear_button_clicked is called - self.media_item.on_clear_button_clicked() + # WHEN: Calling on_clear_button_clicked + self.media_item.on_clear_button_clicked() - # THEN: Search result should be reset and search field should receive focus. - self.media_item.list_view.clear.assert_called_once_with(), - self.media_item.quick_search_edit.clear.assert_called_once_with(), - self.media_item.quick_search_edit.setFocus.assert_called_once_with() + # THEN: The list_view and the search_edit should be cleared + 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): """ - Test that "on_lock_button_toggled" gives focus to the right field and toggles the lock properly. + Test that "on_lock_button_toggled" toggles the lock properly. """ - # GIVEN: Mocked sender & Search edit, quickTab returning value = True on isVisible. - self.media_item.sender = MagicMock() - self.media_item.quick_search_edit = MagicMock() - self.media_item.quickTab = MagicMock(**{'isVisible.return_value': True}) - + # 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' - sender_instance_mock = MagicMock() - self.media_item.sender = MagicMock(return_value=sender_instance_mock) + mocked_sender_instance = MagicMock() + with patch.object(self.media_item, 'sender', return_value=mocked_sender_instance): - # WHEN: on_lock_button_toggled is called and checked returns = True. - self.media_item.on_lock_button_toggled(True) + # WHEN: When the lock_button is checked + self.media_item.on_lock_button_toggled(True) - # THEN: on_quick_search_edit should receive focus and Lock icon should be set. - self.media_item.quick_search_edit.setFocus.assert_called_once_with() - sender_instance_mock.setIcon.assert_called_once_with('lock icon') + # 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') def test_on_lock_button_toggled_unlock_icon(self): """ - Test that lock button unlocks properly and lock toggles properly. + Test that "on_lock_button_toggled" toggles the lock properly. """ - # GIVEN: Mocked sender & Search edit, quickTab returning value = False on isVisible. - self.media_item.sender = MagicMock() - self.media_item.quick_search_edit = MagicMock() - self.media_item.quickTab = MagicMock() - self.media_item.quickTab.isVisible = MagicMock() + # 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' - sender_instance_mock = MagicMock() - self.media_item.sender = MagicMock(return_value=sender_instance_mock) + mocked_sender_instance = MagicMock() + with patch.object(self.media_item, 'sender', return_value=mocked_sender_instance): - # WHEN: on_lock_button_toggled is called and checked returns = False. - self.media_item.on_lock_button_toggled(False) + # WHEN: When the lock_button is unchecked + self.media_item.on_lock_button_toggled(False) - # THEN: Unlock icon should be set. - sender_instance_mock.setIcon.assert_called_once_with('unlock icon') + # 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') + + def test_on_style_combo_box_changed(self): + """ + Test on_style_combo_box_index_changed + """ + # GIVEN: An instance of :class:`MediaManagerItem` a mocked media_item.settings + self.media_item.settings = MagicMock() + + # WHEN: Calling on_style_combo_box_index_changed + self.media_item.on_style_combo_box_index_changed(2) + + # THEN: The layput_style settimg should have been set + self.assertEqual(self.media_item.settings.layout_style, 2) + self.media_item.settings.layout_style_combo_box.setCurrentIndex.assert_called_once_with(2) + self.mocked_settings_instance.setValue.assert_called_once_with('bibles/verse layout style', 2) + + + def test_on_version_combo_box_index_changed_no_bible(self): + """ + Test on_version_combo_box_index_changed when there is no main bible. + """ + # GIVEN: An instance of :class:`MediaManagerItem` and mocked media_item.settings and select_book_combo_box + self.media_item.version_combo_box = MagicMock(**{'currentData.return_value': None}) + self.media_item.select_book_combo_box = MagicMock() + with patch.object(self.media_item, 'initialise_advanced_bible') as mocked_initialise_advanced_bible: + + # WHEN: Calling on_version_combo_box_index_changed + self.media_item.on_version_combo_box_index_changed() + + # THEN: The vesion should be saved to settings and the 'select tab' should be initialised + self.assertFalse(self.mocked_settings_instance.setValue.called) + self.assertTrue(self.media_item.initialise_advanced_bible.called) + + def test_on_version_combo_box_index_changed_bible_selected(self): + """ + Test on_version_combo_box_index_changed when a bible has been selected. + """ + # GIVEN: An instance of :class:`MediaManagerItem` and mocked media_item.settings and select_book_combo_box + mocked_bible_db = MagicMock() + mocked_bible_db.name='ABC' + self.media_item.version_combo_box = MagicMock(**{'currentData.return_value': mocked_bible_db}) + self.media_item.select_book_combo_box = MagicMock() + with patch.object(self.media_item, 'initialise_advanced_bible') as mocked_initialise_advanced_bible: + + # WHEN: Calling on_version_combo_box_index_changed + self.media_item.on_version_combo_box_index_changed() + + # THEN: The vesion should be saved to settings and the 'select tab' should be initialised + self.mocked_settings_instance.setValue.assert_called_once_with('bibles/primary bible', 'ABC') + self.assertTrue(self.media_item.initialise_advanced_bible.called) + + def test_on_second_combo_box_index_changed_mode_not_changed(self): + """ + Test on_second_combo_box_index_changed when the user does not change from dual mode + results and the user chooses no to the message box + """ + # GIVEN: An instance of :class:`MediaManagerItem` + 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() + with patch.object(self.media_item, 'initialise_advanced_bible') as mocked_initialise_advanced_bible, \ + patch('openlp.plugins.bibles.lib.mediaitem.critical_error_message_box') \ + as mocked_critical_error_message_box: + + # WHEN: The previously selected bible is one bible and the new selection is annother bible + self.media_item.second_bible = self.mocked_bible_1 + self.media_item.second_combo_box = MagicMock(**{'currentData.return_value': self.mocked_bible_2}) + self.media_item.on_second_combo_box_index_changed(5) + + # THEN: The new bible should now be the current bible + self.assertFalse(mocked_critical_error_message_box.called) + self.media_item.style_combo_box.setEnabled.assert_called_once_with(False) + self.assertEqual(self.media_item.second_bible, self.mocked_bible_2) + + + def test_on_second_combo_box_index_changed_single_to_dual_user_abort(self): + """ + Test on_second_combo_box_index_changed when the user changes from single to dual bible mode, there are search + results and the user chooses no to the message box + """ + # GIVEN: An instance of :class:`MediaManagerItem` + 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() + with patch.object(self.media_item, 'initialise_advanced_bible') as mocked_initialise_advanced_bible, \ + patch('openlp.plugins.bibles.lib.mediaitem.critical_error_message_box', + return_value=QtWidgets.QMessageBox.No) as mocked_critical_error_message_box: + + # WHEN: The previously selected bible is None and the new selection is a bible and the user selects yes + # to the dialog box + self.media_item.second_bible = None + 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 currently selected bible should not be channged + self.assertTrue(mocked_critical_error_message_box.called) + self.assertTrue(self.media_item.second_combo_box.setCurrentIndex.called) + self.assertFalse(self.media_item.style_combo_box.setEnabled.called) + self.assertEqual(self.media_item.second_bible, None) + + def test_on_second_combo_box_index_changed_single_to_dual(self): + """ + Test on_second_combo_box_index_changed when the user changes from single to dual bible mode, there are search + results and the user chooses yes to the message box + """ + # GIVEN: An instance of :class:`MediaManagerItem` + 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() + with patch.object(self.media_item, 'initialise_advanced_bible') as mocked_initialise_advanced_bible, \ + patch('openlp.plugins.bibles.lib.mediaitem.critical_error_message_box', + return_value=QtWidgets.QMessageBox.Yes) as mocked_critical_error_message_box: + + # WHEN: The previously selected bible is None and the new selection is a bible and the user selects yes + # to the dialog box + self.media_item.second_bible = None + 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 + 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) + + def test_on_second_combo_box_index_changed_dual_to_single(self): + """ + Test on_second_combo_box_index_changed when the user changes from dual to single bible mode, there are search + results and the user chooses yes to the message box + """ + # GIVEN: An instance of :class:`MediaManagerItem` + 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() + with patch.object(self.media_item, 'initialise_advanced_bible') as mocked_initialise_advanced_bible, \ + 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 + # to the dialog box + self.media_item.second_bible = self.mocked_bible_1 + 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 + 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) + + def test_on_advanced_book_combo_box(self): + """ + Test on_advanced_book_combo_box when the book returns 0 for the verse count. + """ + # GIVEN: An instance of :class:`MediaManagerItem` and a mocked get_verse_count_by_book_ref_id which returns 0 + self.media_item.select_book_combo_box = MagicMock(**{'currentData.return_value': 2}) + self.media_item.bible = self.mocked_bible_1 + self.mocked_plugin.manager.get_verse_count_by_book_ref_id.return_value = 0 + self.media_item.search_button = MagicMock() + with patch('openlp.plugins.bibles.lib.mediaitem.critical_error_message_box') \ + as mocked_critical_error_message_box: + + # WHEN: Calling on_advanced_book_combo_box + self.media_item.on_advanced_book_combo_box() + + # THEN: The user should be informed that the bible cannot be used and the search button should be disabled + self.mocked_plugin.manager.get_book_by_id.assert_called_once_with('Bible 1', 2) + self.media_item.search_button.setEnabled.assert_called_once_with(False) + self.assertTrue(mocked_critical_error_message_box.called) + + def test_on_advanced_book_combo_box_set_up_comboboxes(self): + """ + Test on_advanced_book_combo_box when the book returns 6 for the verse count. + """ + # GIVEN: An instance of :class:`MediaManagerItem` and a mocked get_verse_count_by_book_ref_id which returns 6 + self.media_item.from_chapter = 0 + self.media_item.to_chapter = 0 + self.media_item.from_verse = 0 + self.media_item.to_verse = 0 + self.media_item.select_book_combo_box = MagicMock(**{'currentData.return_value': 2}) + self.media_item.bible = self.mocked_bible_1 + self.mocked_plugin.manager.get_verse_count_by_book_ref_id.return_value = 6 + self.media_item.search_button = MagicMock() + with patch.object(self.media_item, 'adjust_combo_box') as mocked_adjust_combo_box: + # WHEN: Calling on_advanced_book_combo_box + self.media_item.on_advanced_book_combo_box() + + # THEN: The verse selection combobox's should be set up + self.mocked_plugin.manager.get_book_by_id.assert_called_once_with('Bible 1', 2) + self.media_item.search_button.setEnabled.assert_called_once_with(True) + self.assertEqual(mocked_adjust_combo_box.call_count, 4) + + def test_on_from_chapter_activated_invalid_to_chapter(self): + """ + Test on_from_chapter_activated when the to_chapter is less than the from_chapter + """ + # GIVEN: An instance of :class:`MediaManagerItem`, some mocked comboboxes with test data + self.media_item.chapter_count = 25 + self.media_item.bible = self.mocked_bible_1 + self.media_item.select_book_combo_box = MagicMock() + self.media_item.from_chapter = MagicMock(**{'currentData.return_value': 10}) + self.media_item.to_chapter = MagicMock(**{'currentData.return_value': 5}) + self.media_item.from_verse = MagicMock() + self.media_item.to_verse = MagicMock() + self.mocked_plugin.manager.get_verse_count_by_book_ref_id.return_value = 20 + with patch.object(self.media_item, 'adjust_combo_box') as mocked_adjust_combo_box: + + # WHEN: Calling on_from_chapter_activated + self.media_item.on_from_chapter_activated() + + # THEN: The to_verse and to_chapter comboboxes should be updated appropriately + self.assertEqual(mocked_adjust_combo_box.call_args_list, [ + call(1, 20, self.media_item.from_verse), call(1, 20, self.media_item.to_verse, False), + call(10, 25, self.media_item.to_chapter, False)]) + + def test_on_from_chapter_activated_same_chapter(self): + """ + Test on_from_chapter_activated when the to_chapter is the same as from_chapter + """ + # GIVEN: An instance of :class:`MediaManagerItem`, some mocked comboboxes with test data + self.media_item.chapter_count = 25 + self.media_item.bible = self.mocked_bible_1 + self.media_item.select_book_combo_box = MagicMock() + self.media_item.from_chapter = MagicMock(**{'currentData.return_value': 5}) + self.media_item.to_chapter = MagicMock(**{'currentData.return_value': 5}) + self.media_item.from_verse = MagicMock() + self.media_item.to_verse = MagicMock() + self.mocked_plugin.manager.get_verse_count_by_book_ref_id.return_value = 20 + with patch.object(self.media_item, 'adjust_combo_box') as mocked_adjust_combo_box: + + # WHEN: Calling on_from_chapter_activated + self.media_item.on_from_chapter_activated() + + # THEN: The to_verse and to_chapter comboboxes should be updated appropriately + self.assertEqual(mocked_adjust_combo_box.call_args_list, [ + call(1, 20, self.media_item.from_verse), call(1, 20, self.media_item.to_verse, True), + call(5, 25, self.media_item.to_chapter, False)]) + + def test_on_from_chapter_activated_lower_chapter(self): + """ + Test on_from_chapter_activated when the to_chapter is greater than the from_chapter + """ + # GIVEN: An instance of :class:`MediaManagerItem`, some mocked comboboxes with test data + self.media_item.chapter_count = 25 + self.media_item.bible = self.mocked_bible_1 + self.media_item.select_book_combo_box = MagicMock() + self.media_item.from_chapter = MagicMock(**{'currentData.return_value': 5}) + self.media_item.to_chapter = MagicMock(**{'currentData.return_value': 7}) + self.media_item.from_verse = MagicMock() + self.media_item.to_verse = MagicMock() + self.mocked_plugin.manager.get_verse_count_by_book_ref_id.return_value = 20 + with patch.object(self.media_item, 'adjust_combo_box') as mocked_adjust_combo_box: + # WHEN: Calling on_from_chapter_activated + self.media_item.on_from_chapter_activated() + + # THEN: The to_verse and to_chapter comboboxes should be updated appropriately + self.assertEqual(mocked_adjust_combo_box.call_args_list, [ + call(1, 20, self.media_item.from_verse), call(5, 25, self.media_item.to_chapter, True)]) + + def test_on_from_verse(self): + """ + Test on_from_verse when the to_chapter is not equal to the from_chapter + """ + # GIVEN: An instance of :class:`MediaManagerItem`, some mocked comboboxes with test data + self.media_item.select_book_combo_box = MagicMock() + self.media_item.from_chapter = MagicMock(**{'currentData.return_value': 2}) + self.media_item.to_chapter = MagicMock(**{'currentData.return_value': 5}) + + # WHEN: Calling on_from_verse + self.media_item.on_from_verse() + + # THEN: select_book_combo_box.currentData should nto be called + self.assertFalse(self.media_item.select_book_combo_box.currentData.called) + + def test_on_from_verse_equal(self): + """ + Test on_from_verse when the to_chapter is equal to the from_chapter + """ + # GIVEN: An instance of :class:`MediaManagerItem`, some mocked comboboxes with test data + self.media_item.bible = self.mocked_bible_1 + self.media_item.select_book_combo_box = MagicMock() + self.media_item.from_chapter = MagicMock(**{'currentData.return_value': 5}) + self.media_item.to_chapter = MagicMock(**{'currentData.return_value': 5}) + self.media_item.from_verse = MagicMock(**{'currentData.return_value': 7}) + self.media_item.to_verse = MagicMock() + self.mocked_plugin.manager.get_verse_count_by_book_ref_id.return_value = 20 + with patch.object(self.media_item, 'adjust_combo_box') as mocked_adjust_combo_box: + + # WHEN: Calling on_from_verse + self.media_item.on_from_verse() + + # THEN: The to_verse should have been updated + mocked_adjust_combo_box.assert_called_once_with(7, 20, self.media_item.to_verse, True) + + def test_on_to_chapter_same_chapter_from_greater_than(self): + """ + Test on_to_chapter when the to_chapter is equal to the from_chapter and the from_verse is greater than the + to_verse + """ + # GIVEN: An instance of :class:`MediaManagerItem`, some mocked comboboxes with test data + self.media_item.bible = self.mocked_bible_1 + self.media_item.select_book_combo_box = MagicMock() + self.media_item.from_chapter = MagicMock(**{'currentData.return_value': 5}) + self.media_item.to_chapter = MagicMock(**{'currentData.return_value': 5}) + self.media_item.from_verse = MagicMock(**{'currentData.return_value': 10}) + self.media_item.to_verse = MagicMock(**{'currentData.return_value': 7}) + self.mocked_plugin.manager.get_verse_count_by_book_ref_id.return_value = 20 + with patch.object(self.media_item, 'adjust_combo_box') as mocked_adjust_combo_box: + + # WHEN: Calling on_tp_chapter + self.media_item.on_to_chapter() + + # THEN: The to_verse should have been updated + mocked_adjust_combo_box.assert_called_once_with(10, 20, self.media_item.to_verse) + + def test_on_from_verse_chapters_not_equal(self): + """ + Test on_from_verse when the to_chapter is not equal to the from_chapter + """ + # GIVEN: An instance of :class:`MediaManagerItem`, some mocked comboboxes with test data + self.media_item.bible = self.mocked_bible_1 + self.media_item.select_book_combo_box = MagicMock() + self.media_item.from_chapter = MagicMock(**{'currentData.return_value': 7}) + self.media_item.to_chapter = MagicMock(**{'currentData.return_value': 5}) + self.media_item.from_verse = MagicMock(**{'currentData.return_value': 10}) + self.media_item.to_verse = MagicMock(**{'currentData.return_value': 7}) + self.mocked_plugin.manager.get_verse_count_by_book_ref_id.return_value = 20 + with patch.object(self.media_item, 'adjust_combo_box') as mocked_adjust_combo_box: + + # WHEN: Calling on_from_chapter_activated + self.media_item.on_to_chapter() + + # THEN: The to_verse should have been updated + mocked_adjust_combo_box.assert_called_once_with(1, 20, self.media_item.to_verse) + + def test_on_from_verse_from_verse_less_than(self): + """ + Test on_from_verse when the to_chapter is equal to the from_chapter and from_verse is less than to_verse + """ + # GIVEN: An instance of :class:`MediaManagerItem`, some mocked comboboxes with test data + self.media_item.bible = self.mocked_bible_1 + self.media_item.select_book_combo_box = MagicMock() + self.media_item.from_chapter = MagicMock(**{'currentData.return_value': 5}) + self.media_item.to_chapter = MagicMock(**{'currentData.return_value': 5}) + self.media_item.from_verse = MagicMock(**{'currentData.return_value': 6}) + self.media_item.to_verse = MagicMock(**{'currentData.return_value': 7}) + self.mocked_plugin.manager.get_verse_count_by_book_ref_id.return_value = 20 + with patch.object(self.media_item, 'adjust_combo_box') as mocked_adjust_combo_box: + + # WHEN: Calling on_from_chapter_activated + self.media_item.on_to_chapter() + + # THEN: The to_verse should have been updated + mocked_adjust_combo_box.assert_called_once_with(1, 20, self.media_item.to_verse) + + def test_adjust_combo_box_no_restore(self): + """ + Test adjust_combo_box when being used with out the restore function + """ + # GIVEN: An instance of :class:`MediaManagerItem` + mocked_combo_box = MagicMock() + + # WHEN: Calling adjust_combo_box with out setting the kwarg `restore` + self.media_item.adjust_combo_box(10, 13, mocked_combo_box) + + # THEN: The combo_box should be cleared, and new items added + mocked_combo_box.clear.assert_called_once_with() + self.assertEqual(mocked_combo_box.addItem.call_args_list, + [call('10', 10), call('11', 11), call('12', 12), call('13', 13)]) + + def test_adjust_combo_box_restore_found(self): + """ + Test adjust_combo_box when being used with out the restore function + """ + # GIVEN: An instance of :class:`MediaManagerItem`, with the 2nd item '12' selected + mocked_combo_box = MagicMock(**{'currentData.return_value': 12, 'findData.return_value': 2}) + + # WHEN: Calling adjust_combo_box with the kwarg `restore` set to True + self.media_item.adjust_combo_box(10, 13, mocked_combo_box, True) + + # THEN: The combo_box should be cleared, and new items added. Finally the previously selected item should be + # reselected + mocked_combo_box.clear.assert_called_once_with() + self.assertEqual(mocked_combo_box.addItem.call_args_list, + [call('10', 10), call('11', 11), call('12', 12), call('13', 13)]) + mocked_combo_box.setCurrentIndex.assert_called_once_with(2) + + def test_adjust_combo_box_restore_not_found(self): + """ + Test adjust_combo_box when being used with out the restore function when the selected item is not available + after the combobox has been updated + """ + # GIVEN: An instance of :class:`MediaManagerItem`, with the 2nd item '12' selected + mocked_combo_box = MagicMock(**{'currentData.return_value': 9, 'findData.return_value': -1}) + + # WHEN: Calling adjust_combo_box with the kwarg `restore` set to True + self.media_item.adjust_combo_box(10, 13, mocked_combo_box, True) + + # THEN: The combo_box should be cleared, and new items added. Finally the first item should be selected + mocked_combo_box.clear.assert_called_once_with() + self.assertEqual(mocked_combo_box.addItem.call_args_list, + [call('10', 10), call('11', 11), call('12', 12), call('13', 13)]) + mocked_combo_box.setCurrentIndex.assert_called_once_with(0) + + def test_on_search_button_no_bible(self): + """ + Test on_search_button_clicked when there is no bible selected + """ + # GIVEN: An instance of :class:`MediaManagerItem` + # WHEN calling on_search_button_clicked and there is no selected bible + self.media_item.bible = None + self.media_item.on_search_button_clicked() + + # THEN: The user should be informed that there are no bibles selected + self.assertEqual(self.mocked_main_window.information_message.call_count, 1) + + def test_on_search_button_search_tab(self): + """ + Test on_search_button_clicked when the `Search` tab is selected + """ + # GIVEN: An instance of :class:`MediaManagerItem`, and a mocked text_search method + self.media_item.bible = self.mocked_bible_1 + self.media_item.search_button = MagicMock() + self.media_item.search_tab = MagicMock(**{'isVisible.return_value': True}) + with patch.object(self.media_item, 'text_search') as mocked_text_search: + + # WHEN: Calling on_search_button_clicked and the 'Search' tab is selected + self.media_item.on_search_button_clicked() + + # THEN: The text_search method should have been called + mocked_text_search.assert_called_once_with() + + def test_on_search_button_select_tab(self): + """ + Test on_search_button_clicked when the `Select` tab is selected + """ + # GIVEN: An instance of :class:`MediaManagerItem`, and a mocked select_search method + self.media_item.bible = self.mocked_bible_1 + self.media_item.search_button = MagicMock() + self.media_item.search_tab = MagicMock(**{'isVisible.return_value': False}) + self.media_item.select_tab = MagicMock(**{'isVisible.return_value': True}) + with patch.object(self.media_item, 'select_search') as mocked_select_search: + + # WHEN: Calling on_search_button_clicked and the 'Select' tab is selected + self.media_item.on_search_button_clicked() + + # THEN: The text_search method should have been called + mocked_select_search.assert_called_once_with() + + def test_select_search_single_bible(self): + """ + Test select_search when only one bible is selected + """ + # GIVEN: An instance of :class:`MediaManagerItem` and mocked plugin.manager.get_verses + self.media_item.select_book_combo_box = MagicMock() + self.media_item.from_chapter = MagicMock() + self.media_item.from_verse = MagicMock() + self.media_item.to_chapter = MagicMock() + self.media_item.to_verse = MagicMock() + with patch.object(self.media_item, 'display_results') as mocked_display_results: + + # WHEN: Calling select_search and there is only one bible selected + self.media_item.bible = self.mocked_bible_1 + self.media_item.second_bible = None + self.media_item.select_search() + + # THEN: reference_search should only be called once + self.assertEqual(self.mocked_plugin.manager.get_verses.call_count, 1) + mocked_display_results.assert_called_once_with() + + def test_select_search_dual_bibles(self): + """ + Test select_search when two bibles are selected + """ + # GIVEN: An instance of :class:`MediaManagerItem` and mocked_reference_search + self.media_item.select_book_combo_box = MagicMock() + self.media_item.from_chapter = MagicMock() + self.media_item.from_verse = MagicMock() + self.media_item.to_chapter = MagicMock() + self.media_item.to_verse = MagicMock() + with patch.object(self.media_item, 'display_results') as mocked_display_results: + + # WHEN: Calling select_search and there are two bibles selected + self.media_item.bible = self.mocked_bible_1 + self.media_item.second_bible = self.mocked_bible_2 + self.media_item.select_search() + + # THEN: reference_search should be called twice + self.assertEqual(self.mocked_plugin.manager.get_verses.call_count, 2) + mocked_display_results.assert_called_once_with() + + def test_text_reference_search_single_bible(self): + """ + Test text_reference_search when only one bible is selected + """ + # GIVEN: An instance of :class:`MediaManagerItem` and mocked plugin.manager.get_verses + with patch.object(self.media_item, 'display_results') as mocked_display_results: + + # WHEN: Calling text_reference_search with only one bible selected + self.media_item.bible = self.mocked_bible_1 + self.media_item.second_bible = None + self.media_item.text_reference_search('Search Text') + + # THEN: reference_search should only be called once + self.assertEqual(self.mocked_plugin.manager.get_verses.call_count, 1) + mocked_display_results.assert_called_once_with() + + def text_reference_search(self, search_text, search_while_type=False): + """ + We are doing a 'Reference Search'. + This search is called on def text_search by Reference and Combined Searches. + """ + verse_refs = self.plugin.manager.parse_ref(self.bible.name, search_text) + self.search_results = self.reference_search(verse_refs, self.bible) + if self.second_bible and self.search_results: + self.second_search_results = self.reference_search(verse_refs, self.second_bible) + self.display_results() + + def test_text_reference_search_dual_bible_no_results(self): + """ + Test text_reference_search when two bible are selected, but the search of the first bible does not return any + results + """ + # GIVEN: An instance of :class:`MediaManagerItem` and mocked plugin.manager.get_verses + # WHEN: Calling text_reference_search with two bibles selected, but no results are found in the first bible + with patch.object(self.media_item, 'display_results') as mocked_display_results: + self.mocked_plugin.manager.get_verses.return_value = [] + self.media_item.bible = self.mocked_bible_1 + self.media_item.second_bible = self.mocked_bible_2 + self.media_item.text_reference_search('Search Text') + + # THEN: reference_search should only be called once + self.assertEqual(self.mocked_plugin.manager.get_verses.call_count, 1) + mocked_display_results.assert_called_once_with() + + def test_text_reference_search_dual_bible(self): + """ + Test text_reference_search when two bible are selected + """ + # GIVEN: An instance of :class:`MediaManagerItem` and mocked plugin.manager.get_verses + with patch.object(self.media_item, 'display_results') as mocked_display_results: + self.media_item.bible = self.mocked_bible_1 + self.media_item.second_bible = self.mocked_bible_2 + + # WHEN: Calling text_reference_search with two bibles selected + self.media_item.text_reference_search('Search Text') + + # THEN: reference_search should be called twice + self.assertEqual(self.mocked_plugin.manager.get_verses.call_count, 2) + mocked_display_results.assert_called_once_with() + + def test_on_text_search_single_bible(self): + """ + Test on_text_search when only one bible is selected + """ + # GIVEN: An instance of :class:`MediaManagerItem` + self.media_item.bible = self.mocked_bible_1 + self.media_item.second_bible = None + + # WHEN: Calling on_text_search and plugin.manager.verse_search returns a list of results + self.mocked_plugin.manager.verse_search.return_value = ['results', 'list'] + with patch.object(self.media_item, 'display_results') as mocked_display_results: + self.media_item.on_text_search('Search Text') + + # THEN: The search results should be the same as those returned by plugin.manager.verse_search + self.assertEqual(self.media_item.search_results, ['results', 'list']) + mocked_display_results.assert_called_once_with() + + def test_on_text_search_no_results(self): + """ + Test on_text_search when the search of the first bible does not return any results + """ + # GIVEN: An instance of :class:`MediaManagerItem` + self.media_item.bible = self.mocked_bible_1 + self.media_item.second_bible = self.mocked_bible_2 + + # WHEN: Calling on_text_search and plugin.manager.verse_search returns an empty list + self.mocked_plugin.manager.verse_search.return_value = [] + with patch.object(self.media_item, 'display_results') as mocked_display_results: + self.media_item.on_text_search('Search Text') + + # THEN: The search results should be an empty list + self.assertEqual(self.media_item.search_results, []) + mocked_display_results.assert_called_once_with() + + def test_on_text_search_all_results_in_both_books(self): + """ + Test on_text_search when all of the results from the first bible are found in the second + """ + # GIVEN: An instance of :class:`MediaManagerItem` and some test data + mocked_verse_1 = MagicMock(**{'book.book_reference_id': 1, 'chapter': 2, 'verse': 3}) + mocked_verse_1a = MagicMock(**{'book.book_reference_id': 1, 'chapter': 2, 'verse': 3}) + mocked_verse_2 = MagicMock(**{'book.book_reference_id': 4, 'chapter': 5, 'verse': 6}) + mocked_verse_2a = MagicMock(**{'book.book_reference_id': 4, 'chapter': 5, 'verse': 6}) + self.media_item.bible = self.mocked_bible_1 + self.media_item.second_bible = self.mocked_bible_2 + self.media_item.second_search_results = [] + + # WHEN: Calling on_text_search and plugin.manager.verse_search returns a list of search results + self.mocked_plugin.manager.verse_search.return_value = [mocked_verse_1, mocked_verse_2] + self.media_item.second_bible.get_verses.side_effect = [[mocked_verse_1a], [mocked_verse_2a]] + with patch.object(self.media_item, 'display_results') as mocked_display_results: + self.media_item.on_text_search('Search Text') + + # THEN: The search results for both bibles should be returned + self.assertEqual(self.media_item.search_results, [mocked_verse_1, mocked_verse_2]) + self.assertEqual(self.media_item.second_search_results, [mocked_verse_1a, mocked_verse_2a]) + self.assertFalse(self.mocked_log.debug.called) + self.assertFalse(self.mocked_main_window.information_message.called) + mocked_display_results.assert_called_once_with() + + def test_on_text_search_not_all_results_in_both_books(self): + """ + Test on_text_search when not all of the results from the first bible are found in the second + """ + # GIVEN: An instance of :class:`MediaManagerItem` and some test data + mocked_verse_1 = MagicMock(**{'book.book_reference_id': 1, 'chapter': 2, 'verse': 3}) + mocked_verse_1a = MagicMock(**{'book.book_reference_id': 1, 'chapter': 2, 'verse': 3}) + mocked_verse_2 = MagicMock(**{'book.book_reference_id': 4, 'chapter': 5, 'verse': 6}) + mocked_verse_3 = MagicMock(**{'book.book_reference_id': 7, 'chapter': 8, 'verse': 9}) + self.media_item.bible = self.mocked_bible_1 + self.media_item.second_bible = self.mocked_bible_2 + self.media_item.second_search_results = [] + + # WHEN: Calling on_text_search and not all results are found in the second bible + self.mocked_plugin.manager.verse_search.return_value = [mocked_verse_1, mocked_verse_2, mocked_verse_3] + self.media_item.second_bible.get_verses.side_effect = [[mocked_verse_1a], [], []] + with patch.object(self.media_item, 'display_results') as mocked_display_results: + self.media_item.on_text_search('Search Text') + + # THEN: The search results included in both bibles should be returned and the user should be notified of + # the missing verses + self.assertEqual(self.media_item.search_results, [mocked_verse_1]) + self.assertEqual(self.media_item.second_search_results, [mocked_verse_1a]) + self.assertEqual(self.mocked_log.debug.call_count, 2) + self.assertTrue(self.mocked_main_window.information_message.called) + mocked_display_results.assert_called_once_with() + + # TODO: Test text_search + + def test_on_search_edit_text_changed_search_while_typing_disabled(self): + """ + Test on_search_edit_text_changed when 'search while typing' is disabled + """ + # GIVEN: An instance of BibleMediaItem and mocked Settings which returns False when the value + # 'bibles/is search while typing enabled' is requested + self.setting_values = {'bibles/is search while typing enabled': False} + self.mocked_qtimer.isActive.return_value = False + + # WHEN: Calling on_search_edit_text_changed + self.media_item.on_search_edit_text_changed() + + # THEN: The method should not have checked if the timer is active + self.assertFalse(self.media_item.search_timer.isActive.called) + + def test_on_search_edit_text_changed_search_while_typing_enabled(self): + """ + Test on_search_edit_text_changed when 'search while typing' is enabled + """ + # GIVEN: An instance of BibleMediaItem and mocked Settings which returns True when the value + # 'bibles/is search while typing enabled' is requested + self.setting_values = {'bibles/is search while typing enabled': True} + self.media_item.search_timer.isActive.return_value = False + + # WHEN: Calling on_search_edit_text_changed + self.media_item.on_search_edit_text_changed() + + # THEN: The method should start the search_timer + self.media_item.search_timer.isActive.assert_called_once_with() + self.media_item.search_timer.start.assert_called_once_with() + + def test_on_search_timer_timeout(self): + """ + Test on_search_timer_timeout + """ + # GIVEN: An instance of BibleMediaItem + with patch.object(self.media_item, 'text_search') as mocked_text_search: + + # 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) + + def test_display_results_no_results(self): + """ + Test the display_results method when there are no items to display + """ + # GIVEN: An instance of BibleMediaItem and a mocked build_display_results which returns an empty list + self.media_item.list_view = MagicMock() + self.media_item.bible = self.mocked_bible_1 + self.media_item.second_bible = self.mocked_bible_2 + self.media_item.search_results = [] + + with patch.object(self.media_item, 'build_display_results', return_value=[]): + + # WHEN: Calling display_results with True + 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): + """ + 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']): + self.media_item.search_results = ['results'] + self.media_item.list_view = MagicMock() + + # WHEN: Calling display_results + 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')]) From a11e731ab0910a5bad75ff0ffaa8d9e71cb44d57 Mon Sep 17 00:00:00 2001 From: Philip Ridout Date: Sun, 18 Dec 2016 14:05:10 +0000 Subject: [PATCH 06/26] Tests --- .../openlp_plugins/bibles/test_lib.py | 143 +++++++++++++++++- 1 file changed, 140 insertions(+), 3 deletions(-) diff --git a/tests/functional/openlp_plugins/bibles/test_lib.py b/tests/functional/openlp_plugins/bibles/test_lib.py index 27d7f5e51..d678ceb7e 100644 --- a/tests/functional/openlp_plugins/bibles/test_lib.py +++ b/tests/functional/openlp_plugins/bibles/test_lib.py @@ -25,11 +25,12 @@ This module contains tests for the lib submodule of the Bibles plugin. from unittest import TestCase from openlp.plugins.bibles import lib -from openlp.plugins.bibles.lib import SearchResults -from tests.functional import patch +from openlp.plugins.bibles.lib import SearchResults, get_reference_match +from tests.functional import MagicMock, patch +from tests.helpers.testmixin import TestMixin -class TestLib(TestCase): +class TestLib(TestCase, TestMixin): """ Test the functions in the :mod:`lib` module. """ @@ -60,6 +61,142 @@ class TestLib(TestCase): self.assertEqual(separators[key], value) mocked_update_reference_separators.assert_called_once_with() + def test_reference_matched_full(self): + """ + Test that the 'full' regex parses bible verse references correctly. + """ + # GIVEN: Some test data which contains different references to parse, with the expected results. + with patch('openlp.plugins.bibles.lib.Settings', return_value=MagicMock(**{'value.return_value':''})): + # The following test data tests with 222 variants when using the default 'separators' + test_data = [ + # Input reference, book name, chapter + verse reference + ('Psalm 23', 'Psalm', '23'), + ('Psalm. 23', 'Psalm', '23'), + ('Psalm 23{to}24', 'Psalm', '23-24'), + ('Psalm 23{verse}1{to}2', 'Psalm', '23:1-2'), + ('Psalm 23{verse}1{to}{end}', 'Psalm', '23:1-end'), + ('Psalm 23{verse}1{to}2{_and}5{to}6', 'Psalm', '23:1-2,5-6'), + ('Psalm 23{verse}1{to}2{_and}5{to}{end}', 'Psalm', '23:1-2,5-end'), + ('Psalm 23{verse}1{to}2{_and}24{verse}1{to}3', 'Psalm', '23:1-2,24:1-3'), + ('Psalm 23{verse}1{to}{end}{_and}24{verse}1{to}{end}', 'Psalm', '23:1-end,24:1-end'), + ('Psalm 23{verse}1{to}24{verse}1', 'Psalm', '23:1-24:1'), + ('Psalm 23{_and}24', 'Psalm', '23,24'), + ('1 John 23', '1 John', '23'), + ('1 John. 23', '1 John', '23'), + ('1 John 23{to}24', '1 John', '23-24'), + ('1 John 23{verse}1{to}2', '1 John', '23:1-2'), + ('1 John 23{verse}1{to}{end}', '1 John', '23:1-end'), + ('1 John 23{verse}1{to}2{_and}5{to}6', '1 John', '23:1-2,5-6'), + ('1 John 23{verse}1{to}2{_and}5{to}{end}', '1 John', '23:1-2,5-end'), + ('1 John 23{verse}1{to}2{_and}24{verse}1{to}3', '1 John', '23:1-2,24:1-3'), + ('1 John 23{verse}1{to}{end}{_and}24{verse}1{to}{end}', '1 John', '23:1-end,24:1-end'), + ('1 John 23{verse}1{to}24{verse}1', '1 John', '23:1-24:1'), + ('1 John 23{_and}24', '1 John', '23,24')] + + full_reference_match = get_reference_match('full') + for reference_text, book_result, ranges_result in test_data: + to_separators = ['-', ' - ', 'to', ' to '] if '{to}' in reference_text else [''] + verse_separators = [':', ' : ', 'v', ' v ', 'V', ' V ', 'verse', ' verse ', 'verses', ' verses '] \ + if '{verse}' in reference_text else [''] + and_separators = [',', ' , ', 'and', ' and '] if '{_and}' in reference_text else [''] + end_separators = ['end', ' end '] if '{end}' in reference_text else ['']; + + for to in to_separators: + for verse in verse_separators: + for _and in and_separators: + for end in end_separators: + reference_text = reference_text.format(to=to, verse=verse, _and=_and, end=end) + + # WHEN: Attempting to parse the input string + match = full_reference_match.match(reference_text) + + # THEN: A match should be returned, and the book and reference should match the + # expected result + self.assertIsNotNone(match, '{text} should provide a match'.format(text=reference_text)) + self.assertEqual(book_result, match.group('book'), + '{text} does not provide the expected result for the book group.' + .format(text=reference_text)) + self.assertEqual(ranges_result, match.group('ranges'), + '{text} does not provide the expected result for the ranges group.' + .format(text=reference_text)) + + def test_reference_matched_range(self): + """ + Test that the 'range' regex parses bible verse references correctly. + Note: This test takes in to account that the regex does not work quite as expected! + see https://bugs.launchpad.net/openlp/+bug/1638620 + """ + # GIVEN: Some test data which contains different references to parse, with the expected results. + with patch('openlp.plugins.bibles.lib.Settings', return_value=MagicMock(**{'value.return_value':''})): + # The following test data tests with 45 variants when using the default 'separators' + test_data = [ + ('23', None, '23', None, None, None), + ('23{to}24', None, '23', '-24', None, '24'), + ('23{verse}1{to}2', '23', '1', '-2', None, '2'), + ('23{verse}1{to}{end}', '23', '1', '-end', None, None), + ('23{verse}1{to}24{verse}1', '23', '1', '-24:1', '24', '1')] + full_reference_match = get_reference_match('range') + for reference_text, from_chapter, from_verse, range_to, to_chapter, to_verse in test_data: + to_separators = ['-', ' - ', 'to', ' to '] if '{to}' in reference_text else [''] + verse_separators = [':', ' : ', 'v', ' v ', 'V', ' V ', 'verse', ' verse ', 'verses', ' verses '] \ + if '{verse}' in reference_text else [''] + and_separators = [',', ' , ', 'and', ' and '] if '{_and}' in reference_text else [''] + end_separators = ['end', ' end '] if '{end}' in reference_text else ['']; + + for to in to_separators: + for verse in verse_separators: + for _and in and_separators: + for end in end_separators: + reference_text = reference_text.format(to=to, verse=verse, _and=_and, end=end) + + # WHEN: Attempting to parse the input string + match = full_reference_match.match(reference_text) + + # THEN: A match should be returned, and the to/from chapter/verses should match as + # expected + self.assertIsNotNone(match, '{text} should provide a match'.format(text=reference_text)) + self.assertEqual(match.group('from_chapter'), from_chapter) + self.assertEqual(match.group('from_verse'), from_verse) + self.assertEqual(match.group('range_to'), range_to) + self.assertEqual(match.group('to_chapter'), to_chapter) + self.assertEqual(match.group('to_verse'), to_verse) + + def test_reference_matched_range_separator(self): + # GIVEN: Some test data which contains different references to parse, with the expected results. + with patch('openlp.plugins.bibles.lib.Settings', return_value=MagicMock(**{'value.return_value': ''})): + # The following test data tests with 111 variants when using the default 'separators' + # The regex for handling ranges is a bit screwy, see https://bugs.launchpad.net/openlp/+bug/1638620 + test_data = [ + ('23',['23']), + ('23{to}24',['23-24']), + ('23{verse}1{to}2', ['23:1-2']), + ('23{verse}1{to}{end}', ['23:1-end']), + ('23{verse}1{to}2{_and}5{to}6', ['23:1-2', '5-6']), + ('23{verse}1{to}2{_and}5{to}{end}', ['23:1-2', '5-end']), + ('23{verse}1{to}2{_and}24{verse}1{to}3', ['23:1-2', '24:1-3']), + ('23{verse}1{to}{end}{_and}24{verse}1{to}{end}', ['23:1-end', '24:1-end']), + ('23{verse}1{to}24{verse}1', ['23:1-24:1']), + ('23,24', ['23', '24'])] + full_reference_match = get_reference_match('range_separator') + for reference_text, ranges in test_data: + to_separators = ['-', ' - ', 'to', ' to '] if '{to}' in reference_text else [''] + verse_separators = [':', ' : ', 'v', ' v ', 'V', ' V ', 'verse', ' verse ', 'verses', ' verses '] \ + if '{verse}' in reference_text else [''] + and_separators = [',', ' , ', 'and', ' and '] if '{_and}' in reference_text else [''] + end_separators = ['end', ' end '] if '{end}' in reference_text else ['']; + + for to in to_separators: + for verse in verse_separators: + for _and in and_separators: + for end in end_separators: + reference_text = reference_text.format(to=to, verse=verse, _and=_and, end=end) + + # WHEN: Attempting to parse the input string + references = full_reference_match.split(reference_text) + + # THEN: The list of references should be as the expected results + self.assertEqual(references, ranges) + def test_search_results_creation(self): """ Test the creation and construction of the SearchResults class From c618c7f29573e127108f8290d024a85be1473bd4 Mon Sep 17 00:00:00 2001 From: Philip Ridout Date: Sun, 18 Dec 2016 14:08:29 +0000 Subject: [PATCH 07/26] Tests --- .../test_listwidgetwithdnd.py | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/tests/functional/openlp_core_ui_lib/test_listwidgetwithdnd.py b/tests/functional/openlp_core_ui_lib/test_listwidgetwithdnd.py index f6cc8b446..f52d71238 100644 --- a/tests/functional/openlp_core_ui_lib/test_listwidgetwithdnd.py +++ b/tests/functional/openlp_core_ui_lib/test_listwidgetwithdnd.py @@ -33,6 +33,37 @@ 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. From 63e19f5c1598f19b946e26c691842d942820275c Mon Sep 17 00:00:00 2001 From: Philip Ridout Date: Sun, 18 Dec 2016 14:11:31 +0000 Subject: [PATCH 08/26] added sort icon --- resources/images/openlp-2.qrc | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/images/openlp-2.qrc b/resources/images/openlp-2.qrc index ebff58a0a..e221474ff 100644 --- a/resources/images/openlp-2.qrc +++ b/resources/images/openlp-2.qrc @@ -29,6 +29,7 @@ image_new_group.png + bibles_book_sort.png bibles_search_combined.png bibles_search_text.png bibles_search_reference.png From aa6d0d07e3bb4920bac9605607be55556e3de9f5 Mon Sep 17 00:00:00 2001 From: Philip Ridout Date: Sun, 18 Dec 2016 14:22:05 +0000 Subject: [PATCH 09/26] Refactors --- openlp/plugins/bibles/lib/db.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/openlp/plugins/bibles/lib/db.py b/openlp/plugins/bibles/lib/db.py index 3a0a89757..e3af8712d 100644 --- a/openlp/plugins/bibles/lib/db.py +++ b/openlp/plugins/bibles/lib/db.py @@ -36,7 +36,7 @@ from sqlalchemy.orm.exc import UnmappedClassError from openlp.core.common import AppLocation, translate, clean_filename from openlp.core.lib.db import BaseModel, init_db, Manager from openlp.core.lib.ui import critical_error_message_box -from openlp.plugins.bibles.lib import upgrade +from openlp.plugins.bibles.lib import BibleStrings, LanguageSelection, upgrade log = logging.getLogger(__name__) @@ -52,9 +52,15 @@ class BibleMeta(BaseModel): class Book(BaseModel): """ - Song model + Bible Book model """ - pass + def get_name(self, language_selection=LanguageSelection.Bible): + if language_selection == LanguageSelection.Bible: + return self.name + elif language_selection == LanguageSelection.Application: + return BibleStrings().BookNames[BiblesResourcesDB.get_book_by_id(self.book_reference_id)['abbreviation']] + elif language_selection == LanguageSelection.English: + return BiblesResourcesDB.get_book_by_id(self.book_reference_id)['name'] class Verse(BaseModel): @@ -380,13 +386,12 @@ class BibleDB(Manager): """ log.debug('BibleDB.verse_search("{text}")'.format(text=text)) verses = self.session.query(Verse) - # TODO: Find out what this is doing before converting to format() if text.find(',') > -1: - keywords = ['%%%s%%' % keyword.strip() for keyword in text.split(',')] + keywords = ['%{keyword}%'.format(keyword=keyword.strip()) for keyword in text.split(',') if keyword.strip()] or_clause = [Verse.text.like(keyword) for keyword in keywords] verses = verses.filter(or_(*or_clause)) else: - keywords = ['%%%s%%' % keyword.strip() for keyword in text.split(' ')] + keywords = ['%{keyword}%'.format(keyword=keyword.strip()) for keyword in text.split(' ') if keyword.strip()] for keyword in keywords: verses = verses.filter(Verse.text.like(keyword)) verses = verses.all() From e261d2e1ba8adbc318fcbe5dfa28626aec1ebf86 Mon Sep 17 00:00:00 2001 From: Philip Ridout Date: Sun, 18 Dec 2016 15:10:05 +0000 Subject: [PATCH 10/26] Refactors --- openlp/core/lib/mediamanageritem.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/openlp/core/lib/mediamanageritem.py b/openlp/core/lib/mediamanageritem.py index 25de144ad..fafd84c5e 100644 --- a/openlp/core/lib/mediamanageritem.py +++ b/openlp/core/lib/mediamanageritem.py @@ -197,14 +197,9 @@ class MediaManagerItem(QtWidgets.QWidget, RegistryProperties): """ # Add the List widget self.list_view = ListWidgetWithDnD(self, self.plugin.name) - self.list_view.setSpacing(1) - self.list_view.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) - self.list_view.setAlternatingRowColors(True) self.list_view.setObjectName('{name}ListView'.format(name=self.plugin.name)) # Add to page_layout self.page_layout.addWidget(self.list_view) - # define and add the context menu - self.list_view.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) if self.has_edit_icon: create_widget_action(self.list_view, text=self.plugin.get_string(StringContent.Edit)['title'], From 09227c3bfc749ad188d05de16ae7deb443846982 Mon Sep 17 00:00:00 2001 From: Philip Ridout Date: Sun, 18 Dec 2016 15:10:30 +0000 Subject: [PATCH 11/26] Refactors --- openlp/core/ui/lib/listwidgetwithdnd.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/openlp/core/ui/lib/listwidgetwithdnd.py b/openlp/core/ui/lib/listwidgetwithdnd.py index de601fa13..30a644b88 100644 --- a/openlp/core/ui/lib/listwidgetwithdnd.py +++ b/openlp/core/ui/lib/listwidgetwithdnd.py @@ -40,6 +40,11 @@ class ListWidgetWithDnD(QtWidgets.QListWidget): super().__init__(parent) self.mime_data_text = name self.no_results_text = UiStrings().NoResults + self.setSpacing(1) + self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) + self.setAlternatingRowColors(True) + self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) + self.locked = False def activateDnD(self): """ @@ -49,13 +54,15 @@ 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): + def clear(self, search_while_typing=False, override_lock=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: From 3f9526caf4dcaf303480578fe174581d425b3af1 Mon Sep 17 00:00:00 2001 From: Philip Ridout Date: Sun, 18 Dec 2016 15:33:51 +0000 Subject: [PATCH 12/26] Refactors --- openlp/plugins/bibles/lib/manager.py | 51 ++++++++++++++-------------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/openlp/plugins/bibles/lib/manager.py b/openlp/plugins/bibles/lib/manager.py index 084e24270..7518cb218 100644 --- a/openlp/plugins/bibles/lib/manager.py +++ b/openlp/plugins/bibles/lib/manager.py @@ -250,7 +250,13 @@ class BibleManager(OpenLPMixin, RegistryProperties): '"{book}", "{chapter}")'.format(bible=bible, book=book_ref_id, chapter=chapter)) return self.db_cache[bible].get_verse_count(book_ref_id, chapter) - def get_verses(self, bible, verse_text, book_ref_id=False, show_error=True): + def parse_ref(self, bible, reference_text, book_ref_id=False): + if not bible: + return + language_selection = self.get_language_selection(bible) + return parse_reference(reference_text, self.db_cache[bible], language_selection, book_ref_id) + + def get_verses(self, bible, ref_list, show_error=True): """ Parses a scripture reference, fetches the verses from the Bible specified, and returns a list of ``Verse`` objects. @@ -271,22 +277,9 @@ class BibleManager(OpenLPMixin, 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( - 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 not bible or not ref_list: return None + return self.db_cache[bible].get_verses(ref_list, show_error) def get_language_selection(self, bible): """ @@ -308,7 +301,7 @@ class BibleManager(OpenLPMixin, RegistryProperties): language_selection = LanguageSelection.Application return language_selection - def verse_search(self, bible, second_bible, text): + def verse_search(self, bible, text): """ Does a verse search for the given bible and text. @@ -325,20 +318,14 @@ class BibleManager(OpenLPMixin, RegistryProperties): 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 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 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.') + '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. @@ -380,6 +367,20 @@ class BibleManager(OpenLPMixin, RegistryProperties): else: return None + def process_verse_range(self, book_ref_id, chapter_from, verse_from, chapter_to, verse_to): + verse_ranges = [] + for chapter in range(chapter_from, chapter_to + 1): + if chapter == chapter_from: + start_verse = verse_from + else: + start_verse = 1 + if chapter == chapter_to: + end_verse = verse_to + else: + end_verse = -1 + verse_ranges.append((book_ref_id, chapter, start_verse, end_verse)) + return verse_ranges + def save_meta_data(self, bible, version, copyright, permissions, book_name_language=None): """ Saves the bibles meta data. From b8404dedf8422344950add0864dc8b970f994ae4 Mon Sep 17 00:00:00 2001 From: Philip Ridout Date: Sun, 18 Dec 2016 15:38:35 +0000 Subject: [PATCH 13/26] misc --- openlp/plugins/songs/forms/songselectform.py | 0 tests/resources/themes/Moss_on_tree.otz | Bin 2 files changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 openlp/plugins/songs/forms/songselectform.py mode change 100755 => 100644 tests/resources/themes/Moss_on_tree.otz diff --git a/openlp/plugins/songs/forms/songselectform.py b/openlp/plugins/songs/forms/songselectform.py old mode 100755 new mode 100644 diff --git a/tests/resources/themes/Moss_on_tree.otz b/tests/resources/themes/Moss_on_tree.otz old mode 100755 new mode 100644 From 849bbd2c5189a7b51080d29699c926d701fe3520 Mon Sep 17 00:00:00 2001 From: Philip Ridout Date: Sun, 18 Dec 2016 18:42:28 +0000 Subject: [PATCH 14/26] Pep fixes --- openlp/core/common/settings.py | 4 +- openlp/plugins/bibles/lib/mediaitem.py | 7 ++- .../openlp_plugins/bibles/test_lib.py | 14 +++--- .../openlp_plugins/bibles/test_mediaitem.py | 45 +++++++++---------- 4 files changed, 34 insertions(+), 36 deletions(-) diff --git a/openlp/core/common/settings.py b/openlp/core/common/settings.py index 73a6cdbcf..46d7d9a7c 100644 --- a/openlp/core/common/settings.py +++ b/openlp/core/common/settings.py @@ -218,8 +218,8 @@ class Settings(QtCore.QSettings): ('shortcuts/escapeItem', 'shortcuts/desktopScreenEnable', []), # Escape item was removed in 2.6. ('shortcuts/offlineHelpItem', 'shortcuts/userManualItem', []), # Online and Offline help were combined in 2.6. ('shortcuts/onlineHelpItem', 'shortcuts/userManualItem', []), # Online and Offline help were combined in 2.6. - ('bibles/advanced bible', '', []), # Common bible search widgets combined in 2.6 - ('bibles/quick bible', 'bibles/primary bible', []) # Common bible search widgets combined in 2.6 + ('bibles/advanced bible', '', []), # Common bible search widgets combined in 2.6 + ('bibles/quick bible', 'bibles/primary bible', []) # Common bible search widgets combined in 2.6 ] @staticmethod diff --git a/openlp/plugins/bibles/lib/mediaitem.py b/openlp/plugins/bibles/lib/mediaitem.py index c09767b89..8beaf596b 100644 --- a/openlp/plugins/bibles/lib/mediaitem.py +++ b/openlp/plugins/bibles/lib/mediaitem.py @@ -504,7 +504,7 @@ class BibleMediaItem(MediaManagerItem): self.second_combo_box.setCurrentIndex(self.second_combo_box.findData(self.second_bible)) return self.second_bible = new_selection - if new_selection == None: + if new_selection is None: self.style_combo_box.setEnabled(True) else: self.style_combo_box.setEnabled(False) @@ -669,8 +669,8 @@ class BibleMediaItem(MediaManagerItem): translate('BiblesPlugin.MediaItem', 'The second Bible "{second_name}" does not contain all the verses that are in the main ' 'Bible "{name}".\nOnly verses found in both Bibles will be shown.\n\n' - '{count:d} verses have not been included in the results.') - .format(second_name=self.second_bible.name, name=self.bible.name, count=not_found_count)) + '{count:d} verses have not been included in the results.' + ).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): @@ -707,7 +707,6 @@ class BibleMediaItem(MediaManagerItem): self.on_text_search(text) self.display_results() - def on_search_edit_text_changed(self): """ If 'search_as_you_type' is enabled, start a timer when the search_edit emits a textChanged signal. This is to diff --git a/tests/functional/openlp_plugins/bibles/test_lib.py b/tests/functional/openlp_plugins/bibles/test_lib.py index d678ceb7e..5cdcffb64 100644 --- a/tests/functional/openlp_plugins/bibles/test_lib.py +++ b/tests/functional/openlp_plugins/bibles/test_lib.py @@ -66,7 +66,7 @@ class TestLib(TestCase, TestMixin): Test that the 'full' regex parses bible verse references correctly. """ # GIVEN: Some test data which contains different references to parse, with the expected results. - with patch('openlp.plugins.bibles.lib.Settings', return_value=MagicMock(**{'value.return_value':''})): + with patch('openlp.plugins.bibles.lib.Settings', return_value=MagicMock(**{'value.return_value': ''})): # The following test data tests with 222 variants when using the default 'separators' test_data = [ # Input reference, book name, chapter + verse reference @@ -99,7 +99,7 @@ class TestLib(TestCase, TestMixin): verse_separators = [':', ' : ', 'v', ' v ', 'V', ' V ', 'verse', ' verse ', 'verses', ' verses '] \ if '{verse}' in reference_text else [''] and_separators = [',', ' , ', 'and', ' and '] if '{_and}' in reference_text else [''] - end_separators = ['end', ' end '] if '{end}' in reference_text else ['']; + end_separators = ['end', ' end '] if '{end}' in reference_text else [''] for to in to_separators: for verse in verse_separators: @@ -127,7 +127,7 @@ class TestLib(TestCase, TestMixin): see https://bugs.launchpad.net/openlp/+bug/1638620 """ # GIVEN: Some test data which contains different references to parse, with the expected results. - with patch('openlp.plugins.bibles.lib.Settings', return_value=MagicMock(**{'value.return_value':''})): + with patch('openlp.plugins.bibles.lib.Settings', return_value=MagicMock(**{'value.return_value': ''})): # The following test data tests with 45 variants when using the default 'separators' test_data = [ ('23', None, '23', None, None, None), @@ -141,7 +141,7 @@ class TestLib(TestCase, TestMixin): verse_separators = [':', ' : ', 'v', ' v ', 'V', ' V ', 'verse', ' verse ', 'verses', ' verses '] \ if '{verse}' in reference_text else [''] and_separators = [',', ' , ', 'and', ' and '] if '{_and}' in reference_text else [''] - end_separators = ['end', ' end '] if '{end}' in reference_text else ['']; + end_separators = ['end', ' end '] if '{end}' in reference_text else [''] for to in to_separators: for verse in verse_separators: @@ -167,8 +167,8 @@ class TestLib(TestCase, TestMixin): # The following test data tests with 111 variants when using the default 'separators' # The regex for handling ranges is a bit screwy, see https://bugs.launchpad.net/openlp/+bug/1638620 test_data = [ - ('23',['23']), - ('23{to}24',['23-24']), + ('23', ['23']), + ('23{to}24', ['23-24']), ('23{verse}1{to}2', ['23:1-2']), ('23{verse}1{to}{end}', ['23:1-end']), ('23{verse}1{to}2{_and}5{to}6', ['23:1-2', '5-6']), @@ -183,7 +183,7 @@ class TestLib(TestCase, TestMixin): verse_separators = [':', ' : ', 'v', ' v ', 'V', ' V ', 'verse', ' verse ', 'verses', ' verses '] \ if '{verse}' in reference_text else [''] and_separators = [',', ' , ', 'and', ' and '] if '{_and}' in reference_text else [''] - end_separators = ['end', ' end '] if '{end}' in reference_text else ['']; + end_separators = ['end', ' end '] if '{end}' in reference_text else [''] for to in to_separators: for verse in verse_separators: diff --git a/tests/functional/openlp_plugins/bibles/test_mediaitem.py b/tests/functional/openlp_plugins/bibles/test_mediaitem.py index 25107c89e..ac15586cc 100644 --- a/tests/functional/openlp_plugins/bibles/test_mediaitem.py +++ b/tests/functional/openlp_plugins/bibles/test_mediaitem.py @@ -68,7 +68,7 @@ class TestBibleMediaItemModulefunctions(TestCase): # THEN: The result should contain the 'verse', 'range', 'list' keys and get_reference_separator should have # been called with the expected values. - self.assertTrue(all (key in result for key in ('verse', 'range', 'list'))) + self.assertTrue(all(key in result for key in ('verse', 'range', 'list'))) mocked_get_reference_separator.assert_has_calls( [call('sep_v_display'), call('sep_r_display'), call('sep_l_display')]) @@ -130,7 +130,7 @@ class TestMediaItem(TestCase, TestMixin): Registry.create() - #self.setup_application() + # self.setup_application() self.mocked_application = MagicMock() Registry().register('application', self.mocked_application) self.mocked_main_window = MagicMock() @@ -138,8 +138,8 @@ class TestMediaItem(TestCase, TestMixin): self.mocked_plugin = MagicMock() with patch('openlp.plugins.bibles.lib.mediaitem.build_icon'), \ - patch('openlp.plugins.bibles.lib.mediaitem.MediaManagerItem._setup'), \ - patch('openlp.plugins.bibles.lib.mediaitem.BibleMediaItem.setup_item'): + patch('openlp.plugins.bibles.lib.mediaitem.MediaManagerItem._setup'), \ + patch('openlp.plugins.bibles.lib.mediaitem.BibleMediaItem.setup_item'): self.media_item = BibleMediaItem(None, self.mocked_plugin) self.media_item.settings_section = 'bibles' @@ -156,8 +156,6 @@ class TestMediaItem(TestCase, TestMixin): self.mocked_bible_2 = MagicMock(**{'get_books.return_value': self.book_list_2}) self.mocked_bible_2.name = 'Bible 2' - - def test_media_item_instance(self): """ When creating an instance of C test that it is also an instance of @@ -266,7 +264,8 @@ class TestMediaItem(TestCase, TestMixin): """ # GIVEN: An instance of :class:`MediaManagerItem` and mocked out settings class with known values self.setting_values = {'bibles/reset to combined quick search': False} - with patch.object(self.media_item, 'populate_bible_combo_boxes'), patch.object(self.media_item, 'config_update'): + with patch.object(self.media_item, 'populate_bible_combo_boxes'), \ + patch.object(self.media_item, 'config_update'): self.media_item.search_edit = MagicMock() # WHEN: Calling initialise() @@ -282,7 +281,8 @@ class TestMediaItem(TestCase, TestMixin): """ # GIVEN: An instance of :class:`MediaManagerItem` and mocked out settings class with known values self.setting_values = {'bibles/reset to combined quick search': True} - with patch.object(self.media_item, 'populate_bible_combo_boxes'), patch.object(self.media_item, 'config_update'): + with patch.object(self.media_item, 'populate_bible_combo_boxes'), \ + patch.object(self.media_item, 'config_update'): self.media_item.search_edit = MagicMock() # WHEN: Calling initialise() @@ -307,7 +307,7 @@ class TestMediaItem(TestCase, TestMixin): self.mocked_plugin.manager.get_bibles.return_value = \ {'Bible 2': bible_2, 'Bible 1': bible_1, 'Bible 3': bible_3} with patch('openlp.plugins.bibles.lib.mediaitem.get_locale_key', side_effect=lambda x: x), \ - patch('openlp.plugins.bibles.lib.mediaitem.find_and_set_in_combo_box'): + patch('openlp.plugins.bibles.lib.mediaitem.find_and_set_in_combo_box'): # WHEN: Calling populate_bible_combo_boxes self.media_item.populate_bible_combo_boxes() @@ -375,7 +375,7 @@ class TestMediaItem(TestCase, TestMixin): """ # GIVEN: An instance of :class:`MediaManagerItem` and a mocked_book_combo_box which simulates data being found # in the list - self.media_item.select_book_combo_box = MagicMock(**{'findData.return_value':2}) + self.media_item.select_book_combo_box = MagicMock(**{'findData.return_value': 2}) with patch.object(self.media_item, 'get_common_books', return_value=self.book_list_1), \ patch.object(self.media_item, 'on_advanced_book_combo_box'): @@ -394,9 +394,9 @@ class TestMediaItem(TestCase, TestMixin): """ # GIVEN: An instance of :class:`MediaManagerItem` and a mocked_book_combo_box which simulates data not being # found in the list - self.media_item.select_book_combo_box = MagicMock(**{'findData.return_value':-1}) + self.media_item.select_book_combo_box = MagicMock(**{'findData.return_value': -1}) with patch.object(self.media_item, 'get_common_books', return_value=self.book_list_1), \ - patch.object(self.media_item, 'on_advanced_book_combo_box'): + patch.object(self.media_item, 'on_advanced_book_combo_box'): # WHEN: Calling initialise_advanced_bible() with the last_id argument set self.media_item.bible = MagicMock() @@ -457,9 +457,10 @@ class TestMediaItem(TestCase, TestMixin): self.media_item.search_edit = mocked_search_edit self.media_item.bible = MagicMock() with patch.object(self.media_item, 'get_common_books', return_value=self.book_list_1), \ - patch('openlp.plugins.bibles.lib.mediaitem.get_locale_key', side_effect=lambda x: x), \ - patch('openlp.plugins.bibles.lib.mediaitem.set_case_insensitive_completer') \ + patch('openlp.plugins.bibles.lib.mediaitem.get_locale_key', side_effect=lambda x: x), \ + patch('openlp.plugins.bibles.lib.mediaitem.set_case_insensitive_completer') \ as mocked_set_case_insensitive_completer: + # WHEN: Calling update_auto_completer self.media_item.update_auto_completer() @@ -525,7 +526,7 @@ class TestMediaItem(TestCase, TestMixin): mocked_edit_bible_form_instance = MagicMock(**{'exec.return_value': False}) with patch('openlp.plugins.bibles.lib.mediaitem.EditBibleForm', return_value=mocked_edit_bible_form_instance) \ as mocked_edit_bible_form, \ - patch.object(self.media_item, 'reload_bibles') as mocked_reload_bibles: + patch.object(self.media_item, 'reload_bibles') as mocked_reload_bibles: # WHEN: on_edit_click is called, and the user cancels the EditBibleForm self.media_item.on_edit_click() @@ -591,8 +592,8 @@ class TestMediaItem(TestCase, TestMixin): # GIVEN: An instance of :class:`MediaManagerItem` and a QMessageBox which reutrns QtWidgets.QMessageBox.Yes self.media_item.bible = MagicMock() with patch('openlp.plugins.bibles.lib.mediaitem.QtWidgets.QMessageBox.question', - return_value=QtWidgets.QMessageBox.Yes) as mocked_qmessage_box, \ - patch.object(self.media_item, 'reload_bibles'): + return_value=QtWidgets.QMessageBox.Yes) as mocked_qmessage_box, \ + patch.object(self.media_item, 'reload_bibles'): # WHEN: on_delete_click is called self.media_item.on_delete_click() @@ -637,7 +638,7 @@ class TestMediaItem(TestCase, TestMixin): """ Test that 'on_book_order_button_toggled' changes the order of the book list """ - self.media_item.select_book_combo_box = MagicMock() + self.media_item.select_book_combo_box = MagicMock() # WHEN: When the book_order_button is checked self.media_item.on_book_order_button_toggled(True) @@ -649,7 +650,7 @@ class TestMediaItem(TestCase, TestMixin): """ Test that 'on_book_order_button_toggled' changes the order of the book list """ - self.media_item.select_book_combo_box = MagicMock() + self.media_item.select_book_combo_box = MagicMock() # WHEN: When the book_order_button is un-checked self.media_item.on_book_order_button_toggled(False) @@ -723,7 +724,6 @@ class TestMediaItem(TestCase, TestMixin): self.media_item.settings.layout_style_combo_box.setCurrentIndex.assert_called_once_with(2) self.mocked_settings_instance.setValue.assert_called_once_with('bibles/verse layout style', 2) - def test_on_version_combo_box_index_changed_no_bible(self): """ Test on_version_combo_box_index_changed when there is no main bible. @@ -746,7 +746,7 @@ class TestMediaItem(TestCase, TestMixin): """ # GIVEN: An instance of :class:`MediaManagerItem` and mocked media_item.settings and select_book_combo_box mocked_bible_db = MagicMock() - mocked_bible_db.name='ABC' + mocked_bible_db.name = 'ABC' self.media_item.version_combo_box = MagicMock(**{'currentData.return_value': mocked_bible_db}) self.media_item.select_book_combo_box = MagicMock() with patch.object(self.media_item, 'initialise_advanced_bible') as mocked_initialise_advanced_bible: @@ -769,7 +769,7 @@ class TestMediaItem(TestCase, TestMixin): self.media_item.select_book_combo_box = MagicMock() with patch.object(self.media_item, 'initialise_advanced_bible') as mocked_initialise_advanced_bible, \ patch('openlp.plugins.bibles.lib.mediaitem.critical_error_message_box') \ - as mocked_critical_error_message_box: + as mocked_critical_error_message_box: # WHEN: The previously selected bible is one bible and the new selection is annother bible self.media_item.second_bible = self.mocked_bible_1 @@ -781,7 +781,6 @@ class TestMediaItem(TestCase, TestMixin): self.media_item.style_combo_box.setEnabled.assert_called_once_with(False) self.assertEqual(self.media_item.second_bible, self.mocked_bible_2) - def test_on_second_combo_box_index_changed_single_to_dual_user_abort(self): """ Test on_second_combo_box_index_changed when the user changes from single to dual bible mode, there are search From d9ccd966d8da2f6b4349a7c98fc83ec33bc12c50 Mon Sep 17 00:00:00 2001 From: Philip Ridout Date: Sun, 18 Dec 2016 18:47:54 +0000 Subject: [PATCH 15/26] Pep fixes --- tests/functional/openlp_plugins/bibles/test_mediaitem.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/functional/openlp_plugins/bibles/test_mediaitem.py b/tests/functional/openlp_plugins/bibles/test_mediaitem.py index ac15586cc..23ead5fa3 100644 --- a/tests/functional/openlp_plugins/bibles/test_mediaitem.py +++ b/tests/functional/openlp_plugins/bibles/test_mediaitem.py @@ -138,8 +138,8 @@ class TestMediaItem(TestCase, TestMixin): self.mocked_plugin = MagicMock() with patch('openlp.plugins.bibles.lib.mediaitem.build_icon'), \ - patch('openlp.plugins.bibles.lib.mediaitem.MediaManagerItem._setup'), \ - patch('openlp.plugins.bibles.lib.mediaitem.BibleMediaItem.setup_item'): + patch('openlp.plugins.bibles.lib.mediaitem.MediaManagerItem._setup'), \ + patch('openlp.plugins.bibles.lib.mediaitem.BibleMediaItem.setup_item'): self.media_item = BibleMediaItem(None, self.mocked_plugin) self.media_item.settings_section = 'bibles' @@ -377,7 +377,7 @@ class TestMediaItem(TestCase, TestMixin): # in the list self.media_item.select_book_combo_box = MagicMock(**{'findData.return_value': 2}) with patch.object(self.media_item, 'get_common_books', return_value=self.book_list_1), \ - patch.object(self.media_item, 'on_advanced_book_combo_box'): + patch.object(self.media_item, 'on_advanced_book_combo_box'): # WHEN: Calling initialise_advanced_bible() with the last_id argument set self.media_item.bible = MagicMock() @@ -592,7 +592,7 @@ class TestMediaItem(TestCase, TestMixin): # GIVEN: An instance of :class:`MediaManagerItem` and a QMessageBox which reutrns QtWidgets.QMessageBox.Yes self.media_item.bible = MagicMock() with patch('openlp.plugins.bibles.lib.mediaitem.QtWidgets.QMessageBox.question', - return_value=QtWidgets.QMessageBox.Yes) as mocked_qmessage_box, \ + return_value=QtWidgets.QMessageBox.Yes) as mocked_qmessage_box, \ patch.object(self.media_item, 'reload_bibles'): # WHEN: on_delete_click is called From f00270d38b8df801946a75d61ed3de70defff626 Mon Sep 17 00:00:00 2001 From: Philip Ridout Date: Sat, 18 Feb 2017 06:52:56 +0000 Subject: [PATCH 16/26] Fixes --- openlp/plugins/bibles/lib/mediaitem.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/openlp/plugins/bibles/lib/mediaitem.py b/openlp/plugins/bibles/lib/mediaitem.py index 8beaf596b..3271834b3 100644 --- a/openlp/plugins/bibles/lib/mediaitem.py +++ b/openlp/plugins/bibles/lib/mediaitem.py @@ -187,7 +187,6 @@ class BibleMediaItem(MediaManagerItem): def setupUi(self): super().setupUi() - sort_model = QtCore.QSortFilterProxyModel(self.select_book_combo_box) model = self.select_book_combo_box.model() # Reparent the combo box model to the sort proxy, otherwise it will be deleted when we change the comobox's @@ -814,7 +813,7 @@ class BibleMediaItem(MediaManagerItem): return False bible_text = '' old_chapter = -1 - raw_slides = [''] + raw_slides = [] verses = VerseReferenceList() for bitem in items: data = bitem.data(QtCore.Qt.UserRole) @@ -823,12 +822,14 @@ class BibleMediaItem(MediaManagerItem): verse_text = self.format_verse(old_chapter, data['chapter'], data['verse']) # We only support 'Verse Per Slide' when using a scond bible if data['second_bible']: - bible_text = '{data[verse]}{data[text]}\n\n{data[verse]}{data[second_text]}'.format(data=data) + second_text = self.format_verse(old_chapter, data['chapter'], data['verse']) + bible_text = '{first_version}{data[text]}\n\n{second_version}{data[second_text]}'\ + .format(first_version=verse_text, second_version=second_text, data=data) raw_slides.append(bible_text.rstrip()) bible_text = '' # If we are 'Verse Per Slide' then create a new slide. elif self.settings.layout_style == LayoutStyle.VersePerSlide: - bible_text = '{data[verse]}{data[text]}'.format(data=data) + bible_text = '{first_version}{data[text]}'.format(first_version=verse_text, data=data) raw_slides.append(bible_text.rstrip()) bible_text = '' # If we are 'Verse Per Line' then force a new line. From 1d377e42cb7fce9ccf4ac1e05126a92b9a0d30bb Mon Sep 17 00:00:00 2001 From: Philip Ridout Date: Sat, 18 Feb 2017 08:10:17 +0000 Subject: [PATCH 17/26] Fixed results not showing when using the search button as apposed to search as type! --- openlp/plugins/bibles/lib/mediaitem.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openlp/plugins/bibles/lib/mediaitem.py b/openlp/plugins/bibles/lib/mediaitem.py index 268aa37c0..cc28c93d1 100644 --- a/openlp/plugins/bibles/lib/mediaitem.py +++ b/openlp/plugins/bibles/lib/mediaitem.py @@ -704,7 +704,6 @@ class BibleMediaItem(MediaManagerItem): self.on_text_search(text, True) elif VALID_TEXT_SEARCH.search(text): self.on_text_search(text) - self.display_results() def on_search_edit_text_changed(self): """ From 04c719e8073d3e480c1fd9ed316e1f243d518fff Mon Sep 17 00:00:00 2001 From: Philip Ridout Date: Sat, 18 Feb 2017 08:21:19 +0000 Subject: [PATCH 18/26] Dont try to display search results if there are none! --- openlp/plugins/bibles/lib/mediaitem.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/openlp/plugins/bibles/lib/mediaitem.py b/openlp/plugins/bibles/lib/mediaitem.py index cc28c93d1..8813e79bc 100644 --- a/openlp/plugins/bibles/lib/mediaitem.py +++ b/openlp/plugins/bibles/lib/mediaitem.py @@ -732,10 +732,11 @@ class BibleMediaItem(MediaManagerItem): :return: None """ self.list_view.clear() - 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() + 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.search_results = [] self.second_search_results = [] From 74382d1466cf59a6be70715c350882593ade47aa Mon Sep 17 00:00:00 2001 From: Philip Ridout Date: Sat, 18 Feb 2017 20:22:47 +0000 Subject: [PATCH 19/26] Added tool tip --- openlp/plugins/bibles/lib/mediaitem.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/openlp/plugins/bibles/lib/mediaitem.py b/openlp/plugins/bibles/lib/mediaitem.py index 8813e79bc..9344fab63 100644 --- a/openlp/plugins/bibles/lib/mediaitem.py +++ b/openlp/plugins/bibles/lib/mediaitem.py @@ -132,27 +132,28 @@ class BibleMediaItem(MediaManagerItem): self.select_tab.setSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum) self.search_tab_bar.addTab(translate('BiblesPlugin.MediaItem', 'Select')) self.select_layout = QtWidgets.QFormLayout(self.select_tab) - self.book_layout = QtWidgets.QHBoxLayout(self.select_tab) + self.book_layout = QtWidgets.QHBoxLayout() self.select_book_combo_box = create_horizontal_adjusting_combo_box(self.select_tab, 'select_book_combo_box') self.book_layout.addWidget(self.select_book_combo_box) self.book_order_button = QtWidgets.QToolButton() self.book_order_button.setIcon(self.sort_icon) self.book_order_button.setCheckable(True) + self.book_order_button.setToolTip(translate('BiblesPlugin.MediaItem', 'Sort bible books alphabetically.')) self.book_layout.addWidget(self.book_order_button) self.select_layout.addRow(translate('BiblesPlugin.MediaItem', 'Book:'), self.book_layout) - self.verse_title_layout = QtWidgets.QHBoxLayout(self.select_tab) + self.verse_title_layout = QtWidgets.QHBoxLayout() self.chapter_label = QtWidgets.QLabel(self.select_tab) self.verse_title_layout.addWidget(self.chapter_label) self.verse_label = QtWidgets.QLabel(self.select_tab) self.verse_title_layout.addWidget(self.verse_label) self.select_layout.addRow('', self.verse_title_layout) - self.from_layout = QtWidgets.QHBoxLayout(self.select_tab) + self.from_layout = QtWidgets.QHBoxLayout() self.from_chapter = QtWidgets.QComboBox(self.select_tab) self.from_layout.addWidget(self.from_chapter) self.from_verse = QtWidgets.QComboBox(self.select_tab) self.from_layout.addWidget(self.from_verse) self.select_layout.addRow(translate('BiblesPlugin.MediaItem', 'From:'), self.from_layout) - self.to_layout = QtWidgets.QHBoxLayout(self.select_tab) + self.to_layout = QtWidgets.QHBoxLayout() self.to_chapter = QtWidgets.QComboBox(self.select_tab) self.to_layout.addWidget(self.to_chapter) self.to_verse = QtWidgets.QComboBox(self.select_tab) @@ -170,7 +171,7 @@ 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_widget) + self.search_button_layout = QtWidgets.QHBoxLayout() 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) From 76c2ec8b8c107063670e60732dfcadeb1a6bd081 Mon Sep 17 00:00:00 2001 From: Philip Ridout Date: Sat, 25 Feb 2017 17:30:39 +0000 Subject: [PATCH 20/26] Regened resources --- resources/images/bibles_book_sort.png | Bin 0 -> 632 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 resources/images/bibles_book_sort.png diff --git a/resources/images/bibles_book_sort.png b/resources/images/bibles_book_sort.png new file mode 100644 index 0000000000000000000000000000000000000000..c199438d8e66f65c414247b55a2da96ee6fb0fd7 GIT binary patch literal 632 zcmV-;0*C#HP)= z4<2tGydU5DF$_RV$$9jK&r-^UF|NoSu^$5xwr`Za*M5L82FtQYYcU|lfunIecveI1 zQys0nMwPjT&wYDxRa(X=;#hdMTtxY85mDDcdAX?U5@~Js9rEzzOC7U!E+H0=pXEF_ z0tE-qP);t!#>a_)mUKGZ#el$r0FAxhO1Ba4Ho6jc(FknYCZPnT6&);u06|s7PO}9m zI3?>&K+c6iA)G@tn?){{(+N_kRLJ00Gk_2Qc*vjwatv7zGMNlj?=TQ;^mzhIioY5B z^mnPU5z~qe=A7fiu>swJ!)Sf~VBkh<6qlC3EQZn16e^WUIDBL-baZJ=~rDfR`(4pk;Uw2G6yWhTng4?)`ta;5atc>pPenIfH8#Pl5}H zhqsgX_;nMH7t67(A?EXW3Nm+~w?J5!xsH+hFAxZUTSKQ%s%~KRX=%I7`9yfizVDmr zq|G^pN3^ls#O0aixN!P7@++$?#bj8#zE|n#GgWI_a_Z45iNPDw<})4mJO2SRUQSlI SFFUdT00001_ literal 0 HcmV?d00001 From f68336a49fb50ddc05a1994e5bd2ae9da4470274 Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Wed, 1 Mar 2017 17:07:16 -0700 Subject: [PATCH 21/26] Make the settings dialog as small as possible Fixes: https://launchpad.net/bugs/1668669 --- openlp/core/ui/settingsdialog.py | 2 +- .../functional/openlp_core_ui/test_settingsform.py | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/openlp/core/ui/settingsdialog.py b/openlp/core/ui/settingsdialog.py index 7ceece699..61626fa6e 100644 --- a/openlp/core/ui/settingsdialog.py +++ b/openlp/core/ui/settingsdialog.py @@ -39,7 +39,7 @@ class Ui_SettingsDialog(object): """ settings_dialog.setObjectName('settings_dialog') settings_dialog.setWindowIcon(build_icon(':/icon/openlp-logo.svg')) - settings_dialog.resize(800, 700) + settings_dialog.resize(920, 625) self.dialog_layout = QtWidgets.QGridLayout(settings_dialog) self.dialog_layout.setObjectName('dialog_layout') self.dialog_layout.setContentsMargins(8, 8, 8, 8) diff --git a/tests/functional/openlp_core_ui/test_settingsform.py b/tests/functional/openlp_core_ui/test_settingsform.py index f936f0583..16f4dc946 100644 --- a/tests/functional/openlp_core_ui/test_settingsform.py +++ b/tests/functional/openlp_core_ui/test_settingsform.py @@ -151,3 +151,17 @@ class TestSettingsForm(TestCase): # THEN: The general tab's cancel() method should have been called, but not the themes tab mocked_general_cancel.assert_called_with() self.assertEqual(0, mocked_theme_cancel.call_count, 'The Themes tab\'s cancel() should not have been called') + + def test_register_post_process(self): + """ + Test that the register_post_process() method works correctly + """ + # GIVEN: A settings form instance + settings_form = SettingsForm(None) + fake_function = MagicMock() + + # WHEN: register_post_process() is called + settings_form.register_post_process(fake_function) + + # THEN: The fake function should be in the settings form's list + assert fake_function in settings_form.processes From 3bc6fc0b77e7b9fbafc6ed09810514b5d288262d Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Wed, 1 Mar 2017 21:34:24 -0700 Subject: [PATCH 22/26] Fix bug #1666005 and bug #1668994 --- openlp/plugins/songs/forms/editsongform.py | 51 +++-- .../songs/forms/test_authorsform.py | 215 ++++++++++++++++++ 2 files changed, 243 insertions(+), 23 deletions(-) diff --git a/openlp/plugins/songs/forms/editsongform.py b/openlp/plugins/songs/forms/editsongform.py index ba79654f4..d7f614245 100644 --- a/openlp/plugins/songs/forms/editsongform.py +++ b/openlp/plugins/songs/forms/editsongform.py @@ -124,7 +124,8 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties): cache.append(obj.name) combo.setItemData(row, obj.id) set_case_insensitive_completer(cache, combo) - combo.setEditText('') + combo.setCurrentIndex(-1) + combo.setCurrentText('') def _add_author_to_list(self, author, author_type): """ @@ -367,7 +368,8 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties): self.authors_combo_box.setItemData(row, author.id) self.authors.append(author.display_name) set_case_insensitive_completer(self.authors, self.authors_combo_box) - self.authors_combo_box.setEditText('') + self.authors_combo_box.setCurrentIndex(-1) + self.authors_combo_box.setCurrentText('') # Types self.author_types_combo_box.clear() @@ -402,7 +404,8 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties): self.themes.sort(key=get_theme_key) self.theme_combo_box.addItems(theme_list) set_case_insensitive_completer(self.themes, self.theme_combo_box) - self.theme_combo_box.setEditText('') + self.theme_combo_box.setCurrentIndex(-1) + self.theme_combo_box.setCurrentText('') def load_media_files(self): """ @@ -441,7 +444,8 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties): self.load_topics() self.load_songbooks() self.load_media_files() - self.theme_combo_box.setEditText('') + self.theme_combo_box.setCurrentIndex(-1) + self.theme_combo_box.setCurrentText('') # it's a new song to preview is not possible self.preview_button.setVisible(False) @@ -466,8 +470,8 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties): find_and_set_in_combo_box(self.theme_combo_box, str(self.song.theme_name)) else: # Clear the theme combo box in case it was previously set (bug #1212801) - self.theme_combo_box.setEditText('') - self.theme_combo_box.setCurrentIndex(0) + self.theme_combo_box.setCurrentIndex(-1) + self.theme_combo_box.setCurrentText('') self.copyright_edit.setText(self.song.copyright if self.song.copyright else '') self.comments_edit.setPlainText(self.song.comments if self.song.comments else '') self.ccli_number_edit.setText(self.song.ccli_number if self.song.ccli_number else '') @@ -570,12 +574,7 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties): item = int(self.authors_combo_box.currentIndex()) text = self.authors_combo_box.currentText().strip(' \r\n\t') author_type = self.author_types_combo_box.itemData(self.author_types_combo_box.currentIndex()) - # This if statement is for OS X, which doesn't seem to work well with - # the QCompleter auto-completion class. See bug #812628. - if text in self.authors: - # Index 0 is a blank string, so add 1 - item = self.authors.index(text) + 1 - if item == 0 and text: + if item == -1 and text: if QtWidgets.QMessageBox.question( self, translate('SongsPlugin.EditSongForm', 'Add Author'), @@ -590,10 +589,11 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties): self.manager.save_object(author) self._add_author_to_list(author, author_type) self.load_authors() - self.authors_combo_box.setEditText('') + self.authors_combo_box.setCurrentIndex(-1) + self.authors_combo_box.setCurrentText('') else: return - elif item > 0: + elif item >= 0: item_id = (self.authors_combo_box.itemData(item)) author = self.manager.get_object(Author, item_id) if self.authors_list_view.findItems(author.get_display_name(author_type), QtCore.Qt.MatchExactly): @@ -601,7 +601,8 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties): message=translate('SongsPlugin.EditSongForm', 'This author is already in the list.')) else: self._add_author_to_list(author, author_type) - self.authors_combo_box.setEditText('') + self.authors_combo_box.setCurrentIndex(-1) + self.authors_combo_box.setCurrentText('') else: QtWidgets.QMessageBox.warning( self, UiStrings().NISs, @@ -653,7 +654,7 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties): def on_topic_add_button_clicked(self): item = int(self.topics_combo_box.currentIndex()) text = self.topics_combo_box.currentText() - if item == 0 and text: + if item == -1 and text: if QtWidgets.QMessageBox.question( self, translate('SongsPlugin.EditSongForm', 'Add Topic'), translate('SongsPlugin.EditSongForm', 'This topic does not exist, do you want to add it?'), @@ -665,10 +666,11 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties): topic_item.setData(QtCore.Qt.UserRole, topic.id) self.topics_list_view.addItem(topic_item) self.load_topics() - self.topics_combo_box.setEditText('') + self.topics_combo_box.setCurrentIndex(-1) + self.topics_combo_box.setCurrentText('') else: return - elif item > 0: + elif item >= 0: item_id = (self.topics_combo_box.itemData(item)) topic = self.manager.get_object(Topic, item_id) if self.topics_list_view.findItems(str(topic.name), QtCore.Qt.MatchExactly): @@ -678,7 +680,8 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties): topic_item = QtWidgets.QListWidgetItem(str(topic.name)) topic_item.setData(QtCore.Qt.UserRole, topic.id) self.topics_list_view.addItem(topic_item) - self.topics_combo_box.setEditText('') + self.topics_combo_box.setCurrentIndex(-1) + self.topics_combo_box.setCurrentText('') else: QtWidgets.QMessageBox.warning( self, UiStrings().NISs, @@ -698,7 +701,7 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties): def on_songbook_add_button_clicked(self): item = int(self.songbooks_combo_box.currentIndex()) text = self.songbooks_combo_box.currentText() - if item == 0 and text: + if item == -1 and text: if QtWidgets.QMessageBox.question( self, translate('SongsPlugin.EditSongForm', 'Add Songbook'), translate('SongsPlugin.EditSongForm', 'This Songbook does not exist, do you want to add it?'), @@ -708,11 +711,12 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties): self.manager.save_object(songbook) self.add_songbook_entry_to_list(songbook.id, songbook.name, self.songbook_entry_edit.text()) self.load_songbooks() - self.songbooks_combo_box.setEditText('') + self.songbooks_combo_box.setCurrentIndex(-1) + self.songbooks_combo_box.setCurrentText('') self.songbook_entry_edit.clear() else: return - elif item > 0: + elif item >= 0: item_id = (self.songbooks_combo_box.itemData(item)) songbook = self.manager.get_object(Book, item_id) if self.songbooks_list_view.findItems(str(songbook.name), QtCore.Qt.MatchExactly): @@ -720,7 +724,8 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties): message=translate('SongsPlugin.EditSongForm', 'This Songbook is already in the list.')) else: self.add_songbook_entry_to_list(songbook.id, songbook.name, self.songbook_entry_edit.text()) - self.songbooks_combo_box.setEditText('') + self.songbooks_combo_box.setCurrentIndex(-1) + self.songbooks_combo_box.setCurrentText('') self.songbook_entry_edit.clear() else: QtWidgets.QMessageBox.warning( diff --git a/tests/interfaces/openlp_plugins/songs/forms/test_authorsform.py b/tests/interfaces/openlp_plugins/songs/forms/test_authorsform.py index 871f79deb..ca6f976f2 100644 --- a/tests/interfaces/openlp_plugins/songs/forms/test_authorsform.py +++ b/tests/interfaces/openlp_plugins/songs/forms/test_authorsform.py @@ -23,6 +23,7 @@ Package to test the openlp.plugins.songs.forms.authorsform package. """ from unittest import TestCase +from unittest.mock import patch from PyQt5 import QtWidgets @@ -138,3 +139,217 @@ class TestAuthorsForm(TestCase, TestMixin): # THEN: The display_name_edit should have the correct value self.assertEqual(self.form.display_edit.text(), display_name, 'The display name should be set correctly') + + @patch('openlp.plugins.songs.forms.authorsform.QtWidgets.QDialog.exec') + def test_exec(self, mocked_exec): + """ + Test the exec() method + """ + # GIVEN: An authors for and various mocked objects + with patch.object(self.form.first_name_edit, 'clear') as mocked_first_name_edit_clear, \ + patch.object(self.form.last_name_edit, 'clear') as mocked_last_name_edit_clear, \ + patch.object(self.form.display_edit, 'clear') as mocked_display_edit_clear, \ + patch.object(self.form.first_name_edit, 'setFocus') as mocked_first_name_edit_setFocus: + # WHEN: The exec() method is called + self.form.exec(clear=True) + + # THEN: The clear and exec() methods should have been called + mocked_first_name_edit_clear.assert_called_once_with() + mocked_last_name_edit_clear.assert_called_once_with() + mocked_display_edit_clear.assert_called_once_with() + mocked_first_name_edit_setFocus.assert_called_once_with() + mocked_exec.assert_called_once_with(self.form) + + def test_first_name_edited(self): + """ + Test the on_first_name_edited() method + """ + # GIVEN: An author form + self.form.auto_display_name = True + + with patch.object(self.form.last_name_edit, 'text') as mocked_last_name_edit_text, \ + patch.object(self.form.display_edit, 'setText') as mocked_display_edit_setText: + mocked_last_name_edit_text.return_value = 'Newton' + + # WHEN: on_first_name_edited() is called + self.form.on_first_name_edited('John') + + # THEN: The display name should be updated + assert mocked_last_name_edit_text.call_count == 2 + mocked_display_edit_setText.assert_called_once_with('John Newton') + + def test_first_name_edited_no_auto(self): + """ + Test the on_first_name_edited() method without auto_display_name + """ + # GIVEN: An author form + self.form.auto_display_name = False + + with patch.object(self.form.last_name_edit, 'text') as mocked_last_name_edit_text, \ + patch.object(self.form.display_edit, 'setText') as mocked_display_edit_setText: + + # WHEN: on_first_name_edited() is called + self.form.on_first_name_edited('John') + + # THEN: The display name should not be updated + assert mocked_last_name_edit_text.call_count == 0 + assert mocked_display_edit_setText.call_count == 0 + + def test_last_name_edited(self): + """ + Test the on_last_name_edited() method + """ + # GIVEN: An author form + self.form.auto_display_name = True + + with patch.object(self.form.first_name_edit, 'text') as mocked_first_name_edit_text, \ + patch.object(self.form.display_edit, 'setText') as mocked_display_edit_setText: + mocked_first_name_edit_text.return_value = 'John' + + # WHEN: on_last_name_edited() is called + self.form.on_last_name_edited('Newton') + + # THEN: The display name should be updated + assert mocked_first_name_edit_text.call_count == 2 + mocked_display_edit_setText.assert_called_once_with('John Newton') + + def test_last_name_edited_no_auto(self): + """ + Test the on_last_name_edited() method without auto_display_name + """ + # GIVEN: An author form + self.form.auto_display_name = False + + with patch.object(self.form.first_name_edit, 'text') as mocked_first_name_edit_text, \ + patch.object(self.form.display_edit, 'setText') as mocked_display_edit_setText: + + # WHEN: on_last_name_edited() is called + self.form.on_last_name_edited('Newton') + + # THEN: The display name should not be updated + assert mocked_first_name_edit_text.call_count == 0 + assert mocked_display_edit_setText.call_count == 0 + + @patch('openlp.plugins.songs.forms.authorsform.critical_error_message_box') + def test_accept_no_first_name(self, mocked_critical_error): + """ + Test the accept() method with no first name + """ + # GIVEN: A form and no text in thefirst name edit + with patch.object(self.form.first_name_edit, 'text') as mocked_first_name_edit_text, \ + patch.object(self.form.first_name_edit, 'setFocus') as mocked_first_name_edit_setFocus: + mocked_first_name_edit_text.return_value = '' + + # WHEN: accept() is called + result = self.form.accept() + + # THEN: The result should be false and a critical error displayed + assert result is False + mocked_critical_error.assert_called_once_with(message='You need to type in the first name of the author.') + mocked_first_name_edit_text.assert_called_once_with() + mocked_first_name_edit_setFocus.assert_called_once_with() + + @patch('openlp.plugins.songs.forms.authorsform.critical_error_message_box') + def test_accept_no_last_name(self, mocked_critical_error): + """ + Test the accept() method with no last name + """ + # GIVEN: A form and no text in the last name edit + with patch.object(self.form.first_name_edit, 'text') as mocked_first_name_edit_text, \ + patch.object(self.form.last_name_edit, 'text') as mocked_last_name_edit_text, \ + patch.object(self.form.last_name_edit, 'setFocus') as mocked_last_name_edit_setFocus: + mocked_first_name_edit_text.return_value = 'John' + mocked_last_name_edit_text.return_value = '' + + # WHEN: accept() is called + result = self.form.accept() + + # THEN: The result should be false and a critical error displayed + assert result is False + mocked_critical_error.assert_called_once_with(message='You need to type in the last name of the author.') + mocked_first_name_edit_text.assert_called_once_with() + mocked_last_name_edit_text.assert_called_once_with() + mocked_last_name_edit_setFocus.assert_called_once_with() + + @patch('openlp.plugins.songs.forms.authorsform.critical_error_message_box') + def test_accept_no_display_name_no_combine(self, mocked_critical_error): + """ + Test the accept() method with no display name and no combining + """ + # GIVEN: A form and no text in the display name edit + mocked_critical_error.return_value = QtWidgets.QMessageBox.No + with patch.object(self.form.first_name_edit, 'text') as mocked_first_name_edit_text, \ + patch.object(self.form.last_name_edit, 'text') as mocked_last_name_edit_text, \ + patch.object(self.form.display_edit, 'text') as mocked_display_edit_text, \ + patch.object(self.form.display_edit, 'setFocus') as mocked_display_edit_setFocus: + mocked_first_name_edit_text.return_value = 'John' + mocked_last_name_edit_text.return_value = 'Newton' + mocked_display_edit_text.return_value = '' + + # WHEN: accept() is called + result = self.form.accept() + + # THEN: The result should be false and a critical error displayed + assert result is False + mocked_critical_error.assert_called_once_with( + message='You have not set a display name for the author, combine the first and last names?', + parent=self.form, question=True) + mocked_first_name_edit_text.assert_called_once_with() + mocked_last_name_edit_text.assert_called_once_with() + mocked_display_edit_text.assert_called_once_with() + mocked_display_edit_setFocus.assert_called_once_with() + + @patch('openlp.plugins.songs.forms.authorsform.critical_error_message_box') + @patch('openlp.plugins.songs.forms.authorsform.QtWidgets.QDialog.accept') + def test_accept_no_display_name(self, mocked_accept, mocked_critical_error): + """ + Test the accept() method with no display name and auto-combine + """ + # GIVEN: A form and no text in the display name edit + mocked_accept.return_value = True + mocked_critical_error.return_value = QtWidgets.QMessageBox.Yes + with patch.object(self.form.first_name_edit, 'text') as mocked_first_name_edit_text, \ + patch.object(self.form.last_name_edit, 'text') as mocked_last_name_edit_text, \ + patch.object(self.form.display_edit, 'text') as mocked_display_edit_text, \ + patch.object(self.form.display_edit, 'setText') as mocked_display_edit_setText: + mocked_first_name_edit_text.return_value = 'John' + mocked_last_name_edit_text.return_value = 'Newton' + mocked_display_edit_text.return_value = '' + + # WHEN: accept() is called + result = self.form.accept() + + # THEN: The result should be false and a critical error displayed + assert result is True + mocked_critical_error.assert_called_once_with( + message='You have not set a display name for the author, combine the first and last names?', + parent=self.form, question=True) + assert mocked_first_name_edit_text.call_count == 2 + assert mocked_last_name_edit_text.call_count == 2 + mocked_display_edit_text.assert_called_once_with() + mocked_display_edit_setText.assert_called_once_with('John Newton') + mocked_accept.assert_called_once_with(self.form) + + @patch('openlp.plugins.songs.forms.authorsform.QtWidgets.QDialog.accept') + def test_accept(self, mocked_accept): + """ + Test the accept() method + """ + # GIVEN: A form and text in the right places + mocked_accept.return_value = True + with patch.object(self.form.first_name_edit, 'text') as mocked_first_name_edit_text, \ + patch.object(self.form.last_name_edit, 'text') as mocked_last_name_edit_text, \ + patch.object(self.form.display_edit, 'text') as mocked_display_edit_text: + mocked_first_name_edit_text.return_value = 'John' + mocked_last_name_edit_text.return_value = 'Newton' + mocked_display_edit_text.return_value = 'John Newton' + + # WHEN: accept() is called + result = self.form.accept() + + # THEN: The result should be false and a critical error displayed + assert result is True + mocked_first_name_edit_text.assert_called_once_with() + mocked_last_name_edit_text.assert_called_once_with() + mocked_display_edit_text.assert_called_once_with() + mocked_accept.assert_called_once_with(self.form) From 094b1f73bc2b3667533aadb1a57b5450f305d9b3 Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Wed, 1 Mar 2017 21:43:45 -0700 Subject: [PATCH 23/26] Fix a test --- tests/functional/openlp_plugins/songs/test_editsongform.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/functional/openlp_plugins/songs/test_editsongform.py b/tests/functional/openlp_plugins/songs/test_editsongform.py index ba020541b..5ff0a633c 100644 --- a/tests/functional/openlp_plugins/songs/test_editsongform.py +++ b/tests/functional/openlp_plugins/songs/test_editsongform.py @@ -106,4 +106,5 @@ class TestEditSongForm(TestCase, TestMixin): mocked_cache.append.assert_called_once_with('Charles') mocked_combo.setItemData.assert_called_once_with(0, 1) mocked_set_case_insensitive_completer.assert_called_once_with(mocked_cache, mocked_combo) - mocked_combo.setEditText.assert_called_once_with('') + mocked_combo.setCurrentIndex.assert_called_once_with(-1) + mocked_combo.setCurrentText.assert_called_once_with('') From c0eb055041d58ea737e01736790016c313a495a0 Mon Sep 17 00:00:00 2001 From: Tim Bentley Date: Sat, 4 Mar 2017 10:24:53 +0000 Subject: [PATCH 24/26] Remove ssl from build --- openlp/plugins/remotes/lib/httpserver.py | 27 +------- openlp/plugins/remotes/lib/remotetab.py | 84 +----------------------- 2 files changed, 4 insertions(+), 107 deletions(-) diff --git a/openlp/plugins/remotes/lib/httpserver.py b/openlp/plugins/remotes/lib/httpserver.py index c8366927a..12571fc55 100644 --- a/openlp/plugins/remotes/lib/httpserver.py +++ b/openlp/plugins/remotes/lib/httpserver.py @@ -111,14 +111,9 @@ class OpenLPServer(RegistryProperties): self.address = address self.is_secure = Settings().value(self.settings_section + '/https enabled') self.needs_authentication = Settings().value(self.settings_section + '/authentication enabled') - if self.is_secure: - port = Settings().value(self.settings_section + '/https port') - self.port = port - self.start_server_instance(address, port, HTTPSServer) - else: - port = Settings().value(self.settings_section + '/port') - self.port = port - self.start_server_instance(address, port, ThreadingHTTPServer) + port = Settings().value(self.settings_section + '/port') + self.port = port + self.start_server_instance(address, port, ThreadingHTTPServer) if hasattr(self, 'httpd') and self.httpd: self.httpd.serve_forever() else: @@ -158,19 +153,3 @@ class OpenLPServer(RegistryProperties): self.http_thread.stop() self.httpd = None log.debug('Stopped the server.') - - -class HTTPSServer(HTTPServer): - def __init__(self, address, handler): - """ - Initialise the secure handlers for the SSL server if required.s - """ - BaseServer.__init__(self, address, handler) - local_data = AppLocation.get_directory(AppLocation.DataDir) - self.socket = ssl.SSLSocket( - sock=socket.socket(self.address_family, self.socket_type), - certfile=os.path.join(local_data, 'remotes', 'openlp.crt'), - keyfile=os.path.join(local_data, 'remotes', 'openlp.key'), - server_side=True) - self.server_bind() - self.server_activate() diff --git a/openlp/plugins/remotes/lib/remotetab.py b/openlp/plugins/remotes/lib/remotetab.py index 73874ae25..314781546 100644 --- a/openlp/plugins/remotes/lib/remotetab.py +++ b/openlp/plugins/remotes/lib/remotetab.py @@ -88,42 +88,6 @@ class RemoteTab(SettingsTab): self.live_url.setOpenExternalLinks(True) self.http_setting_layout.addRow(self.live_url_label, self.live_url) self.left_layout.addWidget(self.http_settings_group_box) - self.https_settings_group_box = QtWidgets.QGroupBox(self.left_column) - self.https_settings_group_box.setCheckable(True) - self.https_settings_group_box.setChecked(False) - self.https_settings_group_box.setObjectName('https_settings_group_box') - self.https_settings_layout = QtWidgets.QFormLayout(self.https_settings_group_box) - self.https_settings_layout.setObjectName('https_settings_layout') - self.https_error_label = QtWidgets.QLabel(self.https_settings_group_box) - self.https_error_label.setVisible(False) - self.https_error_label.setWordWrap(True) - self.https_error_label.setObjectName('https_error_label') - self.https_settings_layout.addRow(self.https_error_label) - self.https_port_label = QtWidgets.QLabel(self.https_settings_group_box) - self.https_port_label.setObjectName('https_port_label') - self.https_port_spin_box = QtWidgets.QSpinBox(self.https_settings_group_box) - self.https_port_spin_box.setMaximum(32767) - self.https_port_spin_box.setObjectName('https_port_spin_box') - self.https_settings_layout.addRow(self.https_port_label, self.https_port_spin_box) - self.remote_https_url = QtWidgets.QLabel(self.https_settings_group_box) - self.remote_https_url.setObjectName('remote_http_url') - self.remote_https_url.setOpenExternalLinks(True) - self.remote_https_url_label = QtWidgets.QLabel(self.https_settings_group_box) - self.remote_https_url_label.setObjectName('remote_http_url_label') - self.https_settings_layout.addRow(self.remote_https_url_label, self.remote_https_url) - self.stage_https_url_label = QtWidgets.QLabel(self.http_settings_group_box) - self.stage_https_url_label.setObjectName('stage_https_url_label') - self.stage_https_url = QtWidgets.QLabel(self.https_settings_group_box) - self.stage_https_url.setObjectName('stage_https_url') - self.stage_https_url.setOpenExternalLinks(True) - self.https_settings_layout.addRow(self.stage_https_url_label, self.stage_https_url) - self.live_https_url_label = QtWidgets.QLabel(self.https_settings_group_box) - self.live_https_url_label.setObjectName('live_url_label') - self.live_https_url = QtWidgets.QLabel(self.https_settings_group_box) - self.live_https_url.setObjectName('live_https_url') - self.live_https_url.setOpenExternalLinks(True) - self.https_settings_layout.addRow(self.live_https_url_label, self.live_https_url) - self.left_layout.addWidget(self.https_settings_group_box) self.user_login_group_box = QtWidgets.QGroupBox(self.left_column) self.user_login_group_box.setCheckable(True) self.user_login_group_box.setChecked(False) @@ -177,8 +141,6 @@ class RemoteTab(SettingsTab): self.thumbnails_check_box.stateChanged.connect(self.on_thumbnails_check_box_changed) self.address_edit.textChanged.connect(self.set_urls) self.port_spin_box.valueChanged.connect(self.set_urls) - self.https_port_spin_box.valueChanged.connect(self.set_urls) - self.https_settings_group_box.clicked.connect(self.https_changed) def retranslateUi(self): self.server_settings_group_box.setTitle(translate('RemotePlugin.RemoteTab', 'Server Settings')) @@ -200,14 +162,6 @@ class RemoteTab(SettingsTab): translate('RemotePlugin.RemoteTab', 'Scan the QR code or click download to install the iOS app from the App ' 'Store.').format(qr='https://itunes.apple.com/app/id1096218725')) - self.https_settings_group_box.setTitle(translate('RemotePlugin.RemoteTab', 'HTTPS Server')) - self.https_error_label.setText( - translate('RemotePlugin.RemoteTab', 'Could not find an SSL certificate. The HTTPS server will not be ' - 'available unless an SSL certificate is found. Please see the manual for more information.')) - self.https_port_label.setText(self.port_label.text()) - self.remote_https_url_label.setText(self.remote_url_label.text()) - self.stage_https_url_label.setText(self.stage_url_label.text()) - self.live_https_url_label.setText(self.live_url_label.text()) self.user_login_group_box.setTitle(translate('RemotePlugin.RemoteTab', 'User Authentication')) self.user_id_label.setText(translate('RemotePlugin.RemoteTab', 'User id:')) self.password_label.setText(translate('RemotePlugin.RemoteTab', 'Password:')) @@ -218,17 +172,11 @@ class RemoteTab(SettingsTab): """ ip_address = self.get_ip_address(self.address_edit.text()) http_url = 'http://{url}:{text}/'.format(url=ip_address, text=self.port_spin_box.value()) - https_url = 'https://{url}:{text}/'.format(url=ip_address, text=self.https_port_spin_box.value()) self.remote_url.setText('{url}'.format(url=http_url)) - self.remote_https_url.setText('{url}'.format(url=https_url)) http_url_temp = http_url + 'stage' - https_url_temp = https_url + 'stage' self.stage_url.setText('{url}'.format(url=http_url_temp)) - self.stage_https_url.setText('{url}'.format(url=https_url_temp)) http_url_temp = http_url + 'main' - https_url_temp = https_url + 'main' self.live_url.setText('{url}'.format(url=http_url_temp)) - self.live_https_url.setText('{url}'.format(url=https_url_temp)) def get_ip_address(self, ip_address): """ @@ -254,43 +202,25 @@ class RemoteTab(SettingsTab): """ Load the configuration and update the server configuration if necessary """ - self.is_secure = Settings().value(self.settings_section + '/https enabled') self.port_spin_box.setValue(Settings().value(self.settings_section + '/port')) - self.https_port_spin_box.setValue(Settings().value(self.settings_section + '/https port')) self.address_edit.setText(Settings().value(self.settings_section + '/ip address')) self.twelve_hour = Settings().value(self.settings_section + '/twelve hour') self.twelve_hour_check_box.setChecked(self.twelve_hour) self.thumbnails = Settings().value(self.settings_section + '/thumbnails') self.thumbnails_check_box.setChecked(self.thumbnails) - local_data = AppLocation.get_directory(AppLocation.DataDir) - if not os.path.exists(os.path.join(local_data, 'remotes', 'openlp.crt')) or \ - not os.path.exists(os.path.join(local_data, 'remotes', 'openlp.key')): - self.https_settings_group_box.setChecked(False) - self.https_settings_group_box.setEnabled(False) - self.https_error_label.setVisible(True) - else: - self.https_settings_group_box.setChecked(Settings().value(self.settings_section + '/https enabled')) - self.https_settings_group_box.setEnabled(True) - self.https_error_label.setVisible(False) self.user_login_group_box.setChecked(Settings().value(self.settings_section + '/authentication enabled')) self.user_id.setText(Settings().value(self.settings_section + '/user id')) self.password.setText(Settings().value(self.settings_section + '/password')) self.set_urls() - self.https_changed() def save(self): """ Save the configuration and update the server configuration if necessary """ if Settings().value(self.settings_section + '/ip address') != self.address_edit.text() or \ - Settings().value(self.settings_section + '/port') != self.port_spin_box.value() or \ - Settings().value(self.settings_section + '/https port') != self.https_port_spin_box.value() or \ - Settings().value(self.settings_section + '/https enabled') != \ - self.https_settings_group_box.isChecked(): + Settings().value(self.settings_section + '/port') != self.port_spin_box.value(): self.settings_form.register_post_process('remotes_config_updated') Settings().setValue(self.settings_section + '/port', self.port_spin_box.value()) - Settings().setValue(self.settings_section + '/https port', self.https_port_spin_box.value()) - Settings().setValue(self.settings_section + '/https enabled', self.https_settings_group_box.isChecked()) Settings().setValue(self.settings_section + '/ip address', self.address_edit.text()) Settings().setValue(self.settings_section + '/twelve hour', self.twelve_hour) Settings().setValue(self.settings_section + '/thumbnails', self.thumbnails) @@ -317,12 +247,6 @@ class RemoteTab(SettingsTab): if check_state == QtCore.Qt.Checked: self.thumbnails = True - def https_changed(self): - """ - Invert the HTTP group box based on Https group settings - """ - self.http_settings_group_box.setEnabled(not self.https_settings_group_box.isChecked()) - def generate_icon(self): """ Generate icon for main window @@ -330,12 +254,6 @@ class RemoteTab(SettingsTab): self.remote_server_icon.hide() icon = QtGui.QImage(':/remote/network_server.png') icon = icon.scaled(80, 80, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation) - if self.is_secure: - overlay = QtGui.QImage(':/remote/network_ssl.png') - overlay = overlay.scaled(60, 60, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation) - painter = QtGui.QPainter(icon) - painter.drawImage(0, 0, overlay) - painter.end() if Settings().value(self.settings_section + '/authentication enabled'): overlay = QtGui.QImage(':/remote/network_auth.png') overlay = overlay.scaled(60, 60, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation) From 0e5462199967bdffc99794c8b0fcc804b5163d1d Mon Sep 17 00:00:00 2001 From: Tim Bentley Date: Sat, 4 Mar 2017 10:28:07 +0000 Subject: [PATCH 25/26] test fix --- .../openlp_plugins/remotes/test_remotetab.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/tests/functional/openlp_plugins/remotes/test_remotetab.py b/tests/functional/openlp_plugins/remotes/test_remotetab.py index 6965503a2..76ce095fe 100644 --- a/tests/functional/openlp_plugins/remotes/test_remotetab.py +++ b/tests/functional/openlp_plugins/remotes/test_remotetab.py @@ -37,8 +37,6 @@ from tests.helpers.testmixin import TestMixin __default_settings__ = { 'remotes/twelve hour': True, 'remotes/port': 4316, - 'remotes/https port': 4317, - 'remotes/https enabled': False, 'remotes/user id': 'openlp', 'remotes/password': 'password', 'remotes/authentication enabled': False, @@ -114,10 +112,6 @@ class TestRemoteTab(TestCase, TestMixin): self.form.set_urls() # THEN: the following screen values should be set self.assertEqual(self.form.address_edit.text(), ZERO_URL, 'The default URL should be set on the screen') - self.assertEqual(self.form.https_settings_group_box.isEnabled(), False, - 'The Https box should not be enabled') - self.assertEqual(self.form.https_settings_group_box.isChecked(), False, - 'The Https checked box should note be Checked') self.assertEqual(self.form.user_login_group_box.isChecked(), False, 'The authentication box should not be enabled') @@ -143,7 +137,3 @@ class TestRemoteTab(TestCase, TestMixin): # THEN: the following screen values should be set self.assertEqual(self.form.http_settings_group_box.isEnabled(), True, 'The Http group box should be enabled') - self.assertEqual(self.form.https_settings_group_box.isChecked(), False, - 'The Https checked box should be Checked') - self.assertEqual(self.form.https_settings_group_box.isEnabled(), True, - 'The Https box should be enabled') From df77c3ebd9dd2bbf3855f9499a027eb3e663e3f0 Mon Sep 17 00:00:00 2001 From: Tim Bentley Date: Sat, 4 Mar 2017 16:51:51 +0000 Subject: [PATCH 26/26] add test --- .../openlp_plugins/remotes/test_remotetab.py | 37 ++++++++----------- .../openlp_plugins/remotes/test_router.py | 2 - tests/resources/remotes/openlp.crt | 0 tests/resources/remotes/openlp.key | 0 4 files changed, 16 insertions(+), 23 deletions(-) delete mode 100644 tests/resources/remotes/openlp.crt delete mode 100644 tests/resources/remotes/openlp.key diff --git a/tests/functional/openlp_plugins/remotes/test_remotetab.py b/tests/functional/openlp_plugins/remotes/test_remotetab.py index 76ce095fe..2dfd41aa3 100644 --- a/tests/functional/openlp_plugins/remotes/test_remotetab.py +++ b/tests/functional/openlp_plugins/remotes/test_remotetab.py @@ -83,7 +83,6 @@ class TestRemoteTab(TestCase, TestMixin): """ Test the get_ip_address function with given ip address """ - # GIVEN: A mocked location # GIVEN: An ip address given_ip = '192.168.1.1' # WHEN: the default ip address is given @@ -115,25 +114,21 @@ class TestRemoteTab(TestCase, TestMixin): self.assertEqual(self.form.user_login_group_box.isChecked(), False, 'The authentication box should not be enabled') - def test_set_certificate_urls(self): + def test_set_urls(self): """ - Test the set_urls function with certificate available + Test the set_url function to generate correct url links """ - # GIVEN: A mocked location - with patch('openlp.core.common.Settings') as mocked_class, \ - patch('openlp.core.common.applocation.AppLocation.get_directory') as mocked_get_directory, \ - patch('openlp.core.common.check_directory_exists') as mocked_check_directory_exists, \ - patch('openlp.core.common.applocation.os') as mocked_os: - # GIVEN: A mocked out Settings class and a mocked out AppLocation.get_directory() - mocked_settings = mocked_class.return_value - mocked_settings.contains.return_value = False - mocked_get_directory.return_value = TEST_PATH - mocked_check_directory_exists.return_value = True - mocked_os.path.normpath.return_value = TEST_PATH - - # WHEN: when the set_urls is called having reloaded the form. - self.form.load() - self.form.set_urls() - # THEN: the following screen values should be set - self.assertEqual(self.form.http_settings_group_box.isEnabled(), True, - 'The Http group box should be enabled') + # GIVEN: An ip address + self.form.address_edit.setText('192.168.1.1') + # WHEN: the urls are generated + self.form.set_urls() + # THEN: the following links are returned + self.assertEqual(self.form.remote_url.text(), + "http://192.168.1.1:4316/", + 'The return value should be a fully formed link') + self.assertEqual(self.form.stage_url.text(), + "http://192.168.1.1:4316/stage", + 'The return value should be a fully formed stage link') + self.assertEqual(self.form.live_url.text(), + "http://192.168.1.1:4316/main", + 'The return value should be a fully formed main link') diff --git a/tests/functional/openlp_plugins/remotes/test_router.py b/tests/functional/openlp_plugins/remotes/test_router.py index 6f1f27740..670f100de 100644 --- a/tests/functional/openlp_plugins/remotes/test_router.py +++ b/tests/functional/openlp_plugins/remotes/test_router.py @@ -35,8 +35,6 @@ from tests.helpers.testmixin import TestMixin __default_settings__ = { 'remotes/twelve hour': True, 'remotes/port': 4316, - 'remotes/https port': 4317, - 'remotes/https enabled': False, 'remotes/user id': 'openlp', 'remotes/password': 'password', 'remotes/authentication enabled': False, diff --git a/tests/resources/remotes/openlp.crt b/tests/resources/remotes/openlp.crt deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/resources/remotes/openlp.key b/tests/resources/remotes/openlp.key deleted file mode 100644 index e69de29bb..000000000