merge lastest trunk

This commit is contained in:
Tomas Groth 2017-03-15 20:51:10 +01:00
commit 31ee3999c0
26 changed files with 2422 additions and 1270 deletions

View File

@ -217,7 +217,9 @@ class Settings(QtCore.QSettings):
('advanced/default image', 'core/logo file', []), # Default image renamed + moved to general after 2.4. ('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/escapeItem', 'shortcuts/desktopScreenEnable', []), # Escape item was removed in 2.6.
('shortcuts/offlineHelpItem', 'shortcuts/userManualItem', []), # Online and Offline help were combined 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 @staticmethod

View File

@ -197,14 +197,9 @@ class MediaManagerItem(QtWidgets.QWidget, RegistryProperties):
""" """
# Add the List widget # Add the List widget
self.list_view = ListWidgetWithDnD(self, self.plugin.name) 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)) self.list_view.setObjectName('{name}ListView'.format(name=self.plugin.name))
# Add to page_layout # Add to page_layout
self.page_layout.addWidget(self.list_view) 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: if self.has_edit_icon:
create_widget_action(self.list_view, create_widget_action(self.list_view,
text=self.plugin.get_string(StringContent.Edit)['title'], text=self.plugin.get_string(StringContent.Edit)['title'],

View File

@ -40,6 +40,11 @@ class ListWidgetWithDnD(QtWidgets.QListWidget):
super().__init__(parent) super().__init__(parent)
self.mime_data_text = name self.mime_data_text = name
self.no_results_text = UiStrings().NoResults 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): def activateDnD(self):
""" """
@ -49,13 +54,15 @@ class ListWidgetWithDnD(QtWidgets.QListWidget):
self.setDragDropMode(QtWidgets.QAbstractItemView.DragDrop) self.setDragDropMode(QtWidgets.QAbstractItemView.DragDrop)
Registry().register_function(('%s_dnd' % self.mime_data_text), self.parent().load_file) 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' 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 :param search_while_typing: True if we want to display the customised message
:return: None :return: None
""" """
if self.locked and not override_lock:
return
if search_while_typing: if search_while_typing:
self.no_results_text = UiStrings().ShortResults self.no_results_text = UiStrings().ShortResults
else: else:

View File

@ -39,7 +39,7 @@ class Ui_SettingsDialog(object):
""" """
settings_dialog.setObjectName('settings_dialog') settings_dialog.setObjectName('settings_dialog')
settings_dialog.setWindowIcon(build_icon(':/icon/openlp-logo.svg')) 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 = QtWidgets.QGridLayout(settings_dialog)
self.dialog_layout.setObjectName('dialog_layout') self.dialog_layout.setObjectName('dialog_layout')
self.dialog_layout.setContentsMargins(8, 8, 8, 8) self.dialog_layout.setContentsMargins(8, 8, 8, 8)

View File

@ -46,8 +46,7 @@ __default_settings__ = {
'bibles/is verse number visible': True, 'bibles/is verse number visible': True,
'bibles/display new chapter': False, 'bibles/display new chapter': False,
'bibles/second bibles': True, 'bibles/second bibles': True,
'bibles/advanced bible': '', 'bibles/primary bible': '',
'bibles/quick bible': '',
'bibles/proxy name': '', 'bibles/proxy name': '',
'bibles/proxy address': '', 'bibles/proxy address': '',
'bibles/proxy username': '', 'bibles/proxy username': '',

View File

@ -230,7 +230,7 @@ def update_reference_separators():
REFERENCE_MATCHES['range_separator'] = re.compile(REFERENCE_SEPARATORS['sep_l'], re.UNICODE) REFERENCE_MATCHES['range_separator'] = re.compile(REFERENCE_SEPARATORS['sep_l'], re.UNICODE)
# full reference match: <book>(<range>(,(?!$)|(?=$)))+ # full reference match: <book>(<range>(,(?!$)|(?=$)))+
REFERENCE_MATCHES['full'] = \ REFERENCE_MATCHES['full'] = \
re.compile('^\s*(?!\s)(?P<book>[\d]*[^\d]+)(?<!\s)\s*' re.compile('^\s*(?!\s)(?P<book>[\d]*[^\d\.]+)\.*(?<!\s)\s*'
'(?P<ranges>(?:%(range_regex)s(?:%(sep_l)s(?!\s*$)|(?=\s*$)))+)\s*$' '(?P<ranges>(?:%(range_regex)s(?:%(sep_l)s(?!\s*$)|(?=\s*$)))+)\s*$'
% dict(list(REFERENCE_SEPARATORS.items()) + [('range_regex', range_regex)]), re.UNICODE) % 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<book>[\d]*[^\d]+)(?<!\s)\s*`` ``^\s*(?!\s)(?P<book>[\d]*[^\d]+)(?<!\s)\s*``
The ``book`` group starts with the first non-whitespace character. There are optional leading digits followed by The ``book`` group starts with the first non-whitespace character. There are optional leading digits followed by
non-digits. The group ends before the whitspace in front of the next digit. non-digits. The group ends before the whitspace, or a full stop in front of the next digit.
``(?P<ranges>(?:%(range_regex)s(?:%(sep_l)s(?!\s*$)|(?=\s*$)))+)\s*$`` ``(?P<ranges>(?:%(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 The second group contains all ``ranges``. This can be multiple declarations of range_regex separated by a list

View File

@ -36,7 +36,7 @@ from sqlalchemy.orm.exc import UnmappedClassError
from openlp.core.common import AppLocation, translate, clean_filename from openlp.core.common import AppLocation, translate, clean_filename
from openlp.core.lib.db import BaseModel, init_db, Manager from openlp.core.lib.db import BaseModel, init_db, Manager
from openlp.core.lib.ui import critical_error_message_box 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__) log = logging.getLogger(__name__)
@ -52,9 +52,15 @@ class BibleMeta(BaseModel):
class Book(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): class Verse(BaseModel):
@ -380,13 +386,12 @@ class BibleDB(Manager):
""" """
log.debug('BibleDB.verse_search("{text}")'.format(text=text)) log.debug('BibleDB.verse_search("{text}")'.format(text=text))
verses = self.session.query(Verse) verses = self.session.query(Verse)
# TODO: Find out what this is doing before converting to format()
if text.find(',') > -1: 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] or_clause = [Verse.text.like(keyword) for keyword in keywords]
verses = verses.filter(or_(*or_clause)) verses = verses.filter(or_(*or_clause))
else: 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: for keyword in keywords:
verses = verses.filter(Verse.text.like(keyword)) verses = verses.filter(Verse.text.like(keyword))
verses = verses.all() verses = verses.all()

View File

@ -250,14 +250,20 @@ class BibleManager(OpenLPMixin, RegistryProperties):
'"{book}", "{chapter}")'.format(bible=bible, book=book_ref_id, chapter=chapter)) '"{book}", "{chapter}")'.format(bible=bible, book=book_ref_id, chapter=chapter))
return self.db_cache[bible].get_verse_count(book_ref_id, 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 Parses a scripture reference, fetches the verses from the Bible
specified, and returns a list of ``Verse`` objects. specified, and returns a list of ``Verse`` objects.
:param bible: Unicode. The Bible to use. :param bible: Unicode. The Bible to use.
:param verse_text: :param verse_text:
Unicode. The scripture reference. Valid scripture references are: String. The scripture reference. Valid scripture references are:
- Genesis 1 - Genesis 1
- Genesis 1-2 - Genesis 1-2
@ -271,22 +277,9 @@ class BibleManager(OpenLPMixin, RegistryProperties):
For second bible this is necessary. For second bible this is necessary.
:param show_error: :param show_error:
""" """
# If no bibles are installed, message is given. if not bible or not ref_list:
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:
return None return None
return self.db_cache[bible].get_verses(ref_list, show_error)
def get_language_selection(self, bible): def get_language_selection(self, bible):
""" """
@ -308,7 +301,7 @@ class BibleManager(OpenLPMixin, RegistryProperties):
language_selection = LanguageSelection.Application language_selection = LanguageSelection.Application
return language_selection 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. Does a verse search for the given bible and text.
@ -325,20 +318,14 @@ class BibleManager(OpenLPMixin, RegistryProperties):
return None return None
# Check if the bible or second_bible is a web bible. # Check if the bible or second_bible is a web bible.
web_bible = self.db_cache[bible].get_object(BibleMeta, 'download_source') web_bible = self.db_cache[bible].get_object(BibleMeta, 'download_source')
second_web_bible = '' if web_bible:
if second_bible:
second_web_bible = self.db_cache[second_bible].get_object(BibleMeta, 'download_source')
if web_bible or second_web_bible:
# If either Bible is Web, cursor is reset to normal and message is given. # If either Bible is Web, cursor is reset to normal and message is given.
self.application.set_normal_cursor() self.application.set_normal_cursor()
self.main_window.information_message( self.main_window.information_message(
translate('BiblesPlugin.BibleManager', 'Web Bible cannot be used in Text Search'), translate('BiblesPlugin.BibleManager', 'Web Bible cannot be used in Text Search'),
translate('BiblesPlugin.BibleManager', 'Text Search is not available with Web Bibles.\n' translate('BiblesPlugin.BibleManager', 'Text Search is not available with Web Bibles.\n'
'Please use the Scripture Reference Search instead.\n\n' 'Please use the Scripture Reference Search instead.\n\n'
'This means that the currently used Bible\nor Second Bible ' 'This means that the currently selected Bible is a Web Bible.')
'is installed as Web Bible.\n\n'
'If you were trying to perform a Reference search\nin Combined '
'Search, your reference is invalid.')
) )
return None return None
# Shorter than 3 char searches break OpenLP with very long search times, thus they are blocked. # 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: else:
return None 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, full_license, book_name_language=None): def save_meta_data(self, bible, version, copyright, permissions, full_license, book_name_language=None):
""" """
Saves the bibles meta data. Saves the bibles meta data.

File diff suppressed because it is too large Load Diff

View File

@ -111,14 +111,9 @@ class OpenLPServer(RegistryProperties):
self.address = address self.address = address
self.is_secure = Settings().value(self.settings_section + '/https enabled') self.is_secure = Settings().value(self.settings_section + '/https enabled')
self.needs_authentication = Settings().value(self.settings_section + '/authentication enabled') self.needs_authentication = Settings().value(self.settings_section + '/authentication enabled')
if self.is_secure: port = Settings().value(self.settings_section + '/port')
port = Settings().value(self.settings_section + '/https port') self.port = port
self.port = port self.start_server_instance(address, port, ThreadingHTTPServer)
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)
if hasattr(self, 'httpd') and self.httpd: if hasattr(self, 'httpd') and self.httpd:
self.httpd.serve_forever() self.httpd.serve_forever()
else: else:
@ -158,19 +153,3 @@ class OpenLPServer(RegistryProperties):
self.http_thread.stop() self.http_thread.stop()
self.httpd = None self.httpd = None
log.debug('Stopped the server.') 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()

View File

@ -94,48 +94,6 @@ class RemoteTab(SettingsTab):
self.live_url.setOpenExternalLinks(True) self.live_url.setOpenExternalLinks(True)
self.http_setting_layout.addRow(self.live_url_label, self.live_url) self.http_setting_layout.addRow(self.live_url_label, self.live_url)
self.left_layout.addWidget(self.http_settings_group_box) 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.chords_https_url_label = QtWidgets.QLabel(self.http_settings_group_box)
self.chords_https_url_label.setObjectName('chords_https_url_label')
self.chords_https_url = QtWidgets.QLabel(self.https_settings_group_box)
self.chords_https_url.setObjectName('chords_https_url')
self.chords_https_url.setOpenExternalLinks(True)
self.https_settings_layout.addRow(self.chords_https_url_label, self.chords_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 = QtWidgets.QGroupBox(self.left_column)
self.user_login_group_box.setCheckable(True) self.user_login_group_box.setCheckable(True)
self.user_login_group_box.setChecked(False) self.user_login_group_box.setChecked(False)
@ -189,8 +147,6 @@ class RemoteTab(SettingsTab):
self.thumbnails_check_box.stateChanged.connect(self.on_thumbnails_check_box_changed) self.thumbnails_check_box.stateChanged.connect(self.on_thumbnails_check_box_changed)
self.address_edit.textChanged.connect(self.set_urls) self.address_edit.textChanged.connect(self.set_urls)
self.port_spin_box.valueChanged.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): def retranslateUi(self):
self.server_settings_group_box.setTitle(translate('RemotePlugin.RemoteTab', 'Server Settings')) self.server_settings_group_box.setTitle(translate('RemotePlugin.RemoteTab', 'Server Settings'))
@ -213,15 +169,6 @@ class RemoteTab(SettingsTab):
translate('RemotePlugin.RemoteTab', translate('RemotePlugin.RemoteTab',
'Scan the QR code or click <a href="{qr}">download</a> to install the iOS app from the App ' 'Scan the QR code or click <a href="{qr}">download</a> to install the iOS app from the App '
'Store.').format(qr='https://itunes.apple.com/app/id1096218725')) '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.chords_https_url_label.setText(self.chords_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_login_group_box.setTitle(translate('RemotePlugin.RemoteTab', 'User Authentication'))
self.user_id_label.setText(translate('RemotePlugin.RemoteTab', 'User id:')) self.user_id_label.setText(translate('RemotePlugin.RemoteTab', 'User id:'))
self.password_label.setText(translate('RemotePlugin.RemoteTab', 'Password:')) self.password_label.setText(translate('RemotePlugin.RemoteTab', 'Password:'))
@ -232,21 +179,11 @@ class RemoteTab(SettingsTab):
""" """
ip_address = self.get_ip_address(self.address_edit.text()) 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()) 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('<a href="{url}">{url}</a>'.format(url=http_url)) self.remote_url.setText('<a href="{url}">{url}</a>'.format(url=http_url))
self.remote_https_url.setText('<a href="{url}">{url}</a>'.format(url=https_url))
http_url_temp = http_url + 'stage' http_url_temp = http_url + 'stage'
https_url_temp = https_url + 'stage'
self.stage_url.setText('<a href="{url}">{url}</a>'.format(url=http_url_temp)) self.stage_url.setText('<a href="{url}">{url}</a>'.format(url=http_url_temp))
self.stage_https_url.setText('<a href="{url}">{url}</a>'.format(url=https_url_temp))
http_url_temp = http_url + 'chords'
https_url_temp = https_url + 'chords'
self.chords_url.setText('<a href="{url}">{url}</a>'.format(url=http_url_temp))
self.chords_https_url.setText('<a href="{url}">{url}</a>'.format(url=https_url_temp))
http_url_temp = http_url + 'main' http_url_temp = http_url + 'main'
https_url_temp = https_url + 'main'
self.live_url.setText('<a href="{url}">{url}</a>'.format(url=http_url_temp)) self.live_url.setText('<a href="{url}">{url}</a>'.format(url=http_url_temp))
self.live_https_url.setText('<a href="{url}">{url}</a>'.format(url=https_url_temp))
def get_ip_address(self, ip_address): def get_ip_address(self, ip_address):
""" """
@ -272,43 +209,25 @@ class RemoteTab(SettingsTab):
""" """
Load the configuration and update the server configuration if necessary 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.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.address_edit.setText(Settings().value(self.settings_section + '/ip address'))
self.twelve_hour = Settings().value(self.settings_section + '/twelve hour') self.twelve_hour = Settings().value(self.settings_section + '/twelve hour')
self.twelve_hour_check_box.setChecked(self.twelve_hour) self.twelve_hour_check_box.setChecked(self.twelve_hour)
self.thumbnails = Settings().value(self.settings_section + '/thumbnails') self.thumbnails = Settings().value(self.settings_section + '/thumbnails')
self.thumbnails_check_box.setChecked(self.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_login_group_box.setChecked(Settings().value(self.settings_section + '/authentication enabled'))
self.user_id.setText(Settings().value(self.settings_section + '/user id')) self.user_id.setText(Settings().value(self.settings_section + '/user id'))
self.password.setText(Settings().value(self.settings_section + '/password')) self.password.setText(Settings().value(self.settings_section + '/password'))
self.set_urls() self.set_urls()
self.https_changed()
def save(self): def save(self):
""" """
Save the configuration and update the server configuration if necessary Save the configuration and update the server configuration if necessary
""" """
if Settings().value(self.settings_section + '/ip address') != self.address_edit.text() or \ 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 + '/port') != self.port_spin_box.value():
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():
self.settings_form.register_post_process('remotes_config_updated') 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 + '/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 + '/ip address', self.address_edit.text())
Settings().setValue(self.settings_section + '/twelve hour', self.twelve_hour) Settings().setValue(self.settings_section + '/twelve hour', self.twelve_hour)
Settings().setValue(self.settings_section + '/thumbnails', self.thumbnails) Settings().setValue(self.settings_section + '/thumbnails', self.thumbnails)
@ -335,12 +254,6 @@ class RemoteTab(SettingsTab):
if check_state == QtCore.Qt.Checked: if check_state == QtCore.Qt.Checked:
self.thumbnails = True 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): def generate_icon(self):
""" """
Generate icon for main window Generate icon for main window
@ -348,12 +261,6 @@ class RemoteTab(SettingsTab):
self.remote_server_icon.hide() self.remote_server_icon.hide()
icon = QtGui.QImage(':/remote/network_server.png') icon = QtGui.QImage(':/remote/network_server.png')
icon = icon.scaled(80, 80, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation) 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'): if Settings().value(self.settings_section + '/authentication enabled'):
overlay = QtGui.QImage(':/remote/network_auth.png') overlay = QtGui.QImage(':/remote/network_auth.png')
overlay = overlay.scaled(60, 60, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation) overlay = overlay.scaled(60, 60, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation)

View File

@ -124,7 +124,8 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
cache.append(obj.name) cache.append(obj.name)
combo.setItemData(row, obj.id) combo.setItemData(row, obj.id)
set_case_insensitive_completer(cache, combo) set_case_insensitive_completer(cache, combo)
combo.setEditText('') combo.setCurrentIndex(-1)
combo.setCurrentText('')
def _add_author_to_list(self, author, author_type): 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_combo_box.setItemData(row, author.id)
self.authors.append(author.display_name) self.authors.append(author.display_name)
set_case_insensitive_completer(self.authors, self.authors_combo_box) 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 # Types
self.author_types_combo_box.clear() 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.themes.sort(key=get_theme_key)
self.theme_combo_box.addItems(theme_list) self.theme_combo_box.addItems(theme_list)
set_case_insensitive_completer(self.themes, self.theme_combo_box) 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): def load_media_files(self):
""" """
@ -441,7 +444,8 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
self.load_topics() self.load_topics()
self.load_songbooks() self.load_songbooks()
self.load_media_files() 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 # it's a new song to preview is not possible
self.preview_button.setVisible(False) 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)) find_and_set_in_combo_box(self.theme_combo_box, str(self.song.theme_name))
else: else:
# Clear the theme combo box in case it was previously set (bug #1212801) # Clear the theme combo box in case it was previously set (bug #1212801)
self.theme_combo_box.setEditText('') self.theme_combo_box.setCurrentIndex(-1)
self.theme_combo_box.setCurrentIndex(0) self.theme_combo_box.setCurrentText('')
self.copyright_edit.setText(self.song.copyright if self.song.copyright else '') 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.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 '') 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()) item = int(self.authors_combo_box.currentIndex())
text = self.authors_combo_box.currentText().strip(' \r\n\t') text = self.authors_combo_box.currentText().strip(' \r\n\t')
author_type = self.author_types_combo_box.itemData(self.author_types_combo_box.currentIndex()) 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 if item == -1 and text:
# 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 QtWidgets.QMessageBox.question( if QtWidgets.QMessageBox.question(
self, self,
translate('SongsPlugin.EditSongForm', 'Add Author'), translate('SongsPlugin.EditSongForm', 'Add Author'),
@ -590,10 +589,11 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
self.manager.save_object(author) self.manager.save_object(author)
self._add_author_to_list(author, author_type) self._add_author_to_list(author, author_type)
self.load_authors() self.load_authors()
self.authors_combo_box.setEditText('') self.authors_combo_box.setCurrentIndex(-1)
self.authors_combo_box.setCurrentText('')
else: else:
return return
elif item > 0: elif item >= 0:
item_id = (self.authors_combo_box.itemData(item)) item_id = (self.authors_combo_box.itemData(item))
author = self.manager.get_object(Author, item_id) author = self.manager.get_object(Author, item_id)
if self.authors_list_view.findItems(author.get_display_name(author_type), QtCore.Qt.MatchExactly): 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.')) message=translate('SongsPlugin.EditSongForm', 'This author is already in the list.'))
else: else:
self._add_author_to_list(author, author_type) 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: else:
QtWidgets.QMessageBox.warning( QtWidgets.QMessageBox.warning(
self, UiStrings().NISs, self, UiStrings().NISs,
@ -653,7 +654,7 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
def on_topic_add_button_clicked(self): def on_topic_add_button_clicked(self):
item = int(self.topics_combo_box.currentIndex()) item = int(self.topics_combo_box.currentIndex())
text = self.topics_combo_box.currentText() text = self.topics_combo_box.currentText()
if item == 0 and text: if item == -1 and text:
if QtWidgets.QMessageBox.question( if QtWidgets.QMessageBox.question(
self, translate('SongsPlugin.EditSongForm', 'Add Topic'), self, translate('SongsPlugin.EditSongForm', 'Add Topic'),
translate('SongsPlugin.EditSongForm', 'This topic does not exist, do you want to add it?'), 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) topic_item.setData(QtCore.Qt.UserRole, topic.id)
self.topics_list_view.addItem(topic_item) self.topics_list_view.addItem(topic_item)
self.load_topics() self.load_topics()
self.topics_combo_box.setEditText('') self.topics_combo_box.setCurrentIndex(-1)
self.topics_combo_box.setCurrentText('')
else: else:
return return
elif item > 0: elif item >= 0:
item_id = (self.topics_combo_box.itemData(item)) item_id = (self.topics_combo_box.itemData(item))
topic = self.manager.get_object(Topic, item_id) topic = self.manager.get_object(Topic, item_id)
if self.topics_list_view.findItems(str(topic.name), QtCore.Qt.MatchExactly): 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 = QtWidgets.QListWidgetItem(str(topic.name))
topic_item.setData(QtCore.Qt.UserRole, topic.id) topic_item.setData(QtCore.Qt.UserRole, topic.id)
self.topics_list_view.addItem(topic_item) self.topics_list_view.addItem(topic_item)
self.topics_combo_box.setEditText('') self.topics_combo_box.setCurrentIndex(-1)
self.topics_combo_box.setCurrentText('')
else: else:
QtWidgets.QMessageBox.warning( QtWidgets.QMessageBox.warning(
self, UiStrings().NISs, self, UiStrings().NISs,
@ -698,7 +701,7 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
def on_songbook_add_button_clicked(self): def on_songbook_add_button_clicked(self):
item = int(self.songbooks_combo_box.currentIndex()) item = int(self.songbooks_combo_box.currentIndex())
text = self.songbooks_combo_box.currentText() text = self.songbooks_combo_box.currentText()
if item == 0 and text: if item == -1 and text:
if QtWidgets.QMessageBox.question( if QtWidgets.QMessageBox.question(
self, translate('SongsPlugin.EditSongForm', 'Add Songbook'), self, translate('SongsPlugin.EditSongForm', 'Add Songbook'),
translate('SongsPlugin.EditSongForm', 'This Songbook does not exist, do you want to add it?'), 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.manager.save_object(songbook)
self.add_songbook_entry_to_list(songbook.id, songbook.name, self.songbook_entry_edit.text()) self.add_songbook_entry_to_list(songbook.id, songbook.name, self.songbook_entry_edit.text())
self.load_songbooks() self.load_songbooks()
self.songbooks_combo_box.setEditText('') self.songbooks_combo_box.setCurrentIndex(-1)
self.songbooks_combo_box.setCurrentText('')
self.songbook_entry_edit.clear() self.songbook_entry_edit.clear()
else: else:
return return
elif item > 0: elif item >= 0:
item_id = (self.songbooks_combo_box.itemData(item)) item_id = (self.songbooks_combo_box.itemData(item))
songbook = self.manager.get_object(Book, item_id) songbook = self.manager.get_object(Book, item_id)
if self.songbooks_list_view.findItems(str(songbook.name), QtCore.Qt.MatchExactly): 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.')) message=translate('SongsPlugin.EditSongForm', 'This Songbook is already in the list.'))
else: else:
self.add_songbook_entry_to_list(songbook.id, songbook.name, self.songbook_entry_edit.text()) 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() self.songbook_entry_edit.clear()
else: else:
QtWidgets.QMessageBox.warning( QtWidgets.QMessageBox.warning(

0
openlp/plugins/songs/forms/songselectform.py Executable file → Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 632 B

View File

@ -29,6 +29,7 @@
<file>image_new_group.png</file> <file>image_new_group.png</file>
</qresource> </qresource>
<qresource prefix="bibles"> <qresource prefix="bibles">
<file>bibles_book_sort.png</file>
<file>bibles_search_combined.png</file> <file>bibles_search_combined.png</file>
<file>bibles_search_text.png</file> <file>bibles_search_text.png</file>
<file>bibles_search_reference.png</file> <file>bibles_search_reference.png</file>

View File

@ -151,3 +151,17 @@ class TestSettingsForm(TestCase):
# THEN: The general tab's cancel() method should have been called, but not the themes tab # THEN: The general tab's cancel() method should have been called, but not the themes tab
mocked_general_cancel.assert_called_with() mocked_general_cancel.assert_called_with()
self.assertEqual(0, mocked_theme_cancel.call_count, 'The Themes tab\'s cancel() should not have been called') 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

View File

@ -33,6 +33,37 @@ class TestListWidgetWithDnD(TestCase):
""" """
Test the :class:`~openlp.core.lib.listwidgetwithdnd.ListWidgetWithDnD` class 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): def test_clear(self):
""" """
Test the clear method when called without any arguments. Test the clear method when called without any arguments.

View File

@ -25,11 +25,12 @@ This module contains tests for the lib submodule of the Bibles plugin.
from unittest import TestCase from unittest import TestCase
from openlp.plugins.bibles import lib from openlp.plugins.bibles import lib
from openlp.plugins.bibles.lib import SearchResults from openlp.plugins.bibles.lib import SearchResults, get_reference_match
from tests.functional import patch 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. Test the functions in the :mod:`lib` module.
""" """
@ -60,6 +61,142 @@ class TestLib(TestCase):
self.assertEqual(separators[key], value) self.assertEqual(separators[key], value)
mocked_update_reference_separators.assert_called_once_with() 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): def test_search_results_creation(self):
""" """
Test the creation and construction of the SearchResults class Test the creation and construction of the SearchResults class

File diff suppressed because it is too large Load Diff

View File

@ -37,8 +37,6 @@ from tests.helpers.testmixin import TestMixin
__default_settings__ = { __default_settings__ = {
'remotes/twelve hour': True, 'remotes/twelve hour': True,
'remotes/port': 4316, 'remotes/port': 4316,
'remotes/https port': 4317,
'remotes/https enabled': False,
'remotes/user id': 'openlp', 'remotes/user id': 'openlp',
'remotes/password': 'password', 'remotes/password': 'password',
'remotes/authentication enabled': False, 'remotes/authentication enabled': False,
@ -85,7 +83,6 @@ class TestRemoteTab(TestCase, TestMixin):
""" """
Test the get_ip_address function with given ip address Test the get_ip_address function with given ip address
""" """
# GIVEN: A mocked location
# GIVEN: An ip address # GIVEN: An ip address
given_ip = '192.168.1.1' given_ip = '192.168.1.1'
# WHEN: the default ip address is given # WHEN: the default ip address is given
@ -114,36 +111,24 @@ class TestRemoteTab(TestCase, TestMixin):
self.form.set_urls() self.form.set_urls()
# THEN: the following screen values should be set # 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.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, self.assertEqual(self.form.user_login_group_box.isChecked(), False,
'The authentication box should not be enabled') '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 # GIVEN: An ip address
with patch('openlp.core.common.Settings') as mocked_class, \ self.form.address_edit.setText('192.168.1.1')
patch('openlp.core.common.applocation.AppLocation.get_directory') as mocked_get_directory, \ # WHEN: the urls are generated
patch('openlp.core.common.check_directory_exists') as mocked_check_directory_exists, \ self.form.set_urls()
patch('openlp.core.common.applocation.os') as mocked_os: # THEN: the following links are returned
# GIVEN: A mocked out Settings class and a mocked out AppLocation.get_directory() self.assertEqual(self.form.remote_url.text(),
mocked_settings = mocked_class.return_value "<a href=\"http://192.168.1.1:4316/\">http://192.168.1.1:4316/</a>",
mocked_settings.contains.return_value = False 'The return value should be a fully formed link')
mocked_get_directory.return_value = TEST_PATH self.assertEqual(self.form.stage_url.text(),
mocked_check_directory_exists.return_value = True "<a href=\"http://192.168.1.1:4316/stage\">http://192.168.1.1:4316/stage</a>",
mocked_os.path.normpath.return_value = TEST_PATH 'The return value should be a fully formed stage link')
self.assertEqual(self.form.live_url.text(),
# WHEN: when the set_urls is called having reloaded the form. "<a href=\"http://192.168.1.1:4316/main\">http://192.168.1.1:4316/main</a>",
self.form.load() 'The return value should be a fully formed main link')
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')
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')

View File

@ -35,8 +35,6 @@ from tests.helpers.testmixin import TestMixin
__default_settings__ = { __default_settings__ = {
'remotes/twelve hour': True, 'remotes/twelve hour': True,
'remotes/port': 4316, 'remotes/port': 4316,
'remotes/https port': 4317,
'remotes/https enabled': False,
'remotes/user id': 'openlp', 'remotes/user id': 'openlp',
'remotes/password': 'password', 'remotes/password': 'password',
'remotes/authentication enabled': False, 'remotes/authentication enabled': False,

View File

@ -106,4 +106,5 @@ class TestEditSongForm(TestCase, TestMixin):
mocked_cache.append.assert_called_once_with('Charles') mocked_cache.append.assert_called_once_with('Charles')
mocked_combo.setItemData.assert_called_once_with(0, 1) mocked_combo.setItemData.assert_called_once_with(0, 1)
mocked_set_case_insensitive_completer.assert_called_once_with(mocked_cache, mocked_combo) 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('')

View File

@ -23,6 +23,7 @@
Package to test the openlp.plugins.songs.forms.authorsform package. Package to test the openlp.plugins.songs.forms.authorsform package.
""" """
from unittest import TestCase from unittest import TestCase
from unittest.mock import patch
from PyQt5 import QtWidgets from PyQt5 import QtWidgets
@ -138,3 +139,217 @@ class TestAuthorsForm(TestCase, TestMixin):
# THEN: The display_name_edit should have the correct value # 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') 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)

0
tests/resources/themes/Moss_on_tree.otz Executable file → Normal file
View File