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)
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):
"""
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
elif data_version != openlp_version:
if QtWidgets.QMessageBox.question(None, translate('OpenLP', 'Backup'),
translate('OpenLP', 'OpenLP has been upgraded, do you want to create '
'a backup of OpenLPs data folder?'),
translate('OpenLP', 'OpenLP has been upgraded, do you want to create\n'
'a backup of the old data folder?'),
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No,
QtWidgets.QMessageBox.Yes) == QtWidgets.QMessageBox.Yes:
# Create copy of data folder
@ -223,8 +255,8 @@ class OpenLP(OpenLPMixin, QtWidgets.QApplication):
translate('OpenLP', 'Backup of the data folder failed!'))
return
message = translate('OpenLP',
'A backup of the data folder has been created'
'at {text}').format(text=data_folder_backup_path)
'A backup of the data folder has been created at:\n\n'
'{text}').format(text=data_folder_backup_path)
QtWidgets.QMessageBox.information(None, translate('OpenLP', 'Backup'), message)
# Update the version in the settings
@ -368,9 +400,13 @@ def main(args=None):
Registry.create()
Registry().register('application', application)
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():
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.
Settings().remove_obsolete_settings()
# First time checks in settings

View File

@ -310,30 +310,23 @@ def expand_tags(text):
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'):
return QtCore.QLocale().createSeparatedList(string_list)
if not string_list:
return ''
elif len(string_list) == 1:
return string_list[0]
# TODO: Verify mocking of translate() test before conversion
elif len(string_list) == 2:
return translate('OpenLP.core.lib', '%s and %s',
'Locale list separator: 2 items') % (string_list[0], string_list[1])
list_length = len(string_list)
if list_length == 1:
list_to_string = string_list[0]
elif list_length == 2:
list_to_string = translate('OpenLP.core.lib', '{one} and {two}').format(one=string_list[0], two=string_list[1])
elif list_length > 2:
list_to_string = translate('OpenLP.core.lib', '{first} and {last}').format(first=', '.join(string_list[:-1]),
last=string_list[-1])
else:
merged = translate('OpenLP.core.lib', '%s, and %s',
'Locale list separator: end') % (string_list[-2], string_list[-1])
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)
list_to_string = ''
return list_to_string
from .exceptions import ValidationError

View File

@ -397,27 +397,6 @@ class AdvancedTab(SettingsTab):
self.data_directory_cancel_button.hide()
# Since data location can be changed, make sure the path is present.
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))
# Don't allow data directory move if running portable.
if settings.value('advanced/is portable'):

View File

@ -26,7 +26,7 @@ import os
from PyQt5 import QtCore, QtGui, QtWidgets
from openlp.core.common import Registry
from openlp.core.common import Registry, is_win
class TreeWidgetWithDnD(QtWidgets.QTreeWidget):
@ -108,6 +108,11 @@ class TreeWidgetWithDnD(QtWidgets.QTreeWidget):
: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():
event.setDropAction(QtCore.Qt.CopyAction)
event.accept()

View File

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

View File

@ -688,8 +688,8 @@ class TestLib(TestCase):
string_result = create_separated_list(string_list)
# 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, ' \
'Author 2, and Author 3\'.'
self.assertEqual(string_result, 'Author 1, Author 2 and Author 3', 'The string should be "Author 1, '
'Author 2, and Author 3".')
def test_create_separated_list_empty_list(self):
"""
@ -705,56 +705,44 @@ class TestLib(TestCase):
string_result = create_separated_list(string_list)
# 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):
"""
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 and the mocked Qt module.
mocked_qt.PYQT_VERSION_STR = '4.8'
mocked_qt.qVersion.return_value = '4.7'
string_list = ['Author 1']
# GIVEN: A list with a string.
string_list = ['Author 1']
# WHEN: We get a string build from the entries it the list and a separator.
string_result = create_separated_list(string_list)
# WHEN: We get a string build from the entries it the list and a separator.
string_result = create_separated_list(string_list)
# THEN: We should have "Author 1"
assert string_result == 'Author 1', 'The string should be u\'Author 1\'.'
# THEN: We should have "Author 1"
self.assertEqual(string_result, 'Author 1', 'The string should be "Author 1".')
def test_create_separated_list_with_two_items(self):
"""
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 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']
# GIVEN: A list with two strings.
string_list = ['Author 1', 'Author 2']
# WHEN: We get a string build from the entries it the list and a seperator.
string_result = create_separated_list(string_list)
# WHEN: We get a string build from the entries it the list and a seperator.
string_result = create_separated_list(string_list)
# 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\'.'
# THEN: We should have "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):
"""
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 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']
# GIVEN: A list with three strings.
string_list = ['Author 1', 'Author 2', 'Author 3']
# WHEN: We get a string build from the entries it the list and a seperator.
string_result = create_separated_list(string_list)
# WHEN: We get a string build from the entries it the list and a seperator.
string_result = create_separated_list(string_list)
# 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, ' \
'Author 2, and Author 3\'.'
# THEN: We should have "Author 1, Author 2 and Author 3"
self.assertEqual(string_result, 'Author 1, Author 2 and Author 3', 'The string should be "Author 1, '
'Author 2, and Author 3".')