diff --git a/openlp/core/lib/__init__.py b/openlp/core/lib/__init__.py index 023bd76fe..a3d9cec4b 100644 --- a/openlp/core/lib/__init__.py +++ b/openlp/core/lib/__init__.py @@ -30,6 +30,7 @@ The :mod:`lib` module contains most of the components and libraries that make OpenLP work. """ +from distutils.version import LooseVersion import logging import os @@ -366,23 +367,23 @@ def create_separated_list(stringlist): ``stringlist`` List of unicode strings """ - if Qt.PYQT_VERSION_STR >= u'4.9' and Qt.qVersion() >= u'4.8': + if LooseVersion(Qt.PYQT_VERSION_STR) >= LooseVersion(u'4.9') and \ + LooseVersion(Qt.qVersion()) >= LooseVersion(u'4.8'): return QtCore.QLocale().createSeparatedList(stringlist) if not stringlist: return u'' elif len(stringlist) == 1: return stringlist[0] elif len(stringlist) == 2: - return translate('OpenLP.core.lib', '%1 and %2', + return translate('OpenLP.core.lib', '%s and %s', 'Locale list separator: 2 items') % (stringlist[0], stringlist[1]) else: - merged = translate('OpenLP.core.lib', '%1, and %2', + merged = translate('OpenLP.core.lib', '%s, and %s', u'Locale list separator: end') % (stringlist[-2], stringlist[-1]) for index in reversed(range(1, len(stringlist) - 2)): - merged = translate('OpenLP.core.lib', '%1, %2', - u'Locale list separator: middle') % (stringlist[index], merged) - return translate('OpenLP.core.lib', '%1, %2', - u'Locale list separator: start') % (stringlist[0], merged) + merged = translate('OpenLP.core.lib', '%s, %s', + u'Locale list separator: middle') % (stringlist[index], merged) + return translate('OpenLP.core.lib', '%s, %s', u'Locale list separator: start') % (stringlist[0], merged) from registry import Registry diff --git a/openlp/core/ui/filerenameform.py b/openlp/core/ui/filerenameform.py index 0743578a1..934a8dee8 100644 --- a/openlp/core/ui/filerenameform.py +++ b/openlp/core/ui/filerenameform.py @@ -34,18 +34,18 @@ from PyQt4 import QtGui from filerenamedialog import Ui_FileRenameDialog -from openlp.core.lib import translate +from openlp.core.lib import translate, Registry class FileRenameForm(QtGui.QDialog, Ui_FileRenameDialog): """ The file rename dialog """ - def __init__(self, parent): + def __init__(self): """ Constructor """ - QtGui.QDialog.__init__(self, parent) + QtGui.QDialog.__init__(self, self.main_window) self.setupUi(self) def exec_(self, copy=False): @@ -56,4 +56,15 @@ class FileRenameForm(QtGui.QDialog, Ui_FileRenameDialog): self.setWindowTitle(translate('OpenLP.FileRenameForm', 'File Copy')) else: self.setWindowTitle(translate('OpenLP.FileRenameForm', 'File Rename')) + self.fileNameEdit.setFocus() return QtGui.QDialog.exec_(self) + + def _get_main_window(self): + """ + Adds the main window to the class dynamically + """ + if not hasattr(self, u'_main_window'): + self._main_window = Registry().get(u'main_window') + return self._main_window + + main_window = property(_get_main_window) diff --git a/openlp/core/ui/thememanager.py b/openlp/core/ui/thememanager.py index c58a8aefd..3e5ce56d4 100644 --- a/openlp/core/ui/thememanager.py +++ b/openlp/core/ui/thememanager.py @@ -62,7 +62,7 @@ class ThemeManager(QtGui.QWidget): Registry().register(u'theme_manager', self) self.settingsSection = u'themes' self.themeForm = ThemeForm(self) - self.fileRenameForm = FileRenameForm(self) + self.fileRenameForm = FileRenameForm() # start with the layout self.layout = QtGui.QVBoxLayout(self) self.layout.setSpacing(0) diff --git a/openlp/plugins/songs/forms/editsongform.py b/openlp/plugins/songs/forms/editsongform.py index e5421990f..f77f8d18e 100644 --- a/openlp/plugins/songs/forms/editsongform.py +++ b/openlp/plugins/songs/forms/editsongform.py @@ -116,6 +116,23 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog): self.findVerseSplit = re.compile(u'---\[\]---\n', re.UNICODE) self.whitespace = re.compile(r'\W+', re.UNICODE) + def keyPressEvent(self, event): + """ + Reimplement the keyPressEvent to react on Return/Enter keys. When some combo boxes have focus we do not want + dialog's default action be triggered but instead our own. + + ``event`` + A QtGui.QKeyEvent event. + """ + if event.key() in (QtCore.Qt.Key_Enter, QtCore.Qt.Key_Return): + if self.authorsComboBox.hasFocus() and self.authorsComboBox.currentText(): + self.onAuthorAddButtonClicked() + return + if self.topicsComboBox.hasFocus() and self.topicsComboBox.currentText(): + self.onTopicAddButtonClicked() + return + QtGui.QDialog.keyPressEvent(self, event) + def initialise(self): """ Set up the form for when it is displayed. diff --git a/scripts/check_dependencies.py b/scripts/check_dependencies.py index be15414b4..3485b8505 100755 --- a/scripts/check_dependencies.py +++ b/scripts/check_dependencies.py @@ -40,7 +40,13 @@ import os import sys from distutils.version import LooseVersion -is_win = sys.platform.startswith('win') +# If we try to import uno before nose this will greate a warning. Just try to import nose first to supress the warning. +try: + import nose +except ImportError: + pass + +IS_WIN = sys.platform.startswith('win') VERS = { 'Python': '2.6', @@ -48,7 +54,7 @@ VERS = { 'Qt4': '4.6', 'sqlalchemy': '0.5', # pyenchant 1.6 required on Windows - 'enchant': '1.6' if is_win else '1.3' + 'enchant': '1.6' if IS_WIN else '1.3' } # pywin32 @@ -84,7 +90,7 @@ OPTIONAL_MODULES = [ ('sqlite', ' (SQLite 2 support)'), ('MySQLdb', ' (MySQL support)'), ('psycopg2', ' (PostgreSQL support)'), - ('pytest', ' (testing framework)'), + ('nose', ' (testing framework)'), ] w = sys.stdout.write @@ -176,7 +182,7 @@ def main(): for m in OPTIONAL_MODULES: check_module(m[0], text=m[1]) - if is_win: + if IS_WIN: print('Checking for Windows specific modules...') for m in WIN32_MODULES: check_module(m) diff --git a/tests/functional/openlp_core_lib/test_lib.py b/tests/functional/openlp_core_lib/test_lib.py index 176197e24..8527373dc 100644 --- a/tests/functional/openlp_core_lib/test_lib.py +++ b/tests/functional/openlp_core_lib/test_lib.py @@ -7,7 +7,7 @@ from datetime import datetime, timedelta from mock import MagicMock, patch from openlp.core.lib import str_to_bool, translate, check_directory_exists, get_text_file_string, build_icon, \ - image_to_byte, check_item_selected, validate_thumb + image_to_byte, check_item_selected, validate_thumb, create_separated_list class TestLib(TestCase): @@ -308,14 +308,14 @@ class TestLib(TestCase): file_path = u'path/to/file' thumb_path = u'path/to/thumb' mocked_os.path.exists.return_value = False - + # WHEN: we run the validate_thumb() function result = validate_thumb(file_path, thumb_path) - + # THEN: we should have called a few functions, and the result should be False mocked_os.path.exists.assert_called_with(thumb_path) assert result is False, u'The result should be False' - + def validate_thumb_file_exists_and_newer_test(self): """ Test the validate_thumb() function when the thumbnail exists and has a newer timestamp than the file @@ -350,7 +350,7 @@ class TestLib(TestCase): thumb_mocked_stat.st_mtime = datetime.now() - timedelta(seconds=10) mocked_os.path.exists.return_value = True mocked_os.stat.side_effect = lambda fname: file_mocked_stat if fname == file_path else thumb_mocked_stat - + # WHEN: we run the validate_thumb() function result = validate_thumb(file_path, thumb_path) @@ -359,3 +359,90 @@ class TestLib(TestCase): mocked_os.stat.assert_any_call(file_path) mocked_os.stat.assert_any_call(thumb_path) assert result is False, u'The result should be False' + + def create_separated_list_qlocate_test(self): + """ + Test the create_separated_list function using the Qt provided method. + """ + with patch(u'openlp.core.lib.Qt') as mocked_qt, \ + patch(u'openlp.core.lib.QtCore.QLocale.createSeparatedList') as mocked_createSeparatedList: + # GIVEN: A list of strings and the mocked Qt module. + mocked_qt.PYQT_VERSION_STR = u'4.9' + mocked_qt.qVersion.return_value = u'4.8' + mocked_createSeparatedList.return_value = u'Author 1, Author 2, and Author 3' + string_list = [u'Author 1', u'Author 2', u'Author 3'] + + # 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 == u'Author 1, Author 2, and Author 3', u'The string should be u\'Author 1, ' \ + 'Author 2, and Author 3\'.' + + def create_separated_list_empty_list_test(self): + """ + Test the create_separated_list function with an empty list. + """ + with patch(u'openlp.core.lib.Qt') as mocked_qt: + # GIVEN: An empty list and the mocked Qt module. + mocked_qt.PYQT_VERSION_STR = u'4.8' + mocked_qt.qVersion.return_value = u'4.7' + 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 shoud have an emptry string. + assert string_result == u'', u'The string sould be empty.' + + def create_separated_list_with_one_item_test(self): + """ + Test the create_separated_list function with a list consisting of only one entry. + """ + with patch(u'openlp.core.lib.Qt') as mocked_qt: + # GIVEN: A list with a string and the mocked Qt module. + mocked_qt.PYQT_VERSION_STR = u'4.8' + mocked_qt.qVersion.return_value = u'4.7' + string_list = [u'Author 1'] + + # 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" + assert string_result == u'Author 1', u'The string should be u\'Author 1\'.' + + def create_separated_list_with_two_items_test(self): + """ + Test the create_separated_list function with a list of two entries. + """ + with patch(u'openlp.core.lib.Qt') as mocked_qt, patch(u'openlp.core.lib.translate') as mocked_translate: + # GIVEN: A list of strings and the mocked Qt module. + mocked_qt.PYQT_VERSION_STR = u'4.8' + mocked_qt.qVersion.return_value = u'4.7' + mocked_translate.return_value = u'%s and %s' + string_list = [u'Author 1', u'Author 2'] + + # 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 == u'Author 1 and Author 2', u'The string should be u\'Author 1 and Author 2\'.' + + def create_separated_list_with_three_items_test(self): + """ + Test the create_separated_list function with a list of three items. + """ + with patch(u'openlp.core.lib.Qt') as mocked_qt, patch(u'openlp.core.lib.translate') as mocked_translate: + # GIVEN: A list with a string and the mocked Qt module. + mocked_qt.PYQT_VERSION_STR = u'4.8' + mocked_qt.qVersion.return_value = u'4.7' + # Always return the untranslated string. + mocked_translate.side_effect = lambda module, string_to_translate, comment: string_to_translate + string_list = [u'Author 1', u'Author 2', u'Author 3'] + + # 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 == u'Author 1, Author 2, and Author 3', u'The string should be u\'Author 1, ' \ + 'Author 2, and Author 3\'.' diff --git a/tests/interfaces/openlp_core_ui/test_filerenamedialog.py b/tests/interfaces/openlp_core_ui/test_filerenamedialog.py new file mode 100644 index 000000000..74fe83b26 --- /dev/null +++ b/tests/interfaces/openlp_core_ui/test_filerenamedialog.py @@ -0,0 +1,83 @@ +""" + Package to test the openlp.core.ui package. +""" +from unittest import TestCase + +from mock import MagicMock, patch +from openlp.core.lib import Registry +from openlp.core.ui import filerenameform +from PyQt4 import QtGui, QtTest + +class TestStartFileRenameForm(TestCase): + + def setUp(self): + """ + Create the UI + """ + registry = Registry.create() + self.app = QtGui.QApplication([]) + self.main_window = QtGui.QMainWindow() + Registry().register(u'main_window', self.main_window) + self.form = filerenameform.FileRenameForm() + + def tearDown(self): + """ + Delete all the C++ objects at the end so that we don't have a segfault + """ + del self.form + del self.main_window + del self.app + + def window_title_test(self): + """ + Test the windowTitle of the FileRenameDialog + """ + # GIVEN: A mocked QDialog.exec_() method + with patch(u'PyQt4.QtGui.QDialog.exec_') as mocked_exec: + + # WHEN: The form is executed with no args + self.form.exec_() + + # THEN: the window title is set correctly + self.assertEqual(self.form.windowTitle(), u'File Rename', u'The window title should be "File Rename"') + + # WHEN: The form is executed with False arg + self.form.exec_(False) + + # THEN: the window title is set correctly + self.assertEqual(self.form.windowTitle(), u'File Rename', u'The window title should be "File Rename"') + + # WHEN: The form is executed with True arg + self.form.exec_(True) + + # THEN: the window title is set correctly + self.assertEqual(self.form.windowTitle(), u'File Copy', u'The window title should be "File Copy"') + + def line_edit_focus_test(self): + """ + Regression test for bug1067251 + Test that the fileNameEdit setFocus has called with True when executed + """ + # GIVEN: A mocked QDialog.exec_() method and mocked fileNameEdit.setFocus() method. + with patch(u'PyQt4.QtGui.QDialog.exec_') as mocked_exec: + mocked_set_focus = MagicMock() + self.form.fileNameEdit.setFocus = mocked_set_focus + + # WHEN: The form is executed + self.form.exec_() + + # THEN: the setFocus method of the fileNameEdit has been called with True + mocked_set_focus.assert_called_with() + + def file_name_validation_test(self): + """ + Test the fileNameEdit validation + """ + # GIVEN: QLineEdit with a validator set with illegal file name characters. + + # WHEN: 'Typing' a string containing invalid file characters. + QtTest.QTest.keyClicks(self.form.fileNameEdit, u'I/n\\v?a*l|i \F[i\l]e" :N+a%me') + + # THEN: The text in the QLineEdit should be the same as the input string with the invalid chatacters filtered + # out. + self.assertEqual(self.form.fileNameEdit.text(), u'Invalid File Name')