Continuation of lp:~suutari-olli/openlp/bug-fixes-2-4-3

This branch fixes the following bugs:
Bug #1487788: Importing photos does not give focus to OpenLP
Bug #1512040: Loop tooltip gets stuck to "Stop playing..."
Bug #1513490: List of authors uses localized "and" instead of English
Bug #1624661: Missing DB in unmounted disk results in Traceback 

bzr-revno: 2706
This commit is contained in:
second@tgc.dk 2016-11-22 21:34:14 +01:00 committed by Tomas Groth
commit 20ab44be0f
6 changed files with 85 additions and 82 deletions

View File

@ -177,6 +177,38 @@ class OpenLP(OpenLPMixin, QtWidgets.QApplication):
self.shared_memory.create(1) self.shared_memory.create(1)
return False return False
def is_data_path_missing(self):
"""
Check if the data folder path exists.
"""
data_folder_path = AppLocation.get_data_path()
if not os.path.exists(data_folder_path):
log.critical('Database was not found in: ' + data_folder_path)
status = QtWidgets.QMessageBox.critical(None, translate('OpenLP', 'Data Directory Error'),
translate('OpenLP', 'OpenLP data folder was not found in:\n\n{path}'
'\n\nThe location of the data folder was '
'previously changed from the OpenLP\'s '
'default location. If the data was stored on '
'removable device, that device needs to be '
'made available.\n\nYou may reset the data '
'location back to the default location, '
'or you can try to make the current location '
'available.\n\nDo you want to reset to the '
'default data location? If not, OpenLP will be '
'closed so you can try to fix the the problem.')
.format(path=data_folder_path),
QtWidgets.QMessageBox.StandardButtons(QtWidgets.QMessageBox.Yes |
QtWidgets.QMessageBox.No),
QtWidgets.QMessageBox.No)
if status == QtWidgets.QMessageBox.No:
# If answer was "No", return "True", it will shutdown OpenLP in def main
log.info('User requested termination')
return True
# If answer was "Yes", remove the custom data path thus resetting the default location.
Settings().remove('advanced/data path')
log.info('Database location has been reset to the default settings.')
return False
def hook_exception(self, exc_type, value, traceback): def hook_exception(self, exc_type, value, traceback):
""" """
Add an exception hook so that any uncaught exceptions are displayed in this window rather than somewhere where Add an exception hook so that any uncaught exceptions are displayed in this window rather than somewhere where
@ -208,8 +240,8 @@ class OpenLP(OpenLPMixin, QtWidgets.QApplication):
# If data_version is different from the current version ask if we should backup the data folder # If data_version is different from the current version ask if we should backup the data folder
elif data_version != openlp_version: elif data_version != openlp_version:
if QtWidgets.QMessageBox.question(None, translate('OpenLP', 'Backup'), if QtWidgets.QMessageBox.question(None, translate('OpenLP', 'Backup'),
translate('OpenLP', 'OpenLP has been upgraded, do you want to create ' translate('OpenLP', 'OpenLP has been upgraded, do you want to create\n'
'a backup of OpenLPs data folder?'), 'a backup of the old data folder?'),
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No, QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No,
QtWidgets.QMessageBox.Yes) == QtWidgets.QMessageBox.Yes: QtWidgets.QMessageBox.Yes) == QtWidgets.QMessageBox.Yes:
# Create copy of data folder # Create copy of data folder
@ -223,8 +255,8 @@ class OpenLP(OpenLPMixin, QtWidgets.QApplication):
translate('OpenLP', 'Backup of the data folder failed!')) translate('OpenLP', 'Backup of the data folder failed!'))
return return
message = translate('OpenLP', message = translate('OpenLP',
'A backup of the data folder has been created' 'A backup of the data folder has been created at:\n\n'
'at {text}').format(text=data_folder_backup_path) '{text}').format(text=data_folder_backup_path)
QtWidgets.QMessageBox.information(None, translate('OpenLP', 'Backup'), message) QtWidgets.QMessageBox.information(None, translate('OpenLP', 'Backup'), message)
# Update the version in the settings # Update the version in the settings
@ -368,9 +400,13 @@ def main(args=None):
Registry.create() Registry.create()
Registry().register('application', application) Registry().register('application', application)
application.setApplicationVersion(get_application_version()['version']) application.setApplicationVersion(get_application_version()['version'])
# Instance check # Check if an instance of OpenLP is already running. Quit if there is a running instance and the user only wants one
if application.is_already_running(): if application.is_already_running():
sys.exit() sys.exit()
# If the custom data path is missing and the user wants to restore the data path, quit OpenLP.
if application.is_data_path_missing():
application.shared_memory.detach()
sys.exit()
# Remove/convert obsolete settings. # Remove/convert obsolete settings.
Settings().remove_obsolete_settings() Settings().remove_obsolete_settings()
# First time checks in settings # First time checks in settings

View File

@ -310,30 +310,23 @@ def expand_tags(text):
def create_separated_list(string_list): def create_separated_list(string_list):
""" """
Returns a string that represents a join of a list of strings with a localized separator. This function corresponds Returns a string that represents a join of a list of strings with a localized separator.
Localized separation will be done via the translate() function by the translators.
to QLocale::createSeparatedList which was introduced in Qt 4.8 and implements the algorithm from
http://www.unicode.org/reports/tr35/#ListPatterns
:param string_list: List of unicode strings :param string_list: List of unicode strings
:return: Formatted string
""" """
if LooseVersion(Qt.PYQT_VERSION_STR) >= LooseVersion('4.9') and LooseVersion(Qt.qVersion()) >= LooseVersion('4.8'): list_length = len(string_list)
return QtCore.QLocale().createSeparatedList(string_list) if list_length == 1:
if not string_list: list_to_string = string_list[0]
return '' elif list_length == 2:
elif len(string_list) == 1: list_to_string = translate('OpenLP.core.lib', '{one} and {two}').format(one=string_list[0], two=string_list[1])
return string_list[0] elif list_length > 2:
# TODO: Verify mocking of translate() test before conversion list_to_string = translate('OpenLP.core.lib', '{first} and {last}').format(first=', '.join(string_list[:-1]),
elif len(string_list) == 2: last=string_list[-1])
return translate('OpenLP.core.lib', '%s and %s',
'Locale list separator: 2 items') % (string_list[0], string_list[1])
else: else:
merged = translate('OpenLP.core.lib', '%s, and %s', list_to_string = ''
'Locale list separator: end') % (string_list[-2], string_list[-1]) return list_to_string
for index in reversed(list(range(1, len(string_list) - 2))):
merged = translate('OpenLP.core.lib', '%s, %s',
'Locale list separator: middle') % (string_list[index], merged)
return translate('OpenLP.core.lib', '%s, %s', 'Locale list separator: start') % (string_list[0], merged)
from .exceptions import ValidationError from .exceptions import ValidationError

View File

@ -397,27 +397,6 @@ class AdvancedTab(SettingsTab):
self.data_directory_cancel_button.hide() self.data_directory_cancel_button.hide()
# Since data location can be changed, make sure the path is present. # Since data location can be changed, make sure the path is present.
self.current_data_path = AppLocation.get_data_path() self.current_data_path = AppLocation.get_data_path()
if not os.path.exists(self.current_data_path):
log.error('Data path not found {path}'.format(path=self.current_data_path))
answer = QtWidgets.QMessageBox.critical(
self, translate('OpenLP.AdvancedTab', 'Data Directory Error'),
translate('OpenLP.AdvancedTab', 'OpenLP data directory was not found\n\n{path}\n\n'
'This data directory was previously changed from the OpenLP '
'default location. If the new location was on removable '
'media, that media needs to be made available.\n\n'
'Click "No" to stop loading OpenLP. allowing you to fix the the problem.\n\n'
'Click "Yes" to reset the data directory to the default '
'location.').format(path=self.current_data_path),
QtWidgets.QMessageBox.StandardButtons(QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No),
QtWidgets.QMessageBox.No)
if answer == QtWidgets.QMessageBox.No:
log.info('User requested termination')
self.main_window.clean_up()
sys.exit()
# Set data location to default.
settings.remove('advanced/data path')
self.current_data_path = AppLocation.get_data_path()
log.warning('User requested data path set to default {path}'.format(path=self.current_data_path))
self.data_directory_label.setText(os.path.abspath(self.current_data_path)) self.data_directory_label.setText(os.path.abspath(self.current_data_path))
# Don't allow data directory move if running portable. # Don't allow data directory move if running portable.
if settings.value('advanced/is portable'): if settings.value('advanced/is portable'):

View File

@ -26,7 +26,7 @@ import os
from PyQt5 import QtCore, QtGui, QtWidgets from PyQt5 import QtCore, QtGui, QtWidgets
from openlp.core.common import Registry from openlp.core.common import Registry, is_win
class TreeWidgetWithDnD(QtWidgets.QTreeWidget): class TreeWidgetWithDnD(QtWidgets.QTreeWidget):
@ -108,6 +108,11 @@ class TreeWidgetWithDnD(QtWidgets.QTreeWidget):
:param event: Handle of the event pint passed :param event: Handle of the event pint passed
""" """
# If we are on Windows, OpenLP window will not be set on top. For example, user can drag images to Library and
# the folder stays on top of the group creation box. This piece of code fixes this issue.
if is_win():
self.setWindowState(self.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive)
self.setWindowState(QtCore.Qt.WindowNoState)
if event.mimeData().hasUrls(): if event.mimeData().hasUrls():
event.setDropAction(QtCore.Qt.CopyAction) event.setDropAction(QtCore.Qt.CopyAction)
event.accept() event.accept()

View File

@ -722,8 +722,10 @@ class SlideController(DisplayController, RegistryProperties):
# Reset the button # Reset the button
self.play_slides_once.setChecked(False) self.play_slides_once.setChecked(False)
self.play_slides_once.setIcon(build_icon(':/media/media_time.png')) self.play_slides_once.setIcon(build_icon(':/media/media_time.png'))
self.play_slides_once.setText(UiStrings().PlaySlidesToEnd)
self.play_slides_loop.setChecked(False) self.play_slides_loop.setChecked(False)
self.play_slides_loop.setIcon(build_icon(':/media/media_time.png')) self.play_slides_loop.setIcon(build_icon(':/media/media_time.png'))
self.play_slides_loop.setText(UiStrings().PlaySlidesInLoop)
if item.is_text(): if item.is_text():
if (Settings().value(self.main_window.songs_settings_section + '/display songbar') and if (Settings().value(self.main_window.songs_settings_section + '/display songbar') and
not self.song_menu.menu().isEmpty()): not self.song_menu.menu().isEmpty()):

View File

@ -688,8 +688,8 @@ class TestLib(TestCase):
string_result = create_separated_list(string_list) string_result = create_separated_list(string_list)
# THEN: We should have "Author 1, Author 2, and Author 3" # THEN: We should have "Author 1, Author 2, and Author 3"
assert string_result == 'Author 1, Author 2, and Author 3', 'The string should be u\'Author 1, ' \ self.assertEqual(string_result, 'Author 1, Author 2 and Author 3', 'The string should be "Author 1, '
'Author 2, and Author 3\'.' 'Author 2, and Author 3".')
def test_create_separated_list_empty_list(self): def test_create_separated_list_empty_list(self):
""" """
@ -705,56 +705,44 @@ class TestLib(TestCase):
string_result = create_separated_list(string_list) string_result = create_separated_list(string_list)
# THEN: We shoud have an emptry string. # THEN: We shoud have an emptry string.
assert string_result == '', 'The string sould be empty.' self.assertEqual(string_result, '', 'The string sould be empty.')
def test_create_separated_list_with_one_item(self): def test_create_separated_list_with_one_item(self):
""" """
Test the create_separated_list function with a list consisting of only one entry Test the create_separated_list function with a list consisting of only one entry
""" """
with patch('openlp.core.lib.Qt') as mocked_qt: # GIVEN: A list with a string.
# GIVEN: A list with a string and the mocked Qt module.
mocked_qt.PYQT_VERSION_STR = '4.8'
mocked_qt.qVersion.return_value = '4.7'
string_list = ['Author 1'] string_list = ['Author 1']
# WHEN: We get a string build from the entries it the list and a separator. # WHEN: We get a string build from the entries it the list and a separator.
string_result = create_separated_list(string_list) string_result = create_separated_list(string_list)
# THEN: We should have "Author 1" # THEN: We should have "Author 1"
assert string_result == 'Author 1', 'The string should be u\'Author 1\'.' self.assertEqual(string_result, 'Author 1', 'The string should be "Author 1".')
def test_create_separated_list_with_two_items(self): def test_create_separated_list_with_two_items(self):
""" """
Test the create_separated_list function with a list of two entries Test the create_separated_list function with a list of two entries
""" """
with patch('openlp.core.lib.Qt') as mocked_qt, patch('openlp.core.lib.translate') as mocked_translate: # GIVEN: A list with two strings.
# GIVEN: A list of strings and the mocked Qt module.
mocked_qt.PYQT_VERSION_STR = '4.8'
mocked_qt.qVersion.return_value = '4.7'
mocked_translate.return_value = '%s and %s'
string_list = ['Author 1', 'Author 2'] string_list = ['Author 1', 'Author 2']
# WHEN: We get a string build from the entries it the list and a seperator. # WHEN: We get a string build from the entries it the list and a seperator.
string_result = create_separated_list(string_list) string_result = create_separated_list(string_list)
# THEN: We should have "Author 1 and Author 2" # THEN: We should have "Author 1 and Author 2"
assert string_result == 'Author 1 and Author 2', 'The string should be u\'Author 1 and Author 2\'.' self.assertEqual(string_result, 'Author 1 and Author 2', 'The string should be "Author 1 and Author 2".')
def test_create_separated_list_with_three_items(self): def test_create_separated_list_with_three_items(self):
""" """
Test the create_separated_list function with a list of three items Test the create_separated_list function with a list of three items
""" """
with patch('openlp.core.lib.Qt') as mocked_qt, patch('openlp.core.lib.translate') as mocked_translate: # GIVEN: A list with three strings.
# GIVEN: A list with a string and the mocked Qt module.
mocked_qt.PYQT_VERSION_STR = '4.8'
mocked_qt.qVersion.return_value = '4.7'
# Always return the untranslated string.
mocked_translate.side_effect = lambda module, string_to_translate, comment: string_to_translate
string_list = ['Author 1', 'Author 2', 'Author 3'] string_list = ['Author 1', 'Author 2', 'Author 3']
# WHEN: We get a string build from the entries it the list and a seperator. # WHEN: We get a string build from the entries it the list and a seperator.
string_result = create_separated_list(string_list) string_result = create_separated_list(string_list)
# THEN: We should have "Author 1, Author 2, and Author 3" # THEN: We should have "Author 1, Author 2 and Author 3"
assert string_result == 'Author 1, Author 2, and Author 3', 'The string should be u\'Author 1, ' \ self.assertEqual(string_result, 'Author 1, Author 2 and Author 3', 'The string should be "Author 1, '
'Author 2, and Author 3\'.' 'Author 2, and Author 3".')