diff --git a/openlp/core/__init__.py b/openlp/core/__init__.py index 6f6addbbd..5de5e69de 100644 --- a/openlp/core/__init__.py +++ b/openlp/core/__init__.py @@ -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 diff --git a/openlp/core/common/settings.py b/openlp/core/common/settings.py index 2caf04dab..8f70fafff 100644 --- a/openlp/core/common/settings.py +++ b/openlp/core/common/settings.py @@ -214,7 +214,10 @@ class Settings(QtCore.QSettings): ('media/players', 'media/players_temp', [(media_players_conv, None)]), # Convert phonon to system ('media/players_temp', 'media/players', []), # Move temp setting from above to correct setting ('advanced/default color', 'core/logo background color', []), # Default image renamed + moved to general > 2.4. - ('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/offlineHelpItem', 'shortcuts/HelpItem', []), # Online and Offline help were combined in 2.6. + ('shortcuts/onlineHelpItem', 'shortcuts/HelpItem', []) # Online and Offline help were combined in 2.6. ] @staticmethod @@ -261,10 +264,10 @@ class Settings(QtCore.QSettings): 'shortcuts/blankScreen': [QtGui.QKeySequence(QtCore.Qt.Key_Period)], 'shortcuts/collapse': [QtGui.QKeySequence(QtCore.Qt.Key_Minus)], 'shortcuts/desktopScreen': [QtGui.QKeySequence(QtCore.Qt.Key_D)], + 'shortcuts/desktopScreenEnable': [QtGui.QKeySequence(QtCore.Qt.Key_Escape)], 'shortcuts/delete': [QtGui.QKeySequence(QtGui.QKeySequence.Delete)], 'shortcuts/down': [QtGui.QKeySequence(QtCore.Qt.Key_Down)], 'shortcuts/editSong': [], - 'shortcuts/escapeItem': [QtGui.QKeySequence(QtCore.Qt.Key_Escape)], 'shortcuts/expand': [QtGui.QKeySequence(QtCore.Qt.Key_Plus)], 'shortcuts/exportThemeItem': [], 'shortcuts/fileNewItem': [QtGui.QKeySequence(QtGui.QKeySequence.New)], @@ -273,6 +276,7 @@ class Settings(QtCore.QSettings): 'shortcuts/fileSaveItem': [QtGui.QKeySequence(QtGui.QKeySequence.Save)], 'shortcuts/fileOpenItem': [QtGui.QKeySequence(QtGui.QKeySequence.Open)], 'shortcuts/goLive': [], + 'shortcuts/HelpItem': [QtGui.QKeySequence(QtGui.QKeySequence.HelpContents)], 'shortcuts/importThemeItem': [], 'shortcuts/importBibleItem': [], 'shortcuts/listViewBiblesDeleteItem': [QtGui.QKeySequence(QtGui.QKeySequence.Delete)], @@ -333,8 +337,6 @@ class Settings(QtCore.QSettings): QtGui.QKeySequence(QtCore.Qt.Key_PageDown)], 'shortcuts/nextService': [QtGui.QKeySequence(QtCore.Qt.Key_Right)], 'shortcuts/newService': [], - 'shortcuts/offlineHelpItem': [QtGui.QKeySequence(QtGui.QKeySequence.HelpContents)], - 'shortcuts/onlineHelpItem': [QtGui.QKeySequence(QtGui.QKeySequence.HelpContents)], 'shortcuts/openService': [], 'shortcuts/saveService': [], 'shortcuts/previousItem_live': [QtGui.QKeySequence(QtCore.Qt.Key_Up), @@ -379,6 +381,7 @@ class Settings(QtCore.QSettings): 'shortcuts/themeScreen': [QtGui.QKeySequence(QtCore.Qt.Key_T)], 'shortcuts/toolsReindexItem': [], 'shortcuts/toolsFindDuplicates': [], + 'shortcuts/toolsSongListReport': [], 'shortcuts/toolsAlertItem': [QtGui.QKeySequence(QtCore.Qt.Key_F7)], 'shortcuts/toolsFirstTimeWizard': [], 'shortcuts/toolsOpenDataFolder': [], diff --git a/openlp/core/common/uistrings.py b/openlp/core/common/uistrings.py index 25df5ac75..3eb4e254d 100644 --- a/openlp/core/common/uistrings.py +++ b/openlp/core/common/uistrings.py @@ -119,6 +119,7 @@ class UiStrings(object): self.NFSp = translate('OpenLP.Ui', 'No Files Selected', 'Plural') self.NISs = translate('OpenLP.Ui', 'No Item Selected', 'Singular') self.NISp = translate('OpenLP.Ui', 'No Items Selected', 'Plural') + self.NoResults = translate('OpenLP.Ui', 'No Search Results') self.OLP = translate('OpenLP.Ui', 'OpenLP') self.OLPV2 = "{name} {version}".format(name=self.OLP, version="2") self.OLPV2x = "{name} {version}".format(name=self.OLP, version="2.4") @@ -149,6 +150,7 @@ class UiStrings(object): self.Settings = translate('OpenLP.Ui', 'Settings') self.SaveService = translate('OpenLP.Ui', 'Save Service') self.Service = translate('OpenLP.Ui', 'Service') + self.ShortResults = translate('OpenLP.Ui', 'Please type more text to use \'Search As You Type\'') self.Split = translate('OpenLP.Ui', 'Optional &Split') self.SplitToolTip = translate('OpenLP.Ui', 'Split a slide into two only if it does not fit on the screen as one slide.') diff --git a/openlp/core/lib/__init__.py b/openlp/core/lib/__init__.py index d00d85b54..49365600d 100644 --- a/openlp/core/lib/__init__.py +++ b/openlp/core/lib/__init__.py @@ -129,16 +129,16 @@ def build_icon(icon): location like ``/path/to/file.png``. However, the **recommended** way is to specify a resource string. :return: The build icon. """ - button_icon = QtGui.QIcon() if isinstance(icon, QtGui.QIcon): - button_icon = icon - elif isinstance(icon, str): - if icon.startswith(':/'): - button_icon.addPixmap(QtGui.QPixmap(icon), QtGui.QIcon.Normal, QtGui.QIcon.Off) - else: - button_icon.addPixmap(QtGui.QPixmap.fromImage(QtGui.QImage(icon)), QtGui.QIcon.Normal, QtGui.QIcon.Off) + return icon + pix_map = None + button_icon = QtGui.QIcon() + if isinstance(icon, str): + pix_map = QtGui.QPixmap(icon) elif isinstance(icon, QtGui.QImage): - button_icon.addPixmap(QtGui.QPixmap.fromImage(icon), QtGui.QIcon.Normal, QtGui.QIcon.Off) + pix_map = QtGui.QPixmap.fromImage(icon) + if pix_map: + button_icon.addPixmap(pix_map, QtGui.QIcon.Normal, QtGui.QIcon.Off) return button_icon @@ -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 diff --git a/openlp/core/lib/mediamanageritem.py b/openlp/core/lib/mediamanageritem.py index 2edea93cf..25de144ad 100644 --- a/openlp/core/lib/mediamanageritem.py +++ b/openlp/core/lib/mediamanageritem.py @@ -266,7 +266,7 @@ class MediaManagerItem(QtWidgets.QWidget, RegistryProperties): self.search_text_layout.setObjectName('search_text_layout') self.search_text_label = QtWidgets.QLabel(self.search_widget) self.search_text_label.setObjectName('search_text_label') - self.search_text_edit = SearchEdit(self.search_widget) + self.search_text_edit = SearchEdit(self.search_widget, self.settings_section) self.search_text_edit.setObjectName('search_text_edit') self.search_text_label.setBuddy(self.search_text_edit) self.search_text_layout.addRow(self.search_text_label, self.search_text_edit) @@ -397,8 +397,6 @@ class MediaManagerItem(QtWidgets.QWidget, RegistryProperties): # Decide if we have to show the context menu or not. if item is None: return - if not item.flags() & QtCore.Qt.ItemIsSelectable: - return self.menu.exec(self.list_view.mapToGlobal(point)) def get_file_list(self): @@ -638,34 +636,6 @@ class MediaManagerItem(QtWidgets.QWidget, RegistryProperties): """ return item - def check_search_result(self): - """ - Checks if the list_view is empty and adds a "No Search Results" item. - """ - if self.list_view.count(): - return - message = translate('OpenLP.MediaManagerItem', 'No Search Results') - item = QtWidgets.QListWidgetItem(message) - item.setFlags(QtCore.Qt.NoItemFlags) - font = QtGui.QFont() - font.setItalic(True) - item.setFont(font) - self.list_view.addItem(item) - - def check_search_result_search_while_typing_short(self): - """ - This is used in Bible "Search while typing" if the search is shorter than the min required len. - """ - if self.list_view.count(): - return - message = translate('OpenLP.MediaManagerItem', 'Search is too short to be used in: "Search while typing"') - item = QtWidgets.QListWidgetItem(message) - item.setFlags(QtCore.Qt.NoItemFlags) - font = QtGui.QFont() - font.setItalic(True) - item.setFont(font) - self.list_view.addItem(item) - def _get_id_of_item_to_generate(self, item, remote_item): """ Utility method to check items being submitted for slide generation. diff --git a/openlp/core/lib/searchedit.py b/openlp/core/lib/searchedit.py index 41e54957d..1fcc0e618 100644 --- a/openlp/core/lib/searchedit.py +++ b/openlp/core/lib/searchedit.py @@ -26,6 +26,7 @@ from PyQt5 import QtCore, QtWidgets from openlp.core.lib import build_icon from openlp.core.lib.ui import create_widget_action +from openlp.core.common import Settings log = logging.getLogger(__name__) @@ -37,11 +38,12 @@ class SearchEdit(QtWidgets.QLineEdit): searchTypeChanged = QtCore.pyqtSignal(QtCore.QVariant) cleared = QtCore.pyqtSignal() - def __init__(self, parent): + def __init__(self, parent, settings_section): """ Constructor. """ - super(SearchEdit, self).__init__(parent) + super().__init__(parent) + self.settings_section = settings_section self._current_search_type = -1 self.clear_button = QtWidgets.QToolButton(self) self.clear_button.setIcon(build_icon(':/system/clear_shortcut.png')) @@ -100,14 +102,10 @@ class SearchEdit(QtWidgets.QLineEdit): menu = self.menu_button.menu() for action in menu.actions(): if identifier == action.data(): - # setPlaceholderText has been implemented in Qt 4.7 and in at least PyQt 4.9 (I am not sure, if it was - # implemented in PyQt 4.8). - try: - self.setPlaceholderText(action.placeholder_text) - except AttributeError: - pass + self.setPlaceholderText(action.placeholder_text) self.menu_button.setDefaultAction(action) self._current_search_type = identifier + Settings().setValue('{section}/last search type'.format(section=self.settings_section), identifier) self.searchTypeChanged.emit(identifier) return True @@ -130,14 +128,10 @@ class SearchEdit(QtWidgets.QLineEdit): (2, ":/songs/authors.png", "Authors", "Search Authors...") """ menu = QtWidgets.QMenu(self) - first = None for identifier, icon, title, placeholder in items: action = create_widget_action( menu, text=title, icon=icon, data=identifier, triggers=self._on_menu_action_triggered) action.placeholder_text = placeholder - if first is None: - first = action - self._current_search_type = identifier if not hasattr(self, 'menu_button'): self.menu_button = QtWidgets.QToolButton(self) self.menu_button.setIcon(build_icon(':/system/clear_shortcut.png')) @@ -146,7 +140,8 @@ class SearchEdit(QtWidgets.QLineEdit): self.menu_button.setStyleSheet('QToolButton { border: none; padding: 0px 10px 0px 0px; }') self.menu_button.resize(QtCore.QSize(28, 18)) self.menu_button.setMenu(menu) - self.menu_button.setDefaultAction(first) + self.set_current_search_type( + Settings().value('{section}/last search type'.format(section=self.settings_section))) self.menu_button.show() self._update_style_sheet() diff --git a/openlp/core/ui/advancedtab.py b/openlp/core/ui/advancedtab.py index ca91e882a..fed712ed2 100644 --- a/openlp/core/ui/advancedtab.py +++ b/openlp/core/ui/advancedtab.py @@ -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'): diff --git a/openlp/core/ui/exceptiondialog.py b/openlp/core/ui/exceptiondialog.py index 7341287c7..36e039cee 100644 --- a/openlp/core/ui/exceptiondialog.py +++ b/openlp/core/ui/exceptiondialog.py @@ -38,7 +38,7 @@ class Ui_ExceptionDialog(object): Set up the UI. """ exception_dialog.setObjectName('exception_dialog') - exception_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg')) + exception_dialog.setWindowIcon(build_icon(':/icon/openlp-logo.svg')) self.exception_layout = QtWidgets.QVBoxLayout(exception_dialog) self.exception_layout.setObjectName('exception_layout') self.message_layout = QtWidgets.QHBoxLayout() @@ -97,9 +97,9 @@ class Ui_ExceptionDialog(object): translate('OpenLP.ExceptionDialog', 'Please describe what you were trying to do. ' ' If possible, write in English.')) exception_part1 = (translate('OpenLP.ExceptionDialog', - 'Oops, OpenLP hit a problem and couldn\'t recover!

' - 'You can help the OpenLP developers to fix this' - ' by
sending them a bug report to {email}{newlines}' + 'Oops, OpenLP hit a problem and couldn\'t recover!

' + 'You can help
the OpenLP developers to fix this' + ' by
sending them a bug report to {email}{newlines}' ).format(email=' bugs@openlp.org', newlines='

')) self.message_label.setText( @@ -107,7 +107,7 @@ class Ui_ExceptionDialog(object): 'No email app? You can save this ' 'information to a file and
' 'send it from your mail on browser via an attachment.

' - 'Thank you for being part of making OpenLP better!
' + 'Thank you for being part of making OpenLP better!
' ).format(first_part=exception_part1)) self.send_report_button.setText(translate('OpenLP.ExceptionDialog', 'Send E-Mail')) self.save_report_button.setText(translate('OpenLP.ExceptionDialog', 'Save to File')) diff --git a/openlp/core/ui/exceptionform.py b/openlp/core/ui/exceptionform.py index 2122acfaa..9e58ac8b2 100644 --- a/openlp/core/ui/exceptionform.py +++ b/openlp/core/ui/exceptionform.py @@ -208,7 +208,7 @@ class ExceptionForm(QtWidgets.QDialog, Ui_ExceptionDialog, RegistryProperties): self.__button_state(False) self.description_word_count.setText( translate('OpenLP.ExceptionDialog', 'Please enter a more detailed description of the situation' - )) + '')) def on_attach_file_button_clicked(self): """ diff --git a/openlp/core/ui/filerenamedialog.py b/openlp/core/ui/filerenamedialog.py index ede60bcfb..cc4574279 100644 --- a/openlp/core/ui/filerenamedialog.py +++ b/openlp/core/ui/filerenamedialog.py @@ -37,7 +37,7 @@ class Ui_FileRenameDialog(object): Set up the UI """ file_rename_dialog.setObjectName('file_rename_dialog') - file_rename_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg')) + file_rename_dialog.setWindowIcon(build_icon(':/icon/openlp-logo.svg')) file_rename_dialog.resize(300, 10) self.dialog_layout = QtWidgets.QGridLayout(file_rename_dialog) self.dialog_layout.setObjectName('dialog_layout') diff --git a/openlp/core/ui/firsttimelanguagedialog.py b/openlp/core/ui/firsttimelanguagedialog.py index dd1389ba2..f32ec9076 100644 --- a/openlp/core/ui/firsttimelanguagedialog.py +++ b/openlp/core/ui/firsttimelanguagedialog.py @@ -38,7 +38,7 @@ class Ui_FirstTimeLanguageDialog(object): Set up the UI. """ language_dialog.setObjectName('language_dialog') - language_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg')) + language_dialog.setWindowIcon(build_icon(':/icon/openlp-logo.svg')) language_dialog.resize(300, 50) self.dialog_layout = QtWidgets.QVBoxLayout(language_dialog) self.dialog_layout.setContentsMargins(8, 8, 8, 8) diff --git a/openlp/core/ui/firsttimewizard.py b/openlp/core/ui/firsttimewizard.py index 7b93c1680..63937ebb2 100644 --- a/openlp/core/ui/firsttimewizard.py +++ b/openlp/core/ui/firsttimewizard.py @@ -55,7 +55,7 @@ class UiFirstTimeWizard(object): :param first_time_wizard: The wizard form """ first_time_wizard.setObjectName('first_time_wizard') - first_time_wizard.setWindowIcon(build_icon(u':/icon/openlp-logo.svg')) + first_time_wizard.setWindowIcon(build_icon(':/icon/openlp-logo.svg')) first_time_wizard.resize(550, 386) first_time_wizard.setModal(True) first_time_wizard.setOptions(QtWidgets.QWizard.IndependentPages | QtWidgets.QWizard.NoBackButtonOnStartPage | diff --git a/openlp/core/ui/formattingtagdialog.py b/openlp/core/ui/formattingtagdialog.py index 19412921d..83ec18115 100644 --- a/openlp/core/ui/formattingtagdialog.py +++ b/openlp/core/ui/formattingtagdialog.py @@ -38,7 +38,7 @@ class Ui_FormattingTagDialog(object): Set up the UI """ formatting_tag_dialog.setObjectName('formatting_tag_dialog') - formatting_tag_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg')) + formatting_tag_dialog.setWindowIcon(build_icon(':/icon/openlp-logo.svg')) formatting_tag_dialog.resize(725, 548) self.list_data_grid_layout = QtWidgets.QVBoxLayout(formatting_tag_dialog) self.list_data_grid_layout.setContentsMargins(8, 8, 8, 8) diff --git a/openlp/core/ui/generaltab.py b/openlp/core/ui/generaltab.py index 463aad73f..629e55e0f 100644 --- a/openlp/core/ui/generaltab.py +++ b/openlp/core/ui/generaltab.py @@ -44,7 +44,7 @@ class GeneralTab(SettingsTab): self.logo_file = ':/graphics/openlp-splash-screen.png' self.logo_background_color = '#ffffff' self.screens = ScreenList() - self.icon_path = ':/icon/openlp-logo-16x16.png' + self.icon_path = ':/icon/openlp-logo.svg' general_translated = translate('OpenLP.GeneralTab', 'General') super(GeneralTab, self).__init__(parent, 'Core', general_translated) diff --git a/openlp/core/ui/lib/listwidgetwithdnd.py b/openlp/core/ui/lib/listwidgetwithdnd.py index 43ec741d0..de601fa13 100644 --- a/openlp/core/ui/lib/listwidgetwithdnd.py +++ b/openlp/core/ui/lib/listwidgetwithdnd.py @@ -26,7 +26,7 @@ import os from PyQt5 import QtCore, QtGui, QtWidgets -from openlp.core.common import Registry +from openlp.core.common import Registry, UiStrings class ListWidgetWithDnD(QtWidgets.QListWidget): @@ -37,8 +37,9 @@ class ListWidgetWithDnD(QtWidgets.QListWidget): """ Initialise the list widget """ - super(ListWidgetWithDnD, self).__init__(parent) + super().__init__(parent) self.mime_data_text = name + self.no_results_text = UiStrings().NoResults def activateDnD(self): """ @@ -48,6 +49,19 @@ class ListWidgetWithDnD(QtWidgets.QListWidget): self.setDragDropMode(QtWidgets.QAbstractItemView.DragDrop) Registry().register_function(('%s_dnd' % self.mime_data_text), self.parent().load_file) + def clear(self, search_while_typing=False): + """ + Re-implement clear, so that we can customise feedback when using 'Search as you type' + + :param search_while_typing: True if we want to display the customised message + :return: None + """ + if search_while_typing: + self.no_results_text = UiStrings().ShortResults + else: + self.no_results_text = UiStrings().NoResults + super().clear() + def mouseMoveEvent(self, event): """ Drag and drop event does not care what data is selected as the recipient will use events to request the data @@ -102,6 +116,24 @@ class ListWidgetWithDnD(QtWidgets.QListWidget): listing = os.listdir(local_file) for file in listing: files.append(os.path.join(local_file, file)) - Registry().execute('%s_dnd' % self.mime_data_text, {'files': files, 'target': self.itemAt(event.pos())}) + Registry().execute('{mime_data}_dnd'.format(mime_data=self.mime_data_text), + {'files': files, 'target': self.itemAt(event.pos())}) else: event.ignore() + + def paintEvent(self, event): + """ + Re-implement paintEvent so that we can add 'No Results' text when the listWidget is empty. + + :param event: A QPaintEvent + :return: None + """ + super().paintEvent(event) + if not self.count(): + viewport = self.viewport() + painter = QtGui.QPainter(viewport) + font = QtGui.QFont() + font.setItalic(True) + painter.setFont(font) + painter.drawText(QtCore.QRect(0, 0, viewport.width(), viewport.height()), + (QtCore.Qt.AlignHCenter | QtCore.Qt.TextWordWrap), self.no_results_text) diff --git a/openlp/core/ui/lib/treewidgetwithdnd.py b/openlp/core/ui/lib/treewidgetwithdnd.py index c49fc144e..f410e453a 100644 --- a/openlp/core/ui/lib/treewidgetwithdnd.py +++ b/openlp/core/ui/lib/treewidgetwithdnd.py @@ -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() diff --git a/openlp/core/ui/mainwindow.py b/openlp/core/ui/mainwindow.py index 92b29d16f..b8bd126dd 100644 --- a/openlp/core/ui/mainwindow.py +++ b/openlp/core/ui/mainwindow.py @@ -309,21 +309,13 @@ class Ui_MainWindow(object): self.about_item.setMenuRole(QtWidgets.QAction.AboutRole) if is_win(): self.local_help_file = os.path.join(AppLocation.get_directory(AppLocation.AppDir), 'OpenLP.chm') - self.offline_help_item = create_action(main_window, 'offlineHelpItem', - icon=':/system/system_help_contents.png', - can_shortcuts=True, - category=UiStrings().Help, triggers=self.on_offline_help_clicked) elif is_macosx(): self.local_help_file = os.path.join(AppLocation.get_directory(AppLocation.AppDir), '..', 'Resources', 'OpenLP.help') - self.offline_help_item = create_action(main_window, 'offlineHelpItem', - icon=':/system/system_help_contents.png', - can_shortcuts=True, - category=UiStrings().Help, triggers=self.on_offline_help_clicked) - self.on_line_help_item = create_action(main_window, 'onlineHelpItem', - icon=':/system/system_online_help.png', - can_shortcuts=True, - category=UiStrings().Help, triggers=self.on_online_help_clicked) + self.on_help_item = create_action(main_window, 'HelpItem', + icon=':/system/system_help_contents.png', + can_shortcuts=True, + category=UiStrings().Help, triggers=self.on_help_clicked) self.web_site_item = create_action(main_window, 'webSiteItem', can_shortcuts=True, category=UiStrings().Help) # Shortcuts not connected to buttons or menu entries. self.search_shortcut_action = create_action(main_window, @@ -362,11 +354,7 @@ class Ui_MainWindow(object): add_actions(self.tools_menu, (self.tools_open_data_folder, None)) add_actions(self.tools_menu, (self.tools_first_time_wizard, None)) add_actions(self.tools_menu, [self.update_theme_images]) - if (is_win() or is_macosx()) and (hasattr(sys, 'frozen') and sys.frozen == 1): - add_actions(self.help_menu, (self.offline_help_item, self.on_line_help_item, None, self.web_site_item, - self.about_item)) - else: - add_actions(self.help_menu, (self.on_line_help_item, None, self.web_site_item, self.about_item)) + add_actions(self.help_menu, (self.on_help_item, None, self.web_site_item, self.about_item)) add_actions(self.menu_bar, (self.file_menu.menuAction(), self.view_menu.menuAction(), self.tools_menu.menuAction(), self.settings_menu.menuAction(), self.help_menu.menuAction())) add_actions(self, [self.search_shortcut_action]) @@ -462,9 +450,7 @@ class Ui_MainWindow(object): 'from here.')) self.about_item.setText(translate('OpenLP.MainWindow', '&About')) self.about_item.setStatusTip(translate('OpenLP.MainWindow', 'More information about OpenLP.')) - if is_win() or is_macosx(): - self.offline_help_item.setText(translate('OpenLP.MainWindow', '&User Guide')) - self.on_line_help_item.setText(translate('OpenLP.MainWindow', '&Online Help')) + self.on_help_item.setText(translate('OpenLP.MainWindow', '&User Manual')) self.search_shortcut_action.setText(UiStrings().Search) self.search_shortcut_action.setToolTip( translate('OpenLP.MainWindow', 'Jump to the search box of the current active plugin.')) @@ -778,18 +764,16 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties): import webbrowser webbrowser.open_new('http://openlp.org/') - def on_offline_help_clicked(self): + def on_help_clicked(self): """ - Load the local OpenLP help file + If is_macosx or is_win, open the local OpenLP help file. + Use the Online manual in other cases. (Linux) """ - QtGui.QDesktopServices.openUrl(QtCore.QUrl("file:///" + self.local_help_file)) - - def on_online_help_clicked(self): - """ - Load the online OpenLP manual - """ - import webbrowser - webbrowser.open_new('http://manual.openlp.org/') + if is_macosx() or is_win(): + QtGui.QDesktopServices.openUrl(QtCore.QUrl("file:///" + self.local_help_file)) + else: + import webbrowser + webbrowser.open_new('http://manual.openlp.org/') def on_about_item_clicked(self): """ diff --git a/openlp/core/ui/plugindialog.py b/openlp/core/ui/plugindialog.py index ad461cd44..cd41d77a8 100644 --- a/openlp/core/ui/plugindialog.py +++ b/openlp/core/ui/plugindialog.py @@ -38,7 +38,7 @@ class Ui_PluginViewDialog(object): Set up the UI """ plugin_view_dialog.setObjectName('plugin_view_dialog') - plugin_view_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg')) + plugin_view_dialog.setWindowIcon(build_icon(':/icon/openlp-logo.svg')) plugin_view_dialog.setWindowModality(QtCore.Qt.ApplicationModal) self.plugin_layout = QtWidgets.QVBoxLayout(plugin_view_dialog) self.plugin_layout.setObjectName('plugin_layout') diff --git a/openlp/core/ui/printservicedialog.py b/openlp/core/ui/printservicedialog.py index c658d1496..f82bfecd4 100644 --- a/openlp/core/ui/printservicedialog.py +++ b/openlp/core/ui/printservicedialog.py @@ -50,7 +50,7 @@ class Ui_PrintServiceDialog(object): Set up the UI """ print_service_dialog.setObjectName('print_service_dialog') - print_service_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg')) + print_service_dialog.setWindowIcon(build_icon(':/icon/openlp-logo.svg')) print_service_dialog.resize(664, 594) self.main_layout = QtWidgets.QVBoxLayout(print_service_dialog) self.main_layout.setSpacing(0) diff --git a/openlp/core/ui/projector/editform.py b/openlp/core/ui/projector/editform.py index f4cf8a774..f15d5b550 100644 --- a/openlp/core/ui/projector/editform.py +++ b/openlp/core/ui/projector/editform.py @@ -47,7 +47,7 @@ class Ui_ProjectorEditForm(object): Create the interface layout. """ edit_projector_dialog.setObjectName('edit_projector_dialog') - edit_projector_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo-32x32.png')) + edit_projector_dialog.setWindowIcon(build_icon(':/icon/openlp-logo.svg')) edit_projector_dialog.setMinimumWidth(400) edit_projector_dialog.setModal(True) # Define the basic layout diff --git a/openlp/core/ui/serviceitemeditdialog.py b/openlp/core/ui/serviceitemeditdialog.py index bbff73f3d..6dc45a847 100644 --- a/openlp/core/ui/serviceitemeditdialog.py +++ b/openlp/core/ui/serviceitemeditdialog.py @@ -38,7 +38,7 @@ class Ui_ServiceItemEditDialog(object): Set up the UI """ serviceItemEditDialog.setObjectName('serviceItemEditDialog') - serviceItemEditDialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg')) + serviceItemEditDialog.setWindowIcon(build_icon(':/icon/openlp-logo.svg')) self.dialog_layout = QtWidgets.QGridLayout(serviceItemEditDialog) self.dialog_layout.setContentsMargins(8, 8, 8, 8) self.dialog_layout.setSpacing(8) diff --git a/openlp/core/ui/settingsdialog.py b/openlp/core/ui/settingsdialog.py index 3b6c4bc00..97c4c7233 100644 --- a/openlp/core/ui/settingsdialog.py +++ b/openlp/core/ui/settingsdialog.py @@ -38,7 +38,7 @@ class Ui_SettingsDialog(object): Set up the UI """ settings_dialog.setObjectName('settings_dialog') - settings_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg')) + settings_dialog.setWindowIcon(build_icon(':/icon/openlp-logo.svg')) settings_dialog.resize(800, 700) self.dialog_layout = QtWidgets.QGridLayout(settings_dialog) self.dialog_layout.setObjectName('dialog_layout') diff --git a/openlp/core/ui/shortcutlistdialog.py b/openlp/core/ui/shortcutlistdialog.py index ed1156025..4de2d4861 100644 --- a/openlp/core/ui/shortcutlistdialog.py +++ b/openlp/core/ui/shortcutlistdialog.py @@ -72,7 +72,7 @@ class Ui_ShortcutListDialog(object): Set up the UI """ shortcutListDialog.setObjectName('shortcutListDialog') - shortcutListDialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg')) + shortcutListDialog.setWindowIcon(build_icon(':/icon/openlp-logo.svg')) shortcutListDialog.resize(500, 438) self.shortcut_list_layout = QtWidgets.QVBoxLayout(shortcutListDialog) self.shortcut_list_layout.setObjectName('shortcut_list_layout') diff --git a/openlp/core/ui/shortcutlistform.py b/openlp/core/ui/shortcutlistform.py index 1266d1cc4..eb91313c9 100644 --- a/openlp/core/ui/shortcutlistform.py +++ b/openlp/core/ui/shortcutlistform.py @@ -426,11 +426,11 @@ class ShortcutListForm(QtWidgets.QDialog, Ui_ShortcutListDialog, RegistryPropert is_valid = False if not is_valid: text = translate('OpenLP.ShortcutListDialog', - 'The shortcut "{key}" is already assigned to another action, please' - ' use a different shortcut.' + 'The shortcut "{key}" is already assigned to another action,\n' + 'please use a different shortcut.' ).format(key=self.get_shortcut_string(key_sequence)) self.main_window.warning_message(translate('OpenLP.ShortcutListDialog', 'Duplicate Shortcut'), - text, for_display=True) + text) self.dialog_was_shown = True return is_valid diff --git a/openlp/core/ui/slidecontroller.py b/openlp/core/ui/slidecontroller.py index 7121e5227..01bc2bd0f 100644 --- a/openlp/core/ui/slidecontroller.py +++ b/openlp/core/ui/slidecontroller.py @@ -234,25 +234,32 @@ class SlideController(DisplayController, RegistryProperties): self.hide_menu.setPopupMode(QtWidgets.QToolButton.MenuButtonPopup) self.hide_menu.setMenu(QtWidgets.QMenu(translate('OpenLP.SlideController', 'Hide'), self.toolbar)) self.toolbar.add_toolbar_widget(self.hide_menu) - self.blank_screen = create_action(self, 'blankScreen', - text=translate('OpenLP.SlideController', 'Blank Screen'), - icon=':/slides/slide_blank.png', - checked=False, can_shortcuts=True, category=self.category, - triggers=self.on_blank_display) - self.theme_screen = create_action(self, 'themeScreen', - text=translate('OpenLP.SlideController', 'Blank to Theme'), - icon=':/slides/slide_theme.png', - checked=False, can_shortcuts=True, category=self.category, - triggers=self.on_theme_display) + # The order of the blank to modes in Shortcuts list comes from here. + self.desktop_screen_enable = create_action(self, 'desktopScreenEnable', + text=translate('OpenLP.SlideController', 'Show Desktop'), + icon=':/slides/slide_desktop.png', can_shortcuts=True, + context=QtCore.Qt.WidgetWithChildrenShortcut, + category=self.category, triggers=self.on_hide_display_enable) self.desktop_screen = create_action(self, 'desktopScreen', - text=translate('OpenLP.SlideController', 'Show Desktop'), + text=translate('OpenLP.SlideController', 'Toggle Desktop'), icon=':/slides/slide_desktop.png', checked=False, can_shortcuts=True, category=self.category, triggers=self.on_hide_display) + self.theme_screen = create_action(self, 'themeScreen', + text=translate('OpenLP.SlideController', 'Toggle Blank to Theme'), + icon=':/slides/slide_theme.png', + checked=False, can_shortcuts=True, category=self.category, + triggers=self.on_theme_display) + self.blank_screen = create_action(self, 'blankScreen', + text=translate('OpenLP.SlideController', 'Toggle Blank Screen'), + icon=':/slides/slide_blank.png', + checked=False, can_shortcuts=True, category=self.category, + triggers=self.on_blank_display) self.hide_menu.setDefaultAction(self.blank_screen) self.hide_menu.menu().addAction(self.blank_screen) self.hide_menu.menu().addAction(self.theme_screen) self.hide_menu.menu().addAction(self.desktop_screen) + self.hide_menu.menu().addAction(self.desktop_screen_enable) # Wide menu of display control buttons. self.blank_screen_button = QtWidgets.QToolButton(self.toolbar) self.blank_screen_button.setObjectName('blank_screen_button') @@ -512,23 +519,6 @@ class SlideController(DisplayController, RegistryProperties): can_shortcuts=True, context=QtCore.Qt.WidgetWithChildrenShortcut, category=self.category, triggers=self.service_next) - self.escape_item = create_action(parent, 'escapeItem', - text=translate('OpenLP.SlideController', 'Escape Item'), - can_shortcuts=True, context=QtCore.Qt.WidgetWithChildrenShortcut, - category=self.category, - triggers=self.live_escape) - - def live_escape(self, field=None): - """ - If you press ESC on the live screen it should close the display temporarily. - """ - self.display.setVisible(False) - self.media_controller.media_stop(self) - # Stop looping if active - if self.play_slides_loop.isChecked(): - self.on_play_slides_loop(False) - elif self.play_slides_once.isChecked(): - self.on_play_slides_once(False) def toggle_display(self, action): """ @@ -622,7 +612,7 @@ class SlideController(DisplayController, RegistryProperties): widget.addActions([ self.previous_item, self.next_item, self.previous_service, self.next_service, - self.escape_item, + self.desktop_screen_enable, self.desktop_screen, self.theme_screen, self.blank_screen]) @@ -732,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()): @@ -965,7 +957,7 @@ class SlideController(DisplayController, RegistryProperties): else: Registry().execute('live_display_show') else: - self.live_escape() + self.on_hide_display_enable() def on_slide_blank(self): """ @@ -1025,6 +1017,7 @@ class SlideController(DisplayController, RegistryProperties): def on_hide_display(self, checked=None): """ Handle the Hide screen button + This toggles the desktop screen. :param checked: the new state of the of the widget """ @@ -1043,6 +1036,20 @@ class SlideController(DisplayController, RegistryProperties): self.update_preview() self.on_toggle_loop() + def on_hide_display_enable(self, checked=None): + """ + Handle the on_hide_display_enable + This only enables the desktop screen. + + :param checked: the new state of the of the widget + """ + self.blank_screen.setChecked(False) + self.theme_screen.setChecked(False) + Registry().execute('live_display_hide', HideMode.Screen) + self.desktop_screen.setChecked(True) + self.update_preview() + self.on_toggle_loop() + def blank_plugin(self): """ Blank/Hide the display screen within a plugin if required. diff --git a/openlp/core/ui/starttimedialog.py b/openlp/core/ui/starttimedialog.py index 42287596c..e20d625d0 100644 --- a/openlp/core/ui/starttimedialog.py +++ b/openlp/core/ui/starttimedialog.py @@ -38,7 +38,7 @@ class Ui_StartTimeDialog(object): Set up the UI """ StartTimeDialog.setObjectName('StartTimeDialog') - StartTimeDialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg')) + StartTimeDialog.setWindowIcon(build_icon(':/icon/openlp-logo.svg')) StartTimeDialog.resize(350, 10) self.dialog_layout = QtWidgets.QGridLayout(StartTimeDialog) self.dialog_layout.setObjectName('dialog_layout') diff --git a/openlp/core/ui/themelayoutdialog.py b/openlp/core/ui/themelayoutdialog.py index 5289e1cc2..119611f33 100644 --- a/openlp/core/ui/themelayoutdialog.py +++ b/openlp/core/ui/themelayoutdialog.py @@ -38,7 +38,7 @@ class Ui_ThemeLayoutDialog(object): Set up the UI """ themeLayoutDialog.setObjectName('themeLayoutDialogDialog') - themeLayoutDialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg')) + themeLayoutDialog.setWindowIcon(build_icon(':/icon/openlp-logo.svg')) self.preview_layout = QtWidgets.QVBoxLayout(themeLayoutDialog) self.preview_layout.setObjectName('preview_layout') self.preview_area = QtWidgets.QWidget(themeLayoutDialog) diff --git a/openlp/core/ui/themewizard.py b/openlp/core/ui/themewizard.py index 427e9836b..95262cf8f 100644 --- a/openlp/core/ui/themewizard.py +++ b/openlp/core/ui/themewizard.py @@ -40,7 +40,7 @@ class Ui_ThemeWizard(object): Set up the UI """ theme_wizard.setObjectName('OpenLP.ThemeWizard') - theme_wizard.setWindowIcon(build_icon(u':/icon/openlp-logo.svg')) + theme_wizard.setWindowIcon(build_icon(':/icon/openlp-logo.svg')) theme_wizard.setModal(True) theme_wizard.setOptions(QtWidgets.QWizard.IndependentPages | QtWidgets.QWizard.NoBackButtonOnStartPage | QtWidgets.QWizard.HaveCustomButton1) diff --git a/openlp/plugins/alerts/forms/alertdialog.py b/openlp/plugins/alerts/forms/alertdialog.py index 81af23ae1..83df7a09c 100644 --- a/openlp/plugins/alerts/forms/alertdialog.py +++ b/openlp/plugins/alerts/forms/alertdialog.py @@ -39,7 +39,7 @@ class Ui_AlertDialog(object): """ alert_dialog.setObjectName('alert_dialog') alert_dialog.resize(400, 300) - alert_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg')) + alert_dialog.setWindowIcon(build_icon(':/icon/openlp-logo.svg')) self.alert_dialog_layout = QtWidgets.QGridLayout(alert_dialog) self.alert_dialog_layout.setObjectName('alert_dialog_layout') self.alert_text_layout = QtWidgets.QFormLayout() diff --git a/openlp/plugins/bibles/forms/booknamedialog.py b/openlp/plugins/bibles/forms/booknamedialog.py index 6679dc1f3..18adde74b 100644 --- a/openlp/plugins/bibles/forms/booknamedialog.py +++ b/openlp/plugins/bibles/forms/booknamedialog.py @@ -30,7 +30,7 @@ from openlp.core.lib.ui import create_button_box class Ui_BookNameDialog(object): def setupUi(self, book_name_dialog): book_name_dialog.setObjectName('book_name_dialog') - book_name_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg')) + book_name_dialog.setWindowIcon(build_icon(':/icon/openlp-logo.svg')) book_name_dialog.resize(400, 271) self.book_name_layout = QtWidgets.QVBoxLayout(book_name_dialog) self.book_name_layout.setSpacing(8) diff --git a/openlp/plugins/bibles/forms/editbibledialog.py b/openlp/plugins/bibles/forms/editbibledialog.py index f326f2af0..7a2de263e 100644 --- a/openlp/plugins/bibles/forms/editbibledialog.py +++ b/openlp/plugins/bibles/forms/editbibledialog.py @@ -32,7 +32,7 @@ from openlp.plugins.bibles.lib.db import BiblesResourcesDB class Ui_EditBibleDialog(object): def setupUi(self, edit_bible_dialog): edit_bible_dialog.setObjectName('edit_bible_dialog') - edit_bible_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg')) + edit_bible_dialog.setWindowIcon(build_icon(':/icon/openlp-logo.svg')) edit_bible_dialog.resize(520, 400) edit_bible_dialog.setModal(True) self.dialog_layout = QtWidgets.QVBoxLayout(edit_bible_dialog) diff --git a/openlp/plugins/bibles/forms/languagedialog.py b/openlp/plugins/bibles/forms/languagedialog.py index ea5c291a0..554c7e35d 100644 --- a/openlp/plugins/bibles/forms/languagedialog.py +++ b/openlp/plugins/bibles/forms/languagedialog.py @@ -30,7 +30,7 @@ from openlp.core.lib.ui import create_button_box class Ui_LanguageDialog(object): def setupUi(self, language_dialog): language_dialog.setObjectName('language_dialog') - language_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg')) + language_dialog.setWindowIcon(build_icon(':/icon/openlp-logo.svg')) language_dialog.resize(400, 165) self.language_layout = QtWidgets.QVBoxLayout(language_dialog) self.language_layout.setSpacing(8) diff --git a/openlp/plugins/bibles/lib/importers/http.py b/openlp/plugins/bibles/lib/importers/http.py index 6921c9005..d41187d93 100644 --- a/openlp/plugins/bibles/lib/importers/http.py +++ b/openlp/plugins/bibles/lib/importers/http.py @@ -493,7 +493,7 @@ class CWExtract(RegistryProperties): for verse in verses_div: self.application.process_events() verse_number = int(verse.find('strong').contents[0]) - verse_span = verse.find('span') + verse_span = verse.find('span', class_='verse-%d' % verse_number) tags_to_remove = verse_span.find_all(['a', 'sup']) for tag in tags_to_remove: tag.decompose() diff --git a/openlp/plugins/bibles/lib/manager.py b/openlp/plugins/bibles/lib/manager.py index 10a02172b..d2913053a 100644 --- a/openlp/plugins/bibles/lib/manager.py +++ b/openlp/plugins/bibles/lib/manager.py @@ -131,7 +131,7 @@ class BibleManager(OpenLPMixin, RegistryProperties): name = bible.get_name() # Remove corrupted files. if name is None: - bible.session.close() + bible.session.close_all() delete_file(os.path.join(self.path, filename)) continue log.debug('Bible Name: "{name}"'.format(name=name)) @@ -178,7 +178,7 @@ class BibleManager(OpenLPMixin, RegistryProperties): """ log.debug('BibleManager.delete_bible("{name}")'.format(name=name)) bible = self.db_cache[name] - bible.session.close() + bible.session.close_all() bible.session = None return delete_file(os.path.join(bible.path, bible.file)) @@ -367,7 +367,6 @@ class BibleManager(OpenLPMixin, RegistryProperties): 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 search ends w/o any message. - self.check_search_result() self.application.set_normal_cursor() return None # Fetch the results from db. If no results are found, return None, no message is given for this. diff --git a/openlp/plugins/bibles/lib/mediaitem.py b/openlp/plugins/bibles/lib/mediaitem.py index b8ff3a752..9a04e5360 100644 --- a/openlp/plugins/bibles/lib/mediaitem.py +++ b/openlp/plugins/bibles/lib/mediaitem.py @@ -75,21 +75,16 @@ class BibleMediaItem(MediaManagerItem): self.has_search = True self.search_results = {} self.second_search_results = {} - self.check_search_result() Registry().register_function('bibles_load_list', self.reload_bibles) def __check_second_bible(self, bible, second_bible): """ Check if the first item is a second bible item or not. """ - bitem = self.list_view.item(0) - if not bitem.flags() & QtCore.Qt.ItemIsSelectable: - # The item is the "No Search Results" item. - self.list_view.clear() + if not self.list_view.count(): self.display_results(bible, second_bible) return - else: - item_second_bible = self._decode_qt_object(bitem, 'second_bible') + item_second_bible = self._decode_qt_object(self.list_view.item(0), 'second_bible') if item_second_bible and second_bible or not item_second_bible and not second_bible: self.display_results(bible, second_bible) elif critical_error_message_box( @@ -199,7 +194,7 @@ class BibleMediaItem(MediaManagerItem): self.quick_search_label = QtWidgets.QLabel(self.quickTab) self.quick_search_label.setObjectName('quick_search_label') self.quickLayout.addWidget(self.quick_search_label, 0, 0, QtCore.Qt.AlignRight) - self.quick_search_edit = SearchEdit(self.quickTab) + self.quick_search_edit = SearchEdit(self.quickTab, self.settings_section) self.quick_search_edit.setSizePolicy(QtWidgets.QSizePolicy.Ignored, QtWidgets.QSizePolicy.Fixed) self.quick_search_edit.setObjectName('quick_search_edit') self.quick_search_label.setBuddy(self.quick_search_edit) @@ -254,8 +249,8 @@ class BibleMediaItem(MediaManagerItem): self.quickStyleComboBox.activated.connect(self.on_quick_style_combo_box_changed) self.advancedStyleComboBox.activated.connect(self.on_advanced_style_combo_box_changed) # Buttons - self.advancedClearButton.clicked.connect(self.on_clear_button) - self.quickClearButton.clicked.connect(self.on_clear_button) + self.advancedClearButton.clicked.connect(self.on_advanced_clear_button_clicked) + self.quickClearButton.clicked.connect(self.on_clear_button_clicked) self.advancedSearchButton.clicked.connect(self.on_advanced_search_button) self.quickSearchButton.clicked.connect(self.on_quick_search_button) # Other stuff @@ -333,8 +328,8 @@ class BibleMediaItem(MediaManagerItem): translate('BiblesPlugin.MediaItem', 'Text Search'), translate('BiblesPlugin.MediaItem', 'Search Text...')) ]) - text = self.settings_section - self.quick_search_edit.set_current_search_type(Settings().value('{text}/last search type'.format(text=text))) + if Settings().value(self.settings_section + '/reset to combined quick search'): + self.quick_search_edit.set_current_search_type(BibleSearch.Combined) self.config_update() log.debug('bible manager initialise complete') @@ -444,15 +439,6 @@ class BibleMediaItem(MediaManagerItem): only updated when we are doing reference or combined search, in text search the completion list is removed. """ log.debug('update_auto_completer') - # Save the current search type to the configuration. If setting for automatically resetting the search type to - # Combined is enabled, use that otherwise use the currently selected search type. - # Note: This setting requires a restart to take effect. - if Settings().value(self.settings_section + '/reset to combined quick search'): - Settings().setValue('{section}/last search type'.format(section=self.settings_section), - BibleSearch.Combined) - else: - Settings().setValue('{section}/last search type'.format(section=self.settings_section), - self.quick_search_edit.current_search_type()) # Save the current bible to the configuration. Settings().setValue('{section}/quick bible'.format(section=self.settings_section), self.quickVersionComboBox.currentText()) @@ -548,19 +534,29 @@ class BibleMediaItem(MediaManagerItem): self.advancedTab.setVisible(True) self.advanced_book_combo_box.setFocus() - def on_clear_button(self): + def on_clear_button_clicked(self): # Clear the list, then set the "No search Results" message, then clear the text field and give it focus. self.list_view.clear() - self.check_search_result() self.quick_search_edit.clear() self.quick_search_edit.setFocus() + def on_advanced_clear_button_clicked(self): + # The same as the on_clear_button_clicked, but gives focus to Book name field in "Select" (advanced). + self.list_view.clear() + self.advanced_book_combo_box.setFocus() + def on_lock_button_toggled(self, checked): - self.quick_search_edit.setFocus() + """ + Toggle the lock button, if Search tab is used, set focus to search field. + :param checked: The state of the toggle button. bool + :return: None + """ if checked: self.sender().setIcon(self.lock_icon) else: self.sender().setIcon(self.unlock_icon) + if self.quickTab.isVisible(): + self.quick_search_edit.setFocus() def on_quick_style_combo_box_changed(self): self.settings.layout_style = self.quickStyleComboBox.currentIndex() @@ -680,7 +676,6 @@ class BibleMediaItem(MediaManagerItem): elif self.search_results: self.display_results(bible, second_bible) self.advancedSearchButton.setEnabled(True) - self.check_search_result() self.application.set_normal_cursor() def on_quick_reference_search(self): @@ -874,7 +869,6 @@ class BibleMediaItem(MediaManagerItem): elif self.search_results: self.display_results(bible, second_bible) self.quickSearchButton.setEnabled(True) - self.check_search_result() self.application.set_normal_cursor() def on_quick_search_while_typing(self): @@ -905,7 +899,6 @@ class BibleMediaItem(MediaManagerItem): self.__check_second_bible(bible, second_bible) elif self.search_results: self.display_results(bible, second_bible) - self.check_search_result() self.application.set_normal_cursor() def on_search_text_edit_changed(self): @@ -944,17 +937,13 @@ class BibleMediaItem(MediaManagerItem): if len(text) == 0: if not self.quickLockButton.isChecked(): self.list_view.clear() - self.check_search_result() else: if limit == 3 and (len(text) < limit or len(count_space_digit_reference) == 0): if not self.quickLockButton.isChecked(): self.list_view.clear() - self.check_search_result() - elif (limit == 8 and (len(text) < limit or len(count_spaces_two_chars_text) == 0 or - len(count_two_chars_text) < 2)): + elif limit == 8 and (len(text) < limit or len(count_two_chars_text) < 2): if not self.quickLockButton.isChecked(): - self.list_view.clear() - self.check_search_result_search_while_typing_short() + self.list_view.clear(search_while_typing=True) else: """ Start search if no chars are entered or deleted for 0.2 s diff --git a/openlp/plugins/custom/forms/editcustomdialog.py b/openlp/plugins/custom/forms/editcustomdialog.py index b686e5522..d25ff337b 100644 --- a/openlp/plugins/custom/forms/editcustomdialog.py +++ b/openlp/plugins/custom/forms/editcustomdialog.py @@ -34,7 +34,7 @@ class Ui_CustomEditDialog(object): :param custom_edit_dialog: The Dialog """ custom_edit_dialog.setObjectName('custom_edit_dialog') - custom_edit_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg')) + custom_edit_dialog.setWindowIcon(build_icon(':/icon/openlp-logo.svg')) custom_edit_dialog.resize(450, 350) self.dialog_layout = QtWidgets.QVBoxLayout(custom_edit_dialog) self.dialog_layout.setObjectName('dialog_layout') diff --git a/openlp/plugins/custom/forms/editcustomslidedialog.py b/openlp/plugins/custom/forms/editcustomslidedialog.py index 5f46c6f28..d08679eee 100644 --- a/openlp/plugins/custom/forms/editcustomslidedialog.py +++ b/openlp/plugins/custom/forms/editcustomslidedialog.py @@ -31,7 +31,7 @@ from openlp.core.ui.lib import SpellTextEdit class Ui_CustomSlideEditDialog(object): def setupUi(self, custom_slide_edit_dialog): custom_slide_edit_dialog.setObjectName('custom_slide_edit_dialog') - custom_slide_edit_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg')) + custom_slide_edit_dialog.setWindowIcon(build_icon(':/icon/openlp-logo.svg')) custom_slide_edit_dialog.resize(350, 300) self.dialog_layout = QtWidgets.QVBoxLayout(custom_slide_edit_dialog) self.slide_text_edit = SpellTextEdit(self) diff --git a/openlp/plugins/custom/lib/mediaitem.py b/openlp/plugins/custom/lib/mediaitem.py index e9e26111f..eacc5a8f5 100644 --- a/openlp/plugins/custom/lib/mediaitem.py +++ b/openlp/plugins/custom/lib/mediaitem.py @@ -105,8 +105,6 @@ class CustomMediaItem(MediaManagerItem): [(CustomSearch.Titles, ':/songs/song_search_title.png', translate('SongsPlugin.MediaItem', 'Titles'), translate('SongsPlugin.MediaItem', 'Search Titles...')), (CustomSearch.Themes, ':/slides/slide_theme.png', UiStrings().Themes, UiStrings().SearchThemes)]) - text = '{section}/last search type'.format(section=self.settings_section) - self.search_text_edit.set_current_search_type(Settings().value(text)) self.load_list(self.plugin.db_manager.get_all_objects(CustomSlide, order_by_ref=CustomSlide.title)) self.config_update() @@ -131,7 +129,6 @@ class CustomMediaItem(MediaManagerItem): # Called to redisplay the custom list screen edith from a search # or from the exit of the Custom edit dialog. If remote editing is # active trigger it and clean up so it will not update again. - self.check_search_result() def on_new_click(self): """ @@ -250,9 +247,6 @@ class CustomMediaItem(MediaManagerItem): """ Search the plugin database """ - # Save the current search type to the configuration. - Settings().setValue('{section}/last search type'.format(section=self.settings_section), - self.search_text_edit.current_search_type()) # Reload the list considering the new search type. search_type = self.search_text_edit.current_search_type() search_keywords = '%{search}%'.format(search=self.whitespace.sub(' ', self.search_text_edit.displayText())) @@ -268,7 +262,6 @@ class CustomMediaItem(MediaManagerItem): CustomSlide.theme_name.like(search_keywords), order_by_ref=CustomSlide.title) self.load_list(search_results) - self.check_search_result() def on_search_text_edit_changed(self, text): """ @@ -350,7 +343,7 @@ class CustomMediaItem(MediaManagerItem): :param string: The search string :param show_error: The error string to be show. """ - search = '%{search}%'.forma(search=string.lower()) + search = '%{search}%'.format(search=string.lower()) search_results = self.plugin.db_manager.get_all_objects(CustomSlide, or_(func.lower(CustomSlide.title).like(search), func.lower(CustomSlide.text).like(search)), diff --git a/openlp/plugins/songs/forms/authorsdialog.py b/openlp/plugins/songs/forms/authorsdialog.py index 666baffec..7b2995480 100644 --- a/openlp/plugins/songs/forms/authorsdialog.py +++ b/openlp/plugins/songs/forms/authorsdialog.py @@ -36,7 +36,7 @@ class Ui_AuthorsDialog(object): Set up the UI for the dialog. """ authors_dialog.setObjectName('authors_dialog') - authors_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg')) + authors_dialog.setWindowIcon(build_icon(':/icon/openlp-logo.svg')) authors_dialog.resize(300, 10) authors_dialog.setModal(True) self.dialog_layout = QtWidgets.QVBoxLayout(authors_dialog) diff --git a/openlp/plugins/songs/forms/editsongdialog.py b/openlp/plugins/songs/forms/editsongdialog.py index 46e37c1cd..b23fe1189 100644 --- a/openlp/plugins/songs/forms/editsongdialog.py +++ b/openlp/plugins/songs/forms/editsongdialog.py @@ -36,7 +36,7 @@ class Ui_EditSongDialog(object): """ def setupUi(self, edit_song_dialog): edit_song_dialog.setObjectName('edit_song_dialog') - edit_song_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg')) + edit_song_dialog.setWindowIcon(build_icon(':/icon/openlp-logo.svg')) edit_song_dialog.resize(900, 600) edit_song_dialog.setModal(True) self.dialog_layout = QtWidgets.QVBoxLayout(edit_song_dialog) diff --git a/openlp/plugins/songs/forms/editsongform.py b/openlp/plugins/songs/forms/editsongform.py index 200e1436f..a17c9fb5f 100644 --- a/openlp/plugins/songs/forms/editsongform.py +++ b/openlp/plugins/songs/forms/editsongform.py @@ -317,7 +317,7 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties): self.song.verse_order = re.sub('([' + verse.upper() + verse.lower() + '])(\W|$)', r'\g<1>1\2', self.song.verse_order) except: - log.exception('Problem processing song Lyrics \n{xml}'.forma(xml=sxml.dump_xml())) + log.exception('Problem processing song Lyrics \n{xml}'.format(xml=sxml.dump_xml())) raise def keyPressEvent(self, event): diff --git a/openlp/plugins/songs/forms/editversedialog.py b/openlp/plugins/songs/forms/editversedialog.py index 6819f3ca6..13dce97fb 100644 --- a/openlp/plugins/songs/forms/editversedialog.py +++ b/openlp/plugins/songs/forms/editversedialog.py @@ -31,7 +31,7 @@ from openlp.plugins.songs.lib import VerseType class Ui_EditVerseDialog(object): def setupUi(self, edit_verse_dialog): edit_verse_dialog.setObjectName('edit_verse_dialog') - edit_verse_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg')) + edit_verse_dialog.setWindowIcon(build_icon(':/icon/openlp-logo.svg')) edit_verse_dialog.resize(400, 400) edit_verse_dialog.setModal(True) self.dialog_layout = QtWidgets.QVBoxLayout(edit_verse_dialog) diff --git a/openlp/plugins/songs/forms/mediafilesdialog.py b/openlp/plugins/songs/forms/mediafilesdialog.py index 4309eb91f..2b9bcceab 100644 --- a/openlp/plugins/songs/forms/mediafilesdialog.py +++ b/openlp/plugins/songs/forms/mediafilesdialog.py @@ -35,7 +35,7 @@ class Ui_MediaFilesDialog(object): Set up the user interface. """ media_files_dialog.setObjectName('media_files_dialog') - media_files_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg')) + media_files_dialog.setWindowIcon(build_icon(':/icon/openlp-logo.svg')) media_files_dialog.setWindowModality(QtCore.Qt.ApplicationModal) media_files_dialog.resize(400, 300) media_files_dialog.setModal(True) diff --git a/openlp/plugins/songs/forms/songbookdialog.py b/openlp/plugins/songs/forms/songbookdialog.py index 7e1c04d20..10ed154b4 100644 --- a/openlp/plugins/songs/forms/songbookdialog.py +++ b/openlp/plugins/songs/forms/songbookdialog.py @@ -35,7 +35,7 @@ class Ui_SongBookDialog(object): Set up the user interface. """ song_book_dialog.setObjectName('song_book_dialog') - song_book_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg')) + song_book_dialog.setWindowIcon(build_icon(':/icon/openlp-logo.svg')) song_book_dialog.resize(300, 10) self.dialog_layout = QtWidgets.QVBoxLayout(song_book_dialog) self.dialog_layout.setObjectName('dialog_layout') diff --git a/openlp/plugins/songs/forms/songexportform.py b/openlp/plugins/songs/forms/songexportform.py index 27f317963..b0d044b2d 100644 --- a/openlp/plugins/songs/forms/songexportform.py +++ b/openlp/plugins/songs/forms/songexportform.py @@ -51,7 +51,7 @@ class SongExportForm(OpenLPWizard): :param parent: The QWidget-derived parent of the wizard. :param plugin: The songs plugin. """ - super(SongExportForm, self).__init__(parent, plugin, 'song_export_wizard', ':/wizards/wizard_exportsong.bmp') + super(SongExportForm, self).__init__(parent, plugin, 'song_export_wizard', ':/wizards/wizard_song.bmp') self.stop_export_flag = False Registry().register_function('openlp_stop_wizard', self.stop_export) diff --git a/openlp/plugins/songs/forms/songimportform.py b/openlp/plugins/songs/forms/songimportform.py index 2539973da..b25021ca9 100644 --- a/openlp/plugins/songs/forms/songimportform.py +++ b/openlp/plugins/songs/forms/songimportform.py @@ -52,7 +52,7 @@ class SongImportForm(OpenLPWizard, RegistryProperties): :param parent: The QWidget-derived parent of the wizard. :param plugin: The songs plugin. """ - super(SongImportForm, self).__init__(parent, plugin, 'songImportWizard', ':/wizards/wizard_importsong.bmp') + super(SongImportForm, self).__init__(parent, plugin, 'songImportWizard', ':/wizards/wizard_song.bmp') self.clipboard = self.main_window.clipboard def setupUi(self, image): diff --git a/openlp/plugins/songs/forms/songmaintenancedialog.py b/openlp/plugins/songs/forms/songmaintenancedialog.py index cd224267b..c0c532688 100644 --- a/openlp/plugins/songs/forms/songmaintenancedialog.py +++ b/openlp/plugins/songs/forms/songmaintenancedialog.py @@ -37,7 +37,7 @@ class Ui_SongMaintenanceDialog(object): Set up the user interface for the song maintenance dialog """ song_maintenance_dialog.setObjectName('song_maintenance_dialog') - song_maintenance_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg')) + song_maintenance_dialog.setWindowIcon(build_icon(':/icon/openlp-logo.svg')) song_maintenance_dialog.setWindowModality(QtCore.Qt.ApplicationModal) song_maintenance_dialog.resize(10, 350) self.dialog_layout = QtWidgets.QGridLayout(song_maintenance_dialog) diff --git a/openlp/plugins/songs/forms/songreviewwidget.py b/openlp/plugins/songs/forms/songreviewwidget.py index c8873f028..2e5814626 100644 --- a/openlp/plugins/songs/forms/songreviewwidget.py +++ b/openlp/plugins/songs/forms/songreviewwidget.py @@ -182,7 +182,7 @@ class SongReviewWidget(QtWidgets.QWidget): self.song_vertical_layout.addWidget(self.song_group_box) self.song_remove_button = QtWidgets.QPushButton(self) self.song_remove_button.setObjectName('song_remove_button') - self.song_remove_button.setIcon(build_icon(':/songs/song_delete.png')) + self.song_remove_button.setIcon(build_icon(':/general/general_delete.png')) self.song_remove_button.setSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) self.song_vertical_layout.addWidget(self.song_remove_button, alignment=QtCore.Qt.AlignHCenter) diff --git a/openlp/plugins/songs/forms/topicsdialog.py b/openlp/plugins/songs/forms/topicsdialog.py index 1ed3ac61b..90c19238d 100644 --- a/openlp/plugins/songs/forms/topicsdialog.py +++ b/openlp/plugins/songs/forms/topicsdialog.py @@ -35,7 +35,7 @@ class Ui_TopicsDialog(object): Set up the user interface for the topics dialog. """ topics_dialog.setObjectName('topics_dialog') - topics_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg')) + topics_dialog.setWindowIcon(build_icon(':/icon/openlp-logo.svg')) topics_dialog.resize(300, 10) self.dialog_layout = QtWidgets.QVBoxLayout(topics_dialog) self.dialog_layout.setObjectName('dialog_layout') diff --git a/openlp/plugins/songs/lib/importers/mediashout.py b/openlp/plugins/songs/lib/importers/mediashout.py index a3bd7bbbc..9b916cd43 100644 --- a/openlp/plugins/songs/lib/importers/mediashout.py +++ b/openlp/plugins/songs/lib/importers/mediashout.py @@ -101,7 +101,7 @@ class MediaShoutImport(SongImport): self.song_book_name = song.SongID for verse in verses: tag = VERSE_TAGS[verse.Type] + str(verse.Number) if verse.Type < len(VERSE_TAGS) else 'O' - self.add_verse(verse.Text, tag) + self.add_verse(self.tidy_text(verse.Text), tag) for order in verse_order: if order.Type < len(VERSE_TAGS): self.verse_order_list.append(VERSE_TAGS[order.Type] + str(order.Number)) diff --git a/openlp/plugins/songs/lib/importers/songimport.py b/openlp/plugins/songs/lib/importers/songimport.py index c9ef382a7..c8648db85 100644 --- a/openlp/plugins/songs/lib/importers/songimport.py +++ b/openlp/plugins/songs/lib/importers/songimport.py @@ -140,10 +140,13 @@ class SongImport(QtCore.QObject): text = text.replace('\u2026', '...') text = text.replace('\u2013', '-') text = text.replace('\u2014', '-') + # Replace vertical tab with 2 linebreaks + text = text.replace('\v', '\n\n') + # Replace form feed (page break) with 2 linebreaks + text = text.replace('\f', '\n\n') # Remove surplus blank lines, spaces, trailing/leading spaces - text = re.sub(r'[ \t\v]+', ' ', text) + text = re.sub(r'[ \t]+', ' ', text) text = re.sub(r' ?(\r\n?|\n) ?', '\n', text) - text = re.sub(r' ?(\n{5}|\f)+ ?', '\f', text) return text def process_song_text(self, text): diff --git a/openlp/plugins/songs/lib/mediaitem.py b/openlp/plugins/songs/lib/mediaitem.py index 4e048c6bb..d4aa80a6d 100644 --- a/openlp/plugins/songs/lib/mediaitem.py +++ b/openlp/plugins/songs/lib/mediaitem.py @@ -167,14 +167,9 @@ class SongMediaItem(MediaManagerItem): translate('SongsPlugin.MediaItem', 'CCLI number'), translate('SongsPlugin.MediaItem', 'Search CCLI number...')) ]) - self.search_text_edit.set_current_search_type( - Settings().value('{section}/last search type'.format(section=self.settings_section))) self.config_update() def on_search_text_button_clicked(self): - # Save the current search type to the configuration. - Settings().setValue('{section}/last search type'.format(section=self.settings_section), - self.search_text_edit.current_search_type()) # Reload the list considering the new search type. search_keywords = str(self.search_text_edit.displayText()) search_type = self.search_text_edit.current_search_type() @@ -233,7 +228,6 @@ class SongMediaItem(MediaManagerItem): search_results = self.plugin.manager.get_all_objects( Song, and_(Song.ccli_number.like(search_string), Song.ccli_number != '')) self.display_results_cclinumber(search_results) - self.check_search_result() def search_entire(self, search_keywords): search_string = '%{text}%'.format(text=clean_string(search_keywords)) diff --git a/openlp/plugins/songs/lib/openlyricsxml.py b/openlp/plugins/songs/lib/openlyricsxml.py index b4b7651af..52b57302f 100644 --- a/openlp/plugins/songs/lib/openlyricsxml.py +++ b/openlp/plugins/songs/lib/openlyricsxml.py @@ -458,7 +458,7 @@ class OpenLyrics(object): self._add_tag_to_formatting(tag, tags_element) # Replace end tags. for tag in end_tags: - text = text.replace('{{{tag}}}'.format(tag=tag), '') + text = text.replace('{{/{tag}}}'.format(tag=tag), '') # Replace \n with
. text = text.replace('\n', '
') element = etree.XML('{text}'.format(text=text)) diff --git a/openlp/plugins/songs/lib/songcompare.py b/openlp/plugins/songs/lib/songcompare.py index 44e17a3ac..1d19deaaa 100644 --- a/openlp/plugins/songs/lib/songcompare.py +++ b/openlp/plugins/songs/lib/songcompare.py @@ -46,13 +46,13 @@ MIN_BLOCK_SIZE = 70 MAX_TYPO_SIZE = 3 -def songs_probably_equal(song_tupel): +def songs_probably_equal(song_tuple): """ Calculate and return whether two songs are probably equal. - :param song_tupel: A tuple of two songs to compare. + :param song_tuple: A tuple of two songs to compare. """ - song1, song2 = song_tupel + song1, song2 = song_tuple pos1, lyrics1 = song1 pos2, lyrics2 = song2 if len(lyrics1) < len(lyrics2): diff --git a/openlp/plugins/songs/reporting.py b/openlp/plugins/songs/reporting.py new file mode 100644 index 000000000..98b6713fe --- /dev/null +++ b/openlp/plugins/songs/reporting.py @@ -0,0 +1,106 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2016 OpenLP Developers # +# --------------------------------------------------------------------------- # +# This program is free software; you can redistribute it and/or modify it # +# under the terms of the GNU General Public License as published by the Free # +# Software Foundation; version 2 of the License. # +# # +# This program is distributed in the hope that it will be useful, but WITHOUT # +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # +# more details. # +# # +# You should have received a copy of the GNU General Public License along # +# with this program; if not, write to the Free Software Foundation, Inc., 59 # +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # +############################################################################### +""" +The :mod:`db` module provides the ability to provide a csv file of all songs +""" +import csv +import logging + +from PyQt5 import QtWidgets + +from openlp.core.common import Registry, translate +from openlp.core.lib.ui import critical_error_message_box +from openlp.plugins.songs.lib.db import Song + + +log = logging.getLogger(__name__) + + +def report_song_list(): + """ + Export the song list as a CSV file. + :return: Nothing + """ + main_window = Registry().get('main_window') + plugin = Registry().get('songs').plugin + report_file_name, filter_used = QtWidgets.QFileDialog.getSaveFileName( + main_window, + translate('SongPlugin.ReportSongList', 'Save File'), + translate('SongPlugin.ReportSongList', 'song_extract.csv'), + translate('SongPlugin.ReportSongList', 'CSV format (*.csv)')) + + if not report_file_name: + main_window.error_message( + translate('SongPlugin.ReportSongList', 'Output Path Not Selected'), + translate('SongPlugin.ReportSongList', 'You have not set a valid output location for your ' + 'report. \nPlease select an existing path ' + 'on your computer.') + ) + return + if not report_file_name.endswith('csv'): + report_file_name += '.csv' + file_handle = None + Registry().get('application').set_busy_cursor() + try: + file_handle = open(report_file_name, 'wt') + fieldnames = ('Title', 'Alternative Title', 'Copyright', 'Author(s)', 'Song Book', 'Topic') + writer = csv.DictWriter(file_handle, fieldnames=fieldnames, quoting=csv.QUOTE_ALL) + headers = dict((n, n) for n in fieldnames) + writer.writerow(headers) + song_list = plugin.manager.get_all_objects(Song) + for song in song_list: + author_list = [] + for author_song in song.authors_songs: + author_list.append(author_song.author.display_name) + author_string = ' | '.join(author_list) + book_list = [] + for book_song in song.songbook_entries: + if hasattr(book_song, 'entry') and book_song.entry: + book_list.append('{name} #{entry}'.format(name=book_song.songbook.name, entry=book_song.entry)) + book_string = ' | '.join(book_list) + topic_list = [] + for topic_song in song.topics: + if hasattr(topic_song, 'name'): + topic_list.append(topic_song.name) + topic_string = ' | '.join(topic_list) + writer.writerow({'Title': song.title, + 'Alternative Title': song.alternate_title, + 'Copyright': song.copyright, + 'Author(s)': author_string, + 'Song Book': book_string, + 'Topic': topic_string}) + Registry().get('application').set_normal_cursor() + main_window.information_message( + translate('SongPlugin.ReportSongList', 'Report Creation'), + translate('SongPlugin.ReportSongList', + 'Report \n{name} \nhas been successfully created. ').format(name=report_file_name) + ) + except OSError as ose: + Registry().get('application').set_normal_cursor() + log.exception('Failed to write out song usage records') + critical_error_message_box(translate('SongPlugin.ReportSongList', 'Song Extraction Failed'), + translate('SongPlugin.ReportSongList', + 'An error occurred while extracting: {error}' + ).format(error=ose.strerror)) + finally: + if file_handle: + file_handle.close() diff --git a/openlp/plugins/songs/songsplugin.py b/openlp/plugins/songs/songsplugin.py index 91f07814c..c76befeea 100644 --- a/openlp/plugins/songs/songsplugin.py +++ b/openlp/plugins/songs/songsplugin.py @@ -36,6 +36,7 @@ from openlp.core.common.actions import ActionList from openlp.core.lib import Plugin, StringContent, build_icon from openlp.core.lib.db import Manager from openlp.core.lib.ui import create_action +from openlp.plugins.songs import reporting from openlp.plugins.songs.forms.duplicatesongremovalform import DuplicateSongRemovalForm from openlp.plugins.songs.forms.songselectform import SongSelectForm from openlp.plugins.songs.lib import clean_song, upgrade @@ -103,13 +104,13 @@ class SongsPlugin(Plugin): self.songselect_form.initialise() self.song_import_item.setVisible(True) self.song_export_item.setVisible(True) - self.tools_reindex_item.setVisible(True) - self.tools_find_duplicates.setVisible(True) + self.song_tools_menu.menuAction().setVisible(True) action_list = ActionList.get_instance() action_list.add_action(self.song_import_item, UiStrings().Import) action_list.add_action(self.song_export_item, UiStrings().Export) action_list.add_action(self.tools_reindex_item, UiStrings().Tools) action_list.add_action(self.tools_find_duplicates, UiStrings().Tools) + action_list.add_action(self.tools_report_song_list, UiStrings().Tools) def add_import_menu_item(self, import_menu): """ @@ -152,19 +153,37 @@ class SongsPlugin(Plugin): :param tools_menu: The actual **Tools** menu item, so that your actions can use it as their parent. """ log.info('add tools menu') + self.tools_menu = tools_menu + self.song_tools_menu = QtWidgets.QMenu(tools_menu) + self.song_tools_menu.setObjectName('song_tools_menu') + self.song_tools_menu.setTitle(translate('SongsPlugin', 'Songs')) self.tools_reindex_item = create_action( tools_menu, 'toolsReindexItem', text=translate('SongsPlugin', '&Re-index Songs'), icon=':/plugins/plugin_songs.png', statustip=translate('SongsPlugin', 'Re-index the songs database to improve searching and ordering.'), - visible=False, triggers=self.on_tools_reindex_item_triggered) - tools_menu.addAction(self.tools_reindex_item) + triggers=self.on_tools_reindex_item_triggered) self.tools_find_duplicates = create_action( tools_menu, 'toolsFindDuplicates', text=translate('SongsPlugin', 'Find &Duplicate Songs'), statustip=translate('SongsPlugin', 'Find and remove duplicate songs in the song database.'), - visible=False, triggers=self.on_tools_find_duplicates_triggered, can_shortcuts=True) - tools_menu.addAction(self.tools_find_duplicates) + triggers=self.on_tools_find_duplicates_triggered, can_shortcuts=True) + self.tools_report_song_list = create_action( + tools_menu, 'toolsSongListReport', + text=translate('SongsPlugin', 'Song List Report'), + statustip=translate('SongsPlugin', 'Produce a CSV file of all the songs in the database.'), + triggers=self.on_tools_report_song_list_triggered) + + self.tools_menu.addAction(self.song_tools_menu.menuAction()) + self.song_tools_menu.addAction(self.tools_reindex_item) + self.song_tools_menu.addAction(self.tools_find_duplicates) + self.song_tools_menu.addAction(self.tools_report_song_list) + + self.song_tools_menu.menuAction().setVisible(False) + + @staticmethod + def on_tools_report_song_list_triggered(): + reporting.report_song_list() def on_tools_reindex_item_triggered(self): """ @@ -327,13 +346,13 @@ class SongsPlugin(Plugin): self.manager.finalise() self.song_import_item.setVisible(False) self.song_export_item.setVisible(False) - self.tools_reindex_item.setVisible(False) - self.tools_find_duplicates.setVisible(False) action_list = ActionList.get_instance() action_list.remove_action(self.song_import_item, UiStrings().Import) action_list.remove_action(self.song_export_item, UiStrings().Export) action_list.remove_action(self.tools_reindex_item, UiStrings().Tools) action_list.remove_action(self.tools_find_duplicates, UiStrings().Tools) + action_list.add_action(self.tools_report_song_list, UiStrings().Tools) + self.song_tools_menu.menuAction().setVisible(False) super(SongsPlugin, self).finalise() def new_service_created(self): diff --git a/openlp/plugins/songusage/forms/songusagedeletedialog.py b/openlp/plugins/songusage/forms/songusagedeletedialog.py index 40fabe3f7..6f3f72651 100644 --- a/openlp/plugins/songusage/forms/songusagedeletedialog.py +++ b/openlp/plugins/songusage/forms/songusagedeletedialog.py @@ -38,7 +38,7 @@ class Ui_SongUsageDeleteDialog(object): :param song_usage_delete_dialog: """ song_usage_delete_dialog.setObjectName('song_usage_delete_dialog') - song_usage_delete_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg')) + song_usage_delete_dialog.setWindowIcon(build_icon(':/icon/openlp-logo.svg')) song_usage_delete_dialog.resize(291, 243) self.vertical_layout = QtWidgets.QVBoxLayout(song_usage_delete_dialog) self.vertical_layout.setSpacing(8) diff --git a/openlp/plugins/songusage/forms/songusagedetaildialog.py b/openlp/plugins/songusage/forms/songusagedetaildialog.py index b665e74c2..e9e4a6bbe 100644 --- a/openlp/plugins/songusage/forms/songusagedetaildialog.py +++ b/openlp/plugins/songusage/forms/songusagedetaildialog.py @@ -38,7 +38,7 @@ class Ui_SongUsageDetailDialog(object): :param song_usage_detail_dialog: """ song_usage_detail_dialog.setObjectName('song_usage_detail_dialog') - song_usage_detail_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg')) + song_usage_detail_dialog.setWindowIcon(build_icon(':/icon/openlp-logo.svg')) song_usage_detail_dialog.resize(609, 413) self.vertical_layout = QtWidgets.QVBoxLayout(song_usage_detail_dialog) self.vertical_layout.setSpacing(8) diff --git a/resources/images/about-new.bmp b/resources/images/about-new.bmp deleted file mode 100644 index b523f6b60..000000000 Binary files a/resources/images/about-new.bmp and /dev/null differ diff --git a/resources/images/bibles_search_clear.png b/resources/images/bibles_search_clear.png index 551ce7c96..19a1665ba 100644 Binary files a/resources/images/bibles_search_clear.png and b/resources/images/bibles_search_clear.png differ diff --git a/resources/images/bibles_search_combined.png b/resources/images/bibles_search_combined.png index 161b3fcd6..8fc16c886 100644 Binary files a/resources/images/bibles_search_combined.png and b/resources/images/bibles_search_combined.png differ diff --git a/resources/images/bibles_upgrade_alert.png b/resources/images/bibles_upgrade_alert.png deleted file mode 100644 index 331aa2687..000000000 Binary files a/resources/images/bibles_upgrade_alert.png and /dev/null differ diff --git a/resources/images/custom_delete.png b/resources/images/custom_delete.png deleted file mode 100644 index ef8e685e2..000000000 Binary files a/resources/images/custom_delete.png and /dev/null differ diff --git a/resources/images/custom_edit.png b/resources/images/custom_edit.png deleted file mode 100644 index d79aa5151..000000000 Binary files a/resources/images/custom_edit.png and /dev/null differ diff --git a/resources/images/custom_new.png b/resources/images/custom_new.png deleted file mode 100644 index 8431237bd..000000000 Binary files a/resources/images/custom_new.png and /dev/null differ diff --git a/resources/images/general_search_clear.png b/resources/images/general_search_clear.png deleted file mode 100644 index 6c4b83b7a..000000000 Binary files a/resources/images/general_search_clear.png and /dev/null differ diff --git a/resources/images/image_clapperboard.png b/resources/images/image_clapperboard.png deleted file mode 100644 index e46056914..000000000 Binary files a/resources/images/image_clapperboard.png and /dev/null differ diff --git a/resources/images/image_delete.png b/resources/images/image_delete.png deleted file mode 100644 index ef8e685e2..000000000 Binary files a/resources/images/image_delete.png and /dev/null differ diff --git a/resources/images/image_load.png b/resources/images/image_load.png deleted file mode 100644 index eb211e233..000000000 Binary files a/resources/images/image_load.png and /dev/null differ diff --git a/resources/images/import_load.png b/resources/images/import_load.png deleted file mode 100644 index eb211e233..000000000 Binary files a/resources/images/import_load.png and /dev/null differ diff --git a/resources/images/import_move_to_list.png b/resources/images/import_move_to_list.png deleted file mode 100644 index 5c0005856..000000000 Binary files a/resources/images/import_move_to_list.png and /dev/null differ diff --git a/resources/images/import_remove.png b/resources/images/import_remove.png deleted file mode 100644 index ef8e685e2..000000000 Binary files a/resources/images/import_remove.png and /dev/null differ diff --git a/resources/images/import_selectall.png b/resources/images/import_selectall.png deleted file mode 100644 index 0f0d9f152..000000000 Binary files a/resources/images/import_selectall.png and /dev/null differ diff --git a/resources/images/messagebox_critical.png b/resources/images/messagebox_critical.png deleted file mode 100644 index 3fb497a19..000000000 Binary files a/resources/images/messagebox_critical.png and /dev/null differ diff --git a/resources/images/messagebox_info.png b/resources/images/messagebox_info.png deleted file mode 100644 index 4e3a257f3..000000000 Binary files a/resources/images/messagebox_info.png and /dev/null differ diff --git a/resources/images/messagebox_warning.png b/resources/images/messagebox_warning.png deleted file mode 100644 index 331aa2687..000000000 Binary files a/resources/images/messagebox_warning.png and /dev/null differ diff --git a/resources/images/openlp-2.qrc b/resources/images/openlp-2.qrc index b45cc745d..ebff58a0a 100644 --- a/resources/images/openlp-2.qrc +++ b/resources/images/openlp-2.qrc @@ -23,8 +23,7 @@ topic_maintenance.png song_author_edit.png song_book_edit.png - song_delete.png - + image_group.png image_new_group.png @@ -33,7 +32,6 @@ bibles_search_combined.png bibles_search_text.png bibles_search_reference.png - bibles_upgrade_alert.png bibles_search_clear.png bibles_search_unlock.png bibles_search_lock.png @@ -71,7 +69,6 @@ general_back.png - slide_close.png slide_next.png slide_blank.png slide_desktop.png @@ -83,12 +80,6 @@ media_playback_next.png - openlp-logo-16x16.png - openlp-logo-32x32.png - openlp-logo-48x48.png - openlp-logo-64x64.png - openlp-logo-128x128.png - openlp-logo-256x256.png openlp-logo.svg @@ -97,24 +88,18 @@ openlp-about-logo.png - import_selectall.png - import_move_to_list.png - import_remove.png - import_load.png - + export_load.png openlp-osx-wizard.png - wizard_exportsong.bmp - wizard_importsong.bmp + wizard_song.bmp wizard_importbible.bmp wizard_firsttime.bmp wizard_createtheme.bmp wizard_duplicateremoval.bmp - wizard_createprojector.png - + service_collapse_all.png service_expand_all.png @@ -130,7 +115,6 @@ clear_shortcut.png system_about.png system_help_contents.png - system_online_help.png system_mediamanager.png system_volunteer.png system_servicemanager.png @@ -156,10 +140,7 @@ multimedia-player.png - messagebox_critical.png - messagebox_info.png - messagebox_warning.png - + network_server.png network_ssl.png @@ -171,10 +152,8 @@ tools_add.png - tools_alert.png - + - theme_delete.png theme_new.png theme_edit.png @@ -202,12 +181,10 @@ projector_power_on_tiled.png projector_show.png projector_show_tiled.png - projector_spacer.png projector_warmup.png - projector_view.png - + android_app_qr.png ios_app_qr.png - + \ No newline at end of file diff --git a/resources/images/openlp-about-logo.svg b/resources/images/openlp-about-logo.svg deleted file mode 100644 index d4f9c2dbe..000000000 --- a/resources/images/openlp-about-logo.svg +++ /dev/null @@ -1,323 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/resources/images/openlp-default-dualdisplay.svg b/resources/images/openlp-default-dualdisplay.svg deleted file mode 100644 index 40ebedeac..000000000 --- a/resources/images/openlp-default-dualdisplay.svg +++ /dev/null @@ -1,489 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/resources/images/openlp-logo-256x256.png b/resources/images/openlp-logo-256x256.png deleted file mode 100644 index 0fcf37a7c..000000000 Binary files a/resources/images/openlp-logo-256x256.png and /dev/null differ diff --git a/resources/images/openlp-logo-420x420.png b/resources/images/openlp-logo-420x420.png deleted file mode 100644 index 81b893b9a..000000000 Binary files a/resources/images/openlp-logo-420x420.png and /dev/null differ diff --git a/resources/images/openlp-logo.svg b/resources/images/openlp-logo.svg index c5e7985e1..d1ff61bec 100644 --- a/resources/images/openlp-logo.svg +++ b/resources/images/openlp-logo.svg @@ -1,6 +1,4 @@ - - + id="svg5740" + height="467.39178" + width="467.39178"> + style="stop-color:#ffffff;stop-opacity:0.25098041;" /> + style="stop-color:#ffffff;stop-opacity:0;" /> + style="stop-color:#cdcdff;stop-opacity:1;" /> + style="stop-color:#ebebff;stop-opacity:1;" /> + style="stop-color:#000d26;stop-opacity:1;" /> + style="stop-color:#507fda;stop-opacity:1;" /> - + color-interpolation-filters="sRGB" + id="filter6926"> + id="feGaussianBlur6928" + stdDeviation="3.5771872" /> - - - - - - - - - - - - - - - + xlink:href="#linearGradient3195" /> - + + y1="276.68851" + x1="470.25891" + gradientTransform="matrix(0.7247086,0,0,0.7843464,-109.42065,-2.1325924)" + gradientUnits="userSpaceOnUse" + id="linearGradient4057" + xlink:href="#linearGradient3208" /> - @@ -273,92 +88,56 @@ image/svg+xml + + id="layer1" /> + id="layer5" /> + id="layer3" /> + id="layer2" /> + id="layer6"> + transform="translate(9.8817328,9.8817328)" + id="g4018"> - + + id="path6317" + d="M 833.03006,395.26932 A 357.71872,357.71872 0 0 1 475.31134,752.98804 357.71872,357.71872 0 0 1 117.59262,395.26932 357.71872,357.71872 0 0 1 475.31134,37.550598 357.71872,357.71872 0 0 1 833.03006,395.26932 Z" + transform="matrix(0.6317287,0,0,0.6317287,-64.581662,-12.716988)" /> + id="path6327" /> + id="path3203" /> diff --git a/resources/images/openlp-splash-screen.svg b/resources/images/openlp-splash-screen.svg deleted file mode 100644 index b45f2015c..000000000 --- a/resources/images/openlp-splash-screen.svg +++ /dev/null @@ -1,296 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/resources/images/openlp.org.ico b/resources/images/openlp.org.ico deleted file mode 100644 index f11e76809..000000000 Binary files a/resources/images/openlp.org.ico and /dev/null differ diff --git a/resources/images/presentation_delete.png b/resources/images/presentation_delete.png deleted file mode 100644 index ef8e685e2..000000000 Binary files a/resources/images/presentation_delete.png and /dev/null differ diff --git a/resources/images/presentation_load.png b/resources/images/presentation_load.png deleted file mode 100644 index eb211e233..000000000 Binary files a/resources/images/presentation_load.png and /dev/null differ diff --git a/resources/images/projector_connectors.png b/resources/images/projector_connectors.png deleted file mode 100644 index 77a6a4dd7..000000000 Binary files a/resources/images/projector_connectors.png and /dev/null differ diff --git a/resources/images/projector_spacer.png b/resources/images/projector_spacer.png deleted file mode 100644 index ca5a54a04..000000000 Binary files a/resources/images/projector_spacer.png and /dev/null differ diff --git a/resources/images/projector_view.png b/resources/images/projector_view.png deleted file mode 100644 index e4d7e971b..000000000 Binary files a/resources/images/projector_view.png and /dev/null differ diff --git a/resources/images/service_delete.png b/resources/images/service_delete.png deleted file mode 100644 index ef8e685e2..000000000 Binary files a/resources/images/service_delete.png and /dev/null differ diff --git a/resources/images/service_edit.png b/resources/images/service_edit.png deleted file mode 100644 index 84e345d22..000000000 Binary files a/resources/images/service_edit.png and /dev/null differ diff --git a/resources/images/service_new.png b/resources/images/service_new.png deleted file mode 100644 index 8431237bd..000000000 Binary files a/resources/images/service_new.png and /dev/null differ diff --git a/resources/images/service_open.png b/resources/images/service_open.png deleted file mode 100644 index eb211e233..000000000 Binary files a/resources/images/service_open.png and /dev/null differ diff --git a/resources/images/service_save.png b/resources/images/service_save.png deleted file mode 100644 index 8072aea32..000000000 Binary files a/resources/images/service_save.png and /dev/null differ diff --git a/resources/images/slide_close.png b/resources/images/slide_close.png deleted file mode 100644 index 2c2f99eb4..000000000 Binary files a/resources/images/slide_close.png and /dev/null differ diff --git a/resources/images/song_delete.png b/resources/images/song_delete.png deleted file mode 100644 index ef8e685e2..000000000 Binary files a/resources/images/song_delete.png and /dev/null differ diff --git a/resources/images/song_edit.png b/resources/images/song_edit.png deleted file mode 100644 index 84e345d22..000000000 Binary files a/resources/images/song_edit.png and /dev/null differ diff --git a/resources/images/song_export.png b/resources/images/song_export.png deleted file mode 100644 index d26519b7f..000000000 Binary files a/resources/images/song_export.png and /dev/null differ diff --git a/resources/images/song_new.png b/resources/images/song_new.png deleted file mode 100644 index 8431237bd..000000000 Binary files a/resources/images/song_new.png and /dev/null differ diff --git a/resources/images/system_add.png b/resources/images/system_add.png deleted file mode 100644 index 7bb9b1be8..000000000 Binary files a/resources/images/system_add.png and /dev/null differ diff --git a/resources/images/system_live.png b/resources/images/system_live.png deleted file mode 100644 index cb2be2258..000000000 Binary files a/resources/images/system_live.png and /dev/null differ diff --git a/resources/images/system_online_help.png b/resources/images/system_online_help.png deleted file mode 100644 index 670c0716f..000000000 Binary files a/resources/images/system_online_help.png and /dev/null differ diff --git a/resources/images/system_preview.png b/resources/images/system_preview.png deleted file mode 100644 index d196792bb..000000000 Binary files a/resources/images/system_preview.png and /dev/null differ diff --git a/resources/images/theme_delete.png b/resources/images/theme_delete.png deleted file mode 100644 index ef8e685e2..000000000 Binary files a/resources/images/theme_delete.png and /dev/null differ diff --git a/resources/images/theme_export.png b/resources/images/theme_export.png deleted file mode 100644 index d26519b7f..000000000 Binary files a/resources/images/theme_export.png and /dev/null differ diff --git a/resources/images/theme_import.png b/resources/images/theme_import.png deleted file mode 100644 index 0ee789269..000000000 Binary files a/resources/images/theme_import.png and /dev/null differ diff --git a/resources/images/tools_alert.png b/resources/images/tools_alert.png deleted file mode 100644 index 331aa2687..000000000 Binary files a/resources/images/tools_alert.png and /dev/null differ diff --git a/resources/images/video_delete.png b/resources/images/video_delete.png deleted file mode 100644 index ef8e685e2..000000000 Binary files a/resources/images/video_delete.png and /dev/null differ diff --git a/resources/images/video_load.png b/resources/images/video_load.png deleted file mode 100644 index eb211e233..000000000 Binary files a/resources/images/video_load.png and /dev/null differ diff --git a/resources/images/wizard_createprojector.png b/resources/images/wizard_createprojector.png deleted file mode 100644 index d17a01e41..000000000 Binary files a/resources/images/wizard_createprojector.png and /dev/null differ diff --git a/resources/images/wizard_importsong.bmp b/resources/images/wizard_importsong.bmp deleted file mode 100644 index 948422dcc..000000000 Binary files a/resources/images/wizard_importsong.bmp and /dev/null differ diff --git a/resources/images/wizard_exportsong.bmp b/resources/images/wizard_song.bmp similarity index 100% rename from resources/images/wizard_exportsong.bmp rename to resources/images/wizard_song.bmp diff --git a/scripts/clean_up_resources.py b/scripts/clean_up_resources.py new file mode 100755 index 000000000..ce4cc8f6d --- /dev/null +++ b/scripts/clean_up_resources.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2016 OpenLP Developers # +# --------------------------------------------------------------------------- # +# This program is free software; you can redistribute it and/or modify it # +# under the terms of the GNU General Public License as published by the Free # +# Software Foundation; version 2 of the License. # +# # +# This program is distributed in the hope that it will be useful, but WITHOUT # +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # +# more details. # +# # +# You should have received a copy of the GNU General Public License along # +# with this program; if not, write to the Free Software Foundation, Inc., 59 # +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # +############################################################################### + +import fnmatch +import os + +from lxml import etree + +resource_path = os.path.join('..', 'resources', 'images') +resource_file_path = os.path.join(resource_path, 'openlp-2.qrc') +src_directory = os.path.join('..', 'openlp') + +RESOURCES_TO_IGNORE = [ + 'openlp.svg', + 'OpenLP.ico', + 'openlp-2.qrc', + 'openlp-logo-16x16.png', + 'openlp-logo-32x32.png', + 'openlp-logo-48x48.png', + 'openlp-logo-64x64.png', + 'openlp-logo-128x128.png', + 'README.txt' +] + +tree = etree.parse(resource_file_path) +root = tree.getroot() + +print('Looking for unused resources listed in openlp-2.qrc') +print('----------------------------------------------------------------') +resources = root.findall('.//file') +for current_dir, dirs, files in os.walk(src_directory): + for file_name in files: + if not fnmatch.fnmatch(file_name, '*.py'): + continue + file_path = os.path.join(current_dir, file_name) + with open(file_path) as source_file: + file_contents = source_file.read() + # Create a copy of the resources list so that we don't change the list while we're iterating through it! + for resource in list(resources): + if resource.text in file_contents: + resources.remove(resource) +if resources: + print('Unused resources listed in openlp-2.qrc:') + print(*(x.text for x in resources), sep='\n') + print('----------------------------------------------------------------') + remove = None + while remove != 'yes' and remove != 'no': + remove = input('Would you like to remove these files from openlp-2.qrc? (yes/no)') + if remove == 'yes': + for resource in resources: + parent = resource.find('..') + parent.remove(resource) + tree.write(resource_file_path, encoding='utf8') +else: + print('No unused resources listed in openlp-2.qrc') +print('----------------------------------------------------------------') + +print('\nLooking for resource files which are not lited in openlp-2.qrc') +print('----------------------------------------------------------------') +resources = [x.text for x in root.findall('.//file')] +removable_resources = [] +for resource_name in os.listdir(resource_path): + if resource_name not in RESOURCES_TO_IGNORE and resource_name not in resources: + removable_resources.append(resource_name) +if removable_resources: + print('Resource files not listed in openlp-2.qrc:') + print(*removable_resources, sep='\n') + print('----------------------------------------------------------------') + remove = None + while remove != 'yes' and remove != 'no': + remove = input('Would you like to delete these files from the resource folder? (yes/no)') + print('----------------------------------------------------------------') + if remove == 'yes': + for resource in removable_resources: + resource_file = os.path.join(resource_path, resource) + print('Removing {file}'.format(file=resource_file)) + os.remove(resource_file) +else: + print('All resource files are listed!') diff --git a/tests/functional/openlp_core_lib/test_lib.py b/tests/functional/openlp_core_lib/test_lib.py index 145be21f4..fdab52e93 100644 --- a/tests/functional/openlp_core_lib/test_lib.py +++ b/tests/functional/openlp_core_lib/test_lib.py @@ -189,16 +189,14 @@ class TestLib(TestCase): """ Test the build_icon() function with a QIcon instance """ - with patch('openlp.core.lib.QtGui') as MockedQtGui: - # GIVEN: A mocked QIcon - MockedQtGui.QIcon = MagicMock - mocked_icon = MockedQtGui.QIcon() + # GIVEN: An icon QIcon + icon = QtGui.QIcon() - # WHEN: We pass a QIcon instance in - result = build_icon(mocked_icon) + # WHEN: We pass a QIcon instance in + result = build_icon(icon) - # THEN: The result should be our mocked QIcon - self.assertIs(mocked_icon, result, 'The result should be the mocked QIcon') + # THEN: The result should be the same icon as we passed in + self.assertIs(icon, result, 'The result should be the same icon as we passed in') def test_build_icon_with_resource(self): """ @@ -223,6 +221,30 @@ class TestLib(TestCase): self.assertIsInstance(result, MagicMock, 'The result should be a MagicMock, because we mocked it out') def test_image_to_byte(self): + """ + Test the image_to_byte() function + """ + with patch('openlp.core.lib.QtCore') as MockedQtCore: + # GIVEN: A set of mocked-out Qt classes + mocked_byte_array = MagicMock() + MockedQtCore.QByteArray.return_value = mocked_byte_array + mocked_buffer = MagicMock() + MockedQtCore.QBuffer.return_value = mocked_buffer + MockedQtCore.QIODevice.WriteOnly = 'writeonly' + mocked_image = MagicMock() + + # WHEN: We convert an image to a byte array + result = image_to_byte(mocked_image, base_64=False) + + # THEN: We should receive the mocked_buffer + MockedQtCore.QByteArray.assert_called_with() + MockedQtCore.QBuffer.assert_called_with(mocked_byte_array) + mocked_buffer.open.assert_called_with('writeonly') + mocked_image.save.assert_called_with(mocked_buffer, "PNG") + self.assertFalse(mocked_byte_array.toBase64.called) + self.assertEqual(mocked_byte_array, result, 'The mocked out byte array should be returned') + + def test_image_to_byte_base_64(self): """ Test the image_to_byte() function """ @@ -666,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): """ @@ -683,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".') diff --git a/tests/functional/openlp_core_ui/test_servicemanager.py b/tests/functional/openlp_core_ui/test_servicemanager.py index 3e4af8c97..e0366c7d2 100644 --- a/tests/functional/openlp_core_ui/test_servicemanager.py +++ b/tests/functional/openlp_core_ui/test_servicemanager.py @@ -28,6 +28,7 @@ from unittest import TestCase import PyQt5 from openlp.core.common import Registry, ThemeLevel +from openlp.core.ui.lib.toolbar import OpenLPToolbar from openlp.core.lib import ServiceItem, ServiceItemType, ItemCapabilities from openlp.core.ui import ServiceManager @@ -544,8 +545,8 @@ class TestServiceManager(TestCase): self.assertEqual(service_manager.theme_menu.menuAction().setVisible.call_count, 1, 'Should have be called once') - @patch(u'openlp.core.ui.servicemanager.Settings') - @patch(u'PyQt5.QtCore.QTimer.singleShot') + @patch('openlp.core.ui.servicemanager.Settings') + @patch('PyQt5.QtCore.QTimer.singleShot') def test_single_click_preview_true(self, mocked_singleShot, MockedSettings): """ Test that when "Preview items when clicked in Service Manager" enabled the preview timer starts @@ -561,8 +562,8 @@ class TestServiceManager(TestCase): mocked_singleShot.assert_called_with(PyQt5.QtWidgets.QApplication.instance().doubleClickInterval(), service_manager.on_single_click_preview_timeout) - @patch(u'openlp.core.ui.servicemanager.Settings') - @patch(u'PyQt5.QtCore.QTimer.singleShot') + @patch('openlp.core.ui.servicemanager.Settings') + @patch('PyQt5.QtCore.QTimer.singleShot') def test_single_click_preview_false(self, mocked_singleShot, MockedSettings): """ Test that when "Preview items when clicked in Service Manager" disabled the preview timer doesn't start @@ -577,9 +578,9 @@ class TestServiceManager(TestCase): # THEN: timer should not be started self.assertEqual(mocked_singleShot.call_count, 0, 'Should not be called') - @patch(u'openlp.core.ui.servicemanager.Settings') - @patch(u'PyQt5.QtCore.QTimer.singleShot') - @patch(u'openlp.core.ui.servicemanager.ServiceManager.make_live') + @patch('openlp.core.ui.servicemanager.Settings') + @patch('PyQt5.QtCore.QTimer.singleShot') + @patch('openlp.core.ui.servicemanager.ServiceManager.make_live') def test_single_click_preview_double(self, mocked_make_live, mocked_singleShot, MockedSettings): """ Test that when a double click has registered the preview timer doesn't start @@ -596,7 +597,7 @@ class TestServiceManager(TestCase): mocked_make_live.assert_called_with() self.assertEqual(mocked_singleShot.call_count, 0, 'Should not be called') - @patch(u'openlp.core.ui.servicemanager.ServiceManager.make_preview') + @patch('openlp.core.ui.servicemanager.ServiceManager.make_preview') def test_single_click_timeout_single(self, mocked_make_preview): """ Test that when a single click has been registered, the item is sent to preview @@ -609,8 +610,8 @@ class TestServiceManager(TestCase): self.assertEqual(mocked_make_preview.call_count, 1, 'ServiceManager.make_preview() should have been called once') - @patch(u'openlp.core.ui.servicemanager.ServiceManager.make_preview') - @patch(u'openlp.core.ui.servicemanager.ServiceManager.make_live') + @patch('openlp.core.ui.servicemanager.ServiceManager.make_preview') + @patch('openlp.core.ui.servicemanager.ServiceManager.make_live') def test_single_click_timeout_double(self, mocked_make_live, mocked_make_preview): """ Test that when a double click has been registered, the item does not goes to preview @@ -623,9 +624,9 @@ class TestServiceManager(TestCase): # THEN: make_preview() should not have been called self.assertEqual(mocked_make_preview.call_count, 0, 'ServiceManager.make_preview() should not be called') - @patch(u'openlp.core.ui.servicemanager.shutil.copy') - @patch(u'openlp.core.ui.servicemanager.zipfile') - @patch(u'openlp.core.ui.servicemanager.ServiceManager.save_file_as') + @patch('openlp.core.ui.servicemanager.shutil.copy') + @patch('openlp.core.ui.servicemanager.zipfile') + @patch('openlp.core.ui.servicemanager.ServiceManager.save_file_as') def test_save_file_raises_permission_error(self, mocked_save_file_as, mocked_zipfile, mocked_shutil_copy): """ Test that when a PermissionError is raised when trying to save a file, it is handled correctly @@ -652,9 +653,9 @@ class TestServiceManager(TestCase): self.assertTrue(result) mocked_save_file_as.assert_called_with() - @patch(u'openlp.core.ui.servicemanager.shutil.copy') - @patch(u'openlp.core.ui.servicemanager.zipfile') - @patch(u'openlp.core.ui.servicemanager.ServiceManager.save_file_as') + @patch('openlp.core.ui.servicemanager.shutil.copy') + @patch('openlp.core.ui.servicemanager.zipfile') + @patch('openlp.core.ui.servicemanager.ServiceManager.save_file_as') def test_save_local_file_raises_permission_error(self, mocked_save_file_as, mocked_zipfile, mocked_shutil_copy): """ Test that when a PermissionError is raised when trying to save a local file, it is handled correctly @@ -679,3 +680,66 @@ class TestServiceManager(TestCase): # THEN: The "save_as" method is called to save the service self.assertTrue(result) mocked_save_file_as.assert_called_with() + + @patch('openlp.core.ui.servicemanager.ServiceManager.regenerate_service_items') + def test_theme_change_global(self, mocked_regenerate_service_items): + """ + Test that when a Toolbar theme combobox displays correctly when the theme is set to Global + """ + # GIVEN: A service manager, a service to display with a theme level in the renderer + mocked_renderer = MagicMock() + service_manager = ServiceManager(None) + Registry().register('renderer', mocked_renderer) + service_manager.toolbar = OpenLPToolbar(None) + service_manager.toolbar.add_toolbar_action('theme_combo_box', triggers=MagicMock()) + service_manager.toolbar.add_toolbar_action('theme_label', triggers=MagicMock()) + + # WHEN: The service manager has a Global theme + mocked_renderer.theme_level = ThemeLevel.Global + result = service_manager.theme_change() + + # THEN: The the theme toolbar should not be visible + self.assertFalse(service_manager.toolbar.actions['theme_combo_box'].isVisible(), + 'The visibility should be False') + + @patch('openlp.core.ui.servicemanager.ServiceManager.regenerate_service_items') + def test_theme_change_service(self, mocked_regenerate_service_items): + """ + Test that when a Toolbar theme combobox displays correctly when the theme is set to Theme + """ + # GIVEN: A service manager, a service to display with a theme level in the renderer + mocked_renderer = MagicMock() + service_manager = ServiceManager(None) + Registry().register('renderer', mocked_renderer) + service_manager.toolbar = OpenLPToolbar(None) + service_manager.toolbar.add_toolbar_action('theme_combo_box', triggers=MagicMock()) + service_manager.toolbar.add_toolbar_action('theme_label', triggers=MagicMock()) + + # WHEN: The service manager has a Service theme + mocked_renderer.theme_level = ThemeLevel.Service + result = service_manager.theme_change() + + # THEN: The the theme toolbar should be visible + self.assertTrue(service_manager.toolbar.actions['theme_combo_box'].isVisible(), + 'The visibility should be True') + + @patch('openlp.core.ui.servicemanager.ServiceManager.regenerate_service_items') + def test_theme_change_song(self, mocked_regenerate_service_items): + """ + Test that when a Toolbar theme combobox displays correctly when the theme is set to Song + """ + # GIVEN: A service manager, a service to display with a theme level in the renderer + mocked_renderer = MagicMock() + service_manager = ServiceManager(None) + Registry().register('renderer', mocked_renderer) + service_manager.toolbar = OpenLPToolbar(None) + service_manager.toolbar.add_toolbar_action('theme_combo_box', triggers=MagicMock()) + service_manager.toolbar.add_toolbar_action('theme_label', triggers=MagicMock()) + + # WHEN: The service manager has a Song theme + mocked_renderer.theme_level = ThemeLevel.Song + result = service_manager.theme_change() + + # THEN: The the theme toolbar should be visible + self.assertTrue(service_manager.toolbar.actions['theme_combo_box'].isVisible(), + 'The visibility should be True') diff --git a/tests/functional/openlp_core_ui/test_settingsform.py b/tests/functional/openlp_core_ui/test_settingsform.py index 051bc7af1..3f6f0aaad 100644 --- a/tests/functional/openlp_core_ui/test_settingsform.py +++ b/tests/functional/openlp_core_ui/test_settingsform.py @@ -48,7 +48,7 @@ class TestSettingsForm(TestCase): general_tab = MagicMock() general_tab.tab_title = 'mock' general_tab.tab_title_visible = 'Mock' - general_tab.icon_path = ':/icon/openlp-logo-16x16.png' + general_tab.icon_path = ':/icon/openlp-logo.svg' # WHEN: We insert the general tab with patch.object(settings_form.stacked_layout, 'addWidget') as mocked_add_widget, \ @@ -86,14 +86,14 @@ class TestSettingsForm(TestCase): general_tab = QtWidgets.QWidget(None) general_tab.tab_title = 'mock-general' general_tab.tab_title_visible = 'Mock General' - general_tab.icon_path = ':/icon/openlp-logo-16x16.png' + general_tab.icon_path = ':/icon/openlp-logo.svg' mocked_general_save = MagicMock() general_tab.save = mocked_general_save settings_form.insert_tab(general_tab, is_visible=True) themes_tab = QtWidgets.QWidget(None) themes_tab.tab_title = 'mock-themes' themes_tab.tab_title_visible = 'Mock Themes' - themes_tab.icon_path = ':/icon/openlp-logo-16x16.png' + themes_tab.icon_path = ':/icon/openlp-logo.svg' mocked_theme_save = MagicMock() themes_tab.save = mocked_theme_save settings_form.insert_tab(themes_tab, is_visible=False) @@ -114,7 +114,7 @@ class TestSettingsForm(TestCase): general_tab = QtWidgets.QWidget(None) general_tab.tab_title = 'mock' general_tab.tab_title_visible = 'Mock' - general_tab.icon_path = ':/icon/openlp-logo-16x16.png' + general_tab.icon_path = ':/icon/openlp-logo.svg' settings_form.insert_tab(general_tab, is_visible=True) with patch.object(settings_form.stacked_layout, 'count') as mocked_count: @@ -133,14 +133,14 @@ class TestSettingsForm(TestCase): general_tab = QtWidgets.QWidget(None) general_tab.tab_title = 'mock-general' general_tab.tab_title_visible = 'Mock General' - general_tab.icon_path = ':/icon/openlp-logo-16x16.png' + general_tab.icon_path = ':/icon/openlp-logo.svg' mocked_general_cancel = MagicMock() general_tab.cancel = mocked_general_cancel settings_form.insert_tab(general_tab, is_visible=True) themes_tab = QtWidgets.QWidget(None) themes_tab.tab_title = 'mock-themes' themes_tab.tab_title_visible = 'Mock Themes' - themes_tab.icon_path = ':/icon/openlp-logo-16x16.png' + themes_tab.icon_path = ':/icon/openlp-logo.svg' mocked_theme_cancel = MagicMock() themes_tab.cancel = mocked_theme_cancel settings_form.insert_tab(themes_tab, is_visible=False) diff --git a/tests/functional/openlp_core_ui/test_slidecontroller.py b/tests/functional/openlp_core_ui/test_slidecontroller.py index ef1ce5793..b93952097 100644 --- a/tests/functional/openlp_core_ui/test_slidecontroller.py +++ b/tests/functional/openlp_core_ui/test_slidecontroller.py @@ -208,29 +208,6 @@ class TestSlideController(TestCase): mocked_on_theme_display.assert_called_once_with(False) mocked_on_hide_display.assert_called_once_with(False) - def test_live_escape(self): - """ - Test that when the live_escape() method is called, the display is set to invisible and any media is stopped - """ - # GIVEN: A new SlideController instance and mocked out display and media_controller - mocked_display = MagicMock() - mocked_media_controller = MagicMock() - Registry.create() - Registry().register('media_controller', mocked_media_controller) - slide_controller = SlideController(None) - slide_controller.display = mocked_display - play_slides = MagicMock() - play_slides.isChecked.return_value = False - slide_controller.play_slides_loop = play_slides - slide_controller.play_slides_once = play_slides - - # WHEN: live_escape() is called - slide_controller.live_escape() - - # THEN: the display should be set to invisible and the media controller stopped - mocked_display.setVisible.assert_called_once_with(False) - mocked_media_controller.media_stop.assert_called_once_with(slide_controller) - def test_on_go_live_live_controller(self): """ Test that when the on_go_live() method is called the message is sent to the live controller and focus is @@ -697,7 +674,7 @@ class TestSlideController(TestCase): slide_controller.next_item = MagicMock() slide_controller.previous_service = MagicMock() slide_controller.next_service = MagicMock() - slide_controller.escape_item = MagicMock() + slide_controller.desktop_screen_enable = MagicMock() slide_controller.desktop_screen = MagicMock() slide_controller.blank_screen = MagicMock() slide_controller.theme_screen = MagicMock() @@ -709,7 +686,7 @@ class TestSlideController(TestCase): mocked_widget.addActions.assert_called_with([ slide_controller.previous_item, slide_controller.next_item, slide_controller.previous_service, slide_controller.next_service, - slide_controller.escape_item, slide_controller.desktop_screen, + slide_controller.desktop_screen_enable, slide_controller.desktop_screen, slide_controller.theme_screen, slide_controller.blank_screen ]) diff --git a/tests/functional/openlp_core_ui_lib/test_listwidgetwithdnd.py b/tests/functional/openlp_core_ui_lib/test_listwidgetwithdnd.py new file mode 100644 index 000000000..f6cc8b446 --- /dev/null +++ b/tests/functional/openlp_core_ui_lib/test_listwidgetwithdnd.py @@ -0,0 +1,104 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2016 OpenLP Developers # +# --------------------------------------------------------------------------- # +# This program is free software; you can redistribute it and/or modify it # +# under the terms of the GNU General Public License as published by the Free # +# Software Foundation; version 2 of the License. # +# # +# This program is distributed in the hope that it will be useful, but WITHOUT # +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # +# more details. # +# # +# You should have received a copy of the GNU General Public License along # +# with this program; if not, write to the Free Software Foundation, Inc., 59 # +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # +############################################################################### +""" +This module contains tests for the openlp.core.lib.listwidgetwithdnd module +""" +from unittest import TestCase + +from openlp.core.common.uistrings import UiStrings +from openlp.core.ui.lib.listwidgetwithdnd import ListWidgetWithDnD +from unittest.mock import MagicMock, patch + + +class TestListWidgetWithDnD(TestCase): + """ + Test the :class:`~openlp.core.lib.listwidgetwithdnd.ListWidgetWithDnD` class + """ + def test_clear(self): + """ + Test the clear method when called without any arguments. + """ + # GIVEN: An instance of ListWidgetWithDnD + widget = ListWidgetWithDnD() + + # WHEN: Calling clear with out any arguments + widget.clear() + + # THEN: The results text should be the standard 'no results' text. + self.assertEqual(widget.no_results_text, UiStrings().NoResults) + + def test_clear_search_while_typing(self): + """ + Test the clear method when called with the search_while_typing argument set to True + """ + # GIVEN: An instance of ListWidgetWithDnD + widget = ListWidgetWithDnD() + + # WHEN: Calling clear with search_while_typing set to True + widget.clear(search_while_typing=True) + + # THEN: The results text should be the 'short results' text. + self.assertEqual(widget.no_results_text, UiStrings().ShortResults) + + def test_paint_event(self): + """ + Test the paintEvent method when the list is not empty + """ + # GIVEN: An instance of ListWidgetWithDnD with a mocked out count methode which returns 1 + # (i.e the list has an item) + widget = ListWidgetWithDnD() + with patch('openlp.core.ui.lib.listwidgetwithdnd.QtWidgets.QListWidget.paintEvent') as mocked_paint_event, \ + patch.object(widget, 'count', return_value=1), \ + patch.object(widget, 'viewport') as mocked_viewport: + mocked_event = MagicMock() + + # WHEN: Calling paintEvent + widget.paintEvent(mocked_event) + + # THEN: The overridden paintEvnet should have been called + mocked_paint_event.assert_called_once_with(mocked_event) + self.assertFalse(mocked_viewport.called) + + def test_paint_event_no_items(self): + """ + Test the paintEvent method when the list is empty + """ + # GIVEN: An instance of ListWidgetWithDnD with a mocked out count methode which returns 0 + # (i.e the list is empty) + widget = ListWidgetWithDnD() + mocked_painter_instance = MagicMock() + mocked_qrect = MagicMock() + with patch('openlp.core.ui.lib.listwidgetwithdnd.QtWidgets.QListWidget.paintEvent') as mocked_paint_event, \ + patch.object(widget, 'count', return_value=0), \ + patch.object(widget, 'viewport'), \ + patch('openlp.core.ui.lib.listwidgetwithdnd.QtGui.QPainter', + return_value=mocked_painter_instance) as mocked_qpainter, \ + patch('openlp.core.ui.lib.listwidgetwithdnd.QtCore.QRect', return_value=mocked_qrect): + mocked_event = MagicMock() + + # WHEN: Calling paintEvent + widget.paintEvent(mocked_event) + + # THEN: The overridden paintEvnet should have been called, and some text should be drawn. + mocked_paint_event.assert_called_once_with(mocked_event) + mocked_qpainter.assert_called_once_with(widget.viewport()) + mocked_painter_instance.drawText.assert_called_once_with(mocked_qrect, 4100, 'No Search Results') diff --git a/tests/functional/openlp_plugins/bibles/test_manager.py b/tests/functional/openlp_plugins/bibles/test_manager.py new file mode 100644 index 000000000..d18d3cfc6 --- /dev/null +++ b/tests/functional/openlp_plugins/bibles/test_manager.py @@ -0,0 +1,69 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2016 OpenLP Developers # +# --------------------------------------------------------------------------- # +# This program is free software; you can redistribute it and/or modify it # +# under the terms of the GNU General Public License as published by the Free # +# Software Foundation; version 2 of the License. # +# # +# This program is distributed in the hope that it will be useful, but WITHOUT # +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # +# more details. # +# # +# You should have received a copy of the GNU General Public License along # +# with this program; if not, write to the Free Software Foundation, Inc., 59 # +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # +############################################################################### +""" +This module contains tests for the manager submodule of the Bibles plugin. +""" +from unittest import TestCase +from unittest.mock import MagicMock, patch + +from openlp.plugins.bibles.lib.manager import BibleManager + + +class TestManager(TestCase): + """ + Test the functions in the :mod:`manager` module. + """ + + def setUp(self): + app_location_patcher = patch('openlp.plugins.bibles.lib.manager.AppLocation') + self.addCleanup(app_location_patcher.stop) + app_location_patcher.start() + log_patcher = patch('openlp.plugins.bibles.lib.manager.log') + self.addCleanup(log_patcher.stop) + self.mocked_log = log_patcher.start() + settings_patcher = patch('openlp.plugins.bibles.lib.manager.Settings') + self.addCleanup(settings_patcher.stop) + settings_patcher.start() + + def test_delete_bible(self): + """ + Test the BibleManager delete_bible method + """ + # GIVEN: An instance of BibleManager and a mocked bible + with patch.object(BibleManager, 'reload_bibles'), \ + patch('openlp.plugins.bibles.lib.manager.os.path.join', side_effect=lambda x, y: '{}/{}'.format(x, y)),\ + patch('openlp.plugins.bibles.lib.manager.delete_file', return_value=True) as mocked_delete_file: + instance = BibleManager(MagicMock()) + # We need to keep a reference to the mock for close_all as it gets set to None later on! + mocked_close_all = MagicMock() + mocked_bible = MagicMock(file='KJV.sqlite', path='bibles', **{'session.close_all': mocked_close_all}) + instance.db_cache = {'KJV': mocked_bible} + + # WHEN: Calling delete_bible with 'KJV' + result = instance.delete_bible('KJV') + + # THEN: The session should have been closed and set to None, the bible should be deleted, and the result of + # the deletion returned. + self.assertTrue(result) + mocked_close_all.assert_called_once_with() + self.assertIsNone(mocked_bible.session) + mocked_delete_file.assert_called_once_with('bibles/KJV.sqlite') diff --git a/tests/functional/openlp_plugins/bibles/test_mediaitem.py b/tests/functional/openlp_plugins/bibles/test_mediaitem.py index 05418f177..d4a6eee39 100644 --- a/tests/functional/openlp_plugins/bibles/test_mediaitem.py +++ b/tests/functional/openlp_plugins/bibles/test_mediaitem.py @@ -114,7 +114,28 @@ class TestMediaItem(TestCase, TestMixin): self.assertEqual(self.media_item.search_results, {}) self.assertEqual(self.media_item.second_search_results, {}) - def on_quick_search_button_general_test(self): + def test_required_icons(self): + """ + Test that all the required icons are set properly. + """ + # GIVEN: Mocked icons that need to be called. + self.media_item.has_import_icon = MagicMock() + self.media_item.has_new_icon = MagicMock() + self.media_item.has_edit_icon = MagicMock() + self.media_item.has_delete_icon = MagicMock() + self.media_item.add_to_service_item = MagicMock() + + # WHEN: self.media_item.required_icons is called + self.media_item.required_icons() + + # THEN: On windows it should return True, on other platforms False + self.assertTrue(self.media_item.has_import_icon, 'Check that the icon is as True.') + self.assertFalse(self.media_item.has_new_icon, 'Check that the icon is called as False.') + self.assertTrue(self.media_item.has_edit_icon, 'Check that the icon is called as True.') + self.assertTrue(self.media_item.has_delete_icon, 'Check that the icon is called as True.') + self.assertFalse(self.media_item.add_to_service_item, 'Check that the icon is called as False') + + def test_on_quick_search_button_general(self): """ Test that general things, which should be called on all Quick searches are called. """ @@ -134,7 +155,6 @@ class TestMediaItem(TestCase, TestMixin): self.media_item.list_view = MagicMock() self.media_item.search_results = MagicMock() self.media_item.display_results = MagicMock() - self.media_item.check_search_result = MagicMock() self.app.set_normal_cursor = MagicMock() # WHEN: on_quick_search_button is called @@ -148,5 +168,59 @@ class TestMediaItem(TestCase, TestMixin): self.assertEqual(1, self.media_item.quickLockButton.isChecked.call_count, 'Lock Should had been called once') self.assertEqual(1, self.media_item.display_results.call_count, 'Display results Should had been called once') self.assertEqual(2, self.media_item.quickSearchButton.setEnabled.call_count, 'Disable and Enable the button') - self.assertEqual(1, self.media_item.check_search_result.call_count, 'Check results Should had been called once') self.assertEqual(1, self.app.set_normal_cursor.call_count, 'Normal cursor should had been called once') + + def test_on_clear_button_clicked(self): + """ + Test that the on_clear_button_clicked works properly. (Used by Bible search tab) + """ + # GIVEN: Mocked list_view, check_search_results & quick_search_edit. + self.media_item.list_view = MagicMock() + self.media_item.quick_search_edit = MagicMock() + + # WHEN: on_clear_button_clicked is called + self.media_item.on_clear_button_clicked() + + # THEN: Search result should be reset and search field should receive focus. + self.media_item.list_view.clear.assert_called_once_with(), + self.media_item.quick_search_edit.clear.assert_called_once_with(), + self.media_item.quick_search_edit.setFocus.assert_called_once_with() + + def test_on_lock_button_toggled_search_tab_lock_icon(self): + """ + Test that "on_lock_button_toggled" gives focus to the right field and toggles the lock properly. + """ + # GIVEN: Mocked sender & Search edit, quickTab returning value = True on isVisible. + self.media_item.sender = MagicMock() + self.media_item.quick_search_edit = MagicMock() + self.media_item.quickTab = MagicMock(**{'isVisible.return_value': True}) + + self.media_item.lock_icon = 'lock icon' + sender_instance_mock = MagicMock() + self.media_item.sender = MagicMock(return_value=sender_instance_mock) + + # WHEN: on_lock_button_toggled is called and checked returns = True. + self.media_item.on_lock_button_toggled(True) + + # THEN: on_quick_search_edit should receive focus and Lock icon should be set. + self.media_item.quick_search_edit.setFocus.assert_called_once_with() + sender_instance_mock.setIcon.assert_called_once_with('lock icon') + + def test_on_lock_button_toggled_unlock_icon(self): + """ + Test that lock button unlocks properly and lock toggles properly. + """ + # GIVEN: Mocked sender & Search edit, quickTab returning value = False on isVisible. + self.media_item.sender = MagicMock() + self.media_item.quick_search_edit = MagicMock() + self.media_item.quickTab = MagicMock() + self.media_item.quickTab.isVisible = MagicMock() + self.media_item.unlock_icon = 'unlock icon' + sender_instance_mock = MagicMock() + self.media_item.sender = MagicMock(return_value=sender_instance_mock) + + # WHEN: on_lock_button_toggled is called and checked returns = False. + self.media_item.on_lock_button_toggled(False) + + # THEN: Unlock icon should be set. + sender_instance_mock.setIcon.assert_called_once_with('unlock icon') diff --git a/tests/functional/openlp_plugins/bibles/test_swordimport.py b/tests/functional/openlp_plugins/bibles/test_swordimport.py index 261df1c0e..14480bdd1 100644 --- a/tests/functional/openlp_plugins/bibles/test_swordimport.py +++ b/tests/functional/openlp_plugins/bibles/test_swordimport.py @@ -70,8 +70,7 @@ class TestSwordImport(TestCase): @patch('openlp.plugins.bibles.lib.importers.sword.SwordBible.application') @patch('openlp.plugins.bibles.lib.importers.sword.modules') - @patch('openlp.core.common.languages') - def test_simple_import(self, mocked_languages, mocked_pysword_modules, mocked_application): + def test_simple_import(self, mocked_pysword_modules, mocked_application): """ Test that a simple SWORD import works """ @@ -88,7 +87,7 @@ class TestSwordImport(TestCase): importer.create_verse = MagicMock() importer.create_book = MagicMock() importer.session = MagicMock() - mocked_languages.get_language.return_value = 'Danish' + importer.get_language = MagicMock(return_value='Danish') mocked_bible = MagicMock() mocked_genesis = MagicMock() mocked_genesis.name = 'Genesis' diff --git a/tests/functional/openlp_plugins/songs/test_mediashout.py b/tests/functional/openlp_plugins/songs/test_mediashout.py index 4f03c4f94..c08b04df7 100644 --- a/tests/functional/openlp_plugins/songs/test_mediashout.py +++ b/tests/functional/openlp_plugins/songs/test_mediashout.py @@ -22,15 +22,20 @@ """ Test the MediaShout importer """ -from unittest import TestCase +from unittest import TestCase, skipUnless from collections import namedtuple from openlp.core.common import Registry -from openlp.plugins.songs.lib.importers.mediashout import MediaShoutImport +try: + from openlp.plugins.songs.lib.importers.mediashout import MediaShoutImport + CAN_RUN_TESTS = True +except ImportError: + CAN_RUN_TESTS = False from tests.functional import MagicMock, patch, call +@skipUnless(CAN_RUN_TESTS, 'Not Windows, skipping test') class TestMediaShoutImport(TestCase): """ Test the MediaShout importer diff --git a/tests/interfaces/openlp_core_lib/test_searchedit.py b/tests/interfaces/openlp_core_lib/test_searchedit.py index a4c4472f5..464c6217d 100644 --- a/tests/interfaces/openlp_core_lib/test_searchedit.py +++ b/tests/interfaces/openlp_core_lib/test_searchedit.py @@ -23,6 +23,7 @@ Module to test the EditCustomForm. """ from unittest import TestCase +from unittest.mock import MagicMock, call, patch from PyQt5 import QtCore, QtGui, QtTest, QtWidgets @@ -58,7 +59,12 @@ class TestSearchEdit(TestCase, TestMixin): self.main_window = QtWidgets.QMainWindow() Registry().register('main_window', self.main_window) - self.search_edit = SearchEdit(self.main_window) + settings_patcher = patch( + 'openlp.core.lib.searchedit.Settings', return_value=MagicMock(**{'value.return_value': SearchTypes.First})) + self.addCleanup(settings_patcher.stop) + self.mocked_settings = settings_patcher.start() + + self.search_edit = SearchEdit(self.main_window, 'settings_section') # To complete set up we have to set the search types. self.search_edit.set_search_types(SEARCH_TYPES) @@ -78,8 +84,11 @@ class TestSearchEdit(TestCase, TestMixin): # WHEN: - # THEN: The first search type should be the first one in the list. - assert self.search_edit.current_search_type() == SearchTypes.First, "The first search type should be selected." + # THEN: The first search type should be the first one in the list. The selected type should be saved in the + # settings + self.assertEqual(self.search_edit.current_search_type(), SearchTypes.First, + "The first search type should be selected.") + self.mocked_settings().setValue.assert_called_once_with('settings_section/last search type', 0) def test_set_current_search_type(self): """ @@ -90,11 +99,13 @@ class TestSearchEdit(TestCase, TestMixin): result = self.search_edit.set_current_search_type(SearchTypes.Second) # THEN: - assert result, "The call should return success (True)." - assert self.search_edit.current_search_type() == SearchTypes.Second,\ - "The search type should be SearchTypes.Second" - assert self.search_edit.placeholderText() == SECOND_PLACEHOLDER_TEXT,\ - "The correct placeholder text should be 'Second Placeholder Text'." + self.assertTrue(result, "The call should return success (True).") + self.assertEqual(self.search_edit.current_search_type(), SearchTypes.Second, + "The search type should be SearchTypes.Second") + self.assertEqual(self.search_edit.placeholderText(), SECOND_PLACEHOLDER_TEXT, + "The correct placeholder text should be 'Second Placeholder Text'.") + self.mocked_settings().setValue.assert_has_calls( + [call('settings_section/last search type', 0), call('settings_section/last search type', 1)]) def test_clear_button_visibility(self): """ diff --git a/tests/interfaces/openlp_plugins/bibles/test_lib_http.py b/tests/interfaces/openlp_plugins/bibles/test_lib_http.py index 084bfa476..7068898fe 100644 --- a/tests/interfaces/openlp_plugins/bibles/test_lib_http.py +++ b/tests/interfaces/openlp_plugins/bibles/test_lib_http.py @@ -163,3 +163,19 @@ class TestBibleHTTP(TestCase): # THEN: The list should not be None, and some known bibles should be there self.assertIsNotNone(bibles) self.assertIn(('Giovanni Diodati 1649 (Italian)', 'gdb', 'it'), bibles) + + def test_crosswalk_get_verse_text(self): + """ + Test verse text from Crosswalk.com + """ + # GIVEN: A new Crosswalk extraction class + handler = CWExtract() + + # WHEN: downloading NIV Genesis from Crosswalk + niv_genesis_chapter_one = handler.get_bible_chapter('niv', 'Genesis', 1) + + # THEN: The verse list should contain the verses + self.assertTrue(niv_genesis_chapter_one.has_verse_list()) + self.assertEquals('In the beginning God created the heavens and the earth.', + niv_genesis_chapter_one.verse_list[1], + 'The first chapter of genesis should have been fetched.')