diff --git a/.bzrignore b/.bzrignore index f149d97a7..6377150e0 100644 --- a/.bzrignore +++ b/.bzrignore @@ -2,9 +2,12 @@ *.*~ \#*\# *.eric4project +*.eric5project *.ropeproject *.e4* .eric4project +.komodotools +*.komodoproject list openlp.org 2.0.e4* documentation/build/html diff --git a/openlp/__init__.py b/openlp/__init__.py index ac5f063d4..98e847979 100644 --- a/openlp/__init__.py +++ b/openlp/__init__.py @@ -30,7 +30,6 @@ The :mod:`openlp` module contains all the project produced OpenLP functionality """ -import openlp.core -import openlp.plugins +from openlp import core, plugins __all__ = ['core', 'plugins'] diff --git a/openlp/core/common/__init__.py b/openlp/core/common/__init__.py index 8a8e6eef3..22207dec4 100644 --- a/openlp/core/common/__init__.py +++ b/openlp/core/common/__init__.py @@ -38,7 +38,7 @@ import traceback from PyQt4 import QtCore -log = logging.getLogger(__name__+'.__init__') +log = logging.getLogger(__name__ + '.__init__') FIRST_CAMEL_REGEX = re.compile('(.)([A-Z][a-z]+)') @@ -76,6 +76,9 @@ def check_directory_exists(directory, do_not_log=False): def get_frozen_path(frozen_option, non_frozen_option): """ Return a path based on the system status. + + :param frozen_option: + :param non_frozen_option: """ if hasattr(sys, 'frozen') and sys.frozen == 1: return frozen_option diff --git a/openlp/core/lib/__init__.py b/openlp/core/lib/__init__.py index e6b7f4fe3..9561baff4 100644 --- a/openlp/core/lib/__init__.py +++ b/openlp/core/lib/__init__.py @@ -39,7 +39,7 @@ from PyQt4 import QtCore, QtGui, Qt from openlp.core.common import translate -log = logging.getLogger(__name__+'.__init__') +log = logging.getLogger(__name__ + '.__init__') class ServiceItemContext(object): diff --git a/openlp/core/lib/db.py b/openlp/core/lib/db.py index 1014c994d..d67c05c42 100644 --- a/openlp/core/lib/db.py +++ b/openlp/core/lib/db.py @@ -194,6 +194,7 @@ class Manager(object): db_ver, up_ver = upgrade_db(self.db_url, upgrade_mod) except (SQLAlchemyError, DBAPIError): log.exception('Error loading database: %s', self.db_url) + return if db_ver > up_ver: critical_error_message_box( translate('OpenLP.Manager', 'Database Error'), @@ -215,7 +216,7 @@ class Manager(object): Save an object to the database :param object_instance: The object to save - :param commit: Commit the session with this object + :param commit: Commit the session with this object """ for try_count in range(3): try: diff --git a/openlp/core/lib/mediamanageritem.py b/openlp/core/lib/mediamanageritem.py index 13d4b0bbb..4d7676ad6 100644 --- a/openlp/core/lib/mediamanageritem.py +++ b/openlp/core/lib/mediamanageritem.py @@ -168,29 +168,29 @@ class MediaManagerItem(QtGui.QWidget, RegistryProperties): Create buttons for the media item toolbar """ toolbar_actions = [] - ## Import Button ## + # Import Button if self.has_import_icon: toolbar_actions.append(['Import', StringContent.Import, ':/general/general_import.png', self.on_import_click]) - ## Load Button ## + # Load Button if self.has_file_icon: toolbar_actions.append(['Load', StringContent.Load, ':/general/general_open.png', self.on_file_click]) - ## New Button ## + # New Button if self.has_new_icon: toolbar_actions.append(['New', StringContent.New, ':/general/general_new.png', self.on_new_click]) - ## Edit Button ## + # Edit Button if self.has_edit_icon: toolbar_actions.append(['Edit', StringContent.Edit, ':/general/general_edit.png', self.on_edit_click]) - ## Delete Button ## + # Delete Button if self.has_delete_icon: toolbar_actions.append(['Delete', StringContent.Delete, ':/general/general_delete.png', self.on_delete_click]) - ## Preview ## + # Preview toolbar_actions.append(['Preview', StringContent.Preview, ':/general/general_preview.png', self.on_preview_click]) - ## Live Button ## + # Live Button toolbar_actions.append(['Live', StringContent.Live, ':/general/general_live.png', self.on_live_click]) - ## Add to service Button ## + # Add to service Button toolbar_actions.append(['Service', StringContent.Service, ':/general/general_add.png', self.on_add_click]) for action in toolbar_actions: if action[0] == StringContent.Preview: diff --git a/openlp/core/lib/plugin.py b/openlp/core/lib/plugin.py index e14fe8bb0..1f459524c 100644 --- a/openlp/core/lib/plugin.py +++ b/openlp/core/lib/plugin.py @@ -101,7 +101,7 @@ class Plugin(QtCore.QObject, RegistryProperties): ``add_import_menu_item(import_menu)`` Add an item to the Import menu. - ``add_export_menu_Item(export_menu)`` + ``add_export_menu_item(export_menu)`` Add an item to the Export menu. ``create_settings_tab()`` @@ -226,7 +226,7 @@ class Plugin(QtCore.QObject, RegistryProperties): """ pass - def add_export_menu_Item(self, export_menu): + def add_export_menu_item(self, export_menu): """ Create a menu item and add it to the "Export" menu. @@ -329,22 +329,24 @@ class Plugin(QtCore.QObject, RegistryProperties): def set_plugin_ui_text_strings(self, tooltips): """ Called to define all translatable texts of the plugin + + :param tooltips: """ - ## Load Action ## + # Load Action self.__set_name_text_string(StringContent.Load, UiStrings().Load, tooltips['load']) - ## Import Action ## + # Import Action self.__set_name_text_string(StringContent.Import, UiStrings().Import, tooltips['import']) - ## New Action ## + # New Action self.__set_name_text_string(StringContent.New, UiStrings().Add, tooltips['new']) - ## Edit Action ## + # Edit Action self.__set_name_text_string(StringContent.Edit, UiStrings().Edit, tooltips['edit']) - ## Delete Action ## + # Delete Action self.__set_name_text_string(StringContent.Delete, UiStrings().Delete, tooltips['delete']) - ## Preview Action ## + # Preview Action self.__set_name_text_string(StringContent.Preview, UiStrings().Preview, tooltips['preview']) - ## Send Live Action ## + # Send Live Action self.__set_name_text_string(StringContent.Live, UiStrings().Live, tooltips['live']) - ## Add to Service Action ## + # Add to Service Action self.__set_name_text_string(StringContent.Service, UiStrings().Service, tooltips['service']) def __set_name_text_string(self, name, title, tooltip): diff --git a/openlp/core/lib/pluginmanager.py b/openlp/core/lib/pluginmanager.py index d24f07eac..474113c98 100644 --- a/openlp/core/lib/pluginmanager.py +++ b/openlp/core/lib/pluginmanager.py @@ -161,7 +161,7 @@ class PluginManager(RegistryMixin, OpenLPMixin, RegistryProperties): """ for plugin in self.plugins: if plugin.status is not PluginStatus.Disabled: - plugin.add_export_menu_Item(self.main_window.file_export_menu) + plugin.add_export_menu_item(self.main_window.file_export_menu) def hook_tools_menu(self): """ diff --git a/openlp/core/lib/serviceitem.py b/openlp/core/lib/serviceitem.py index 6d07e1c34..17d11ef63 100644 --- a/openlp/core/lib/serviceitem.py +++ b/openlp/core/lib/serviceitem.py @@ -108,6 +108,9 @@ class ItemCapabilities(object): ``CanAutoStartForLive`` The capability to ignore the do not play if display blank flag. + ``CanEditTitle`` + The capability to edit the title of the item + """ CanPreview = 1 CanEdit = 2 @@ -125,6 +128,7 @@ class ItemCapabilities(object): CanWordSplit = 14 HasBackgroundAudio = 15 CanAutoStartForLive = 16 + CanEditTitle = 17 class ServiceItem(RegistryProperties): @@ -383,7 +387,7 @@ class ServiceItem(RegistryProperties): self.will_auto_start = header.get('will_auto_start', False) self.processor = header.get('processor', None) self.has_original_files = True - #TODO Remove me in 2,3 build phase + # TODO: Remove me in 2,3 build phase if self.is_capable(ItemCapabilities.HasDetailedTitleDisplay): self.capabilities.remove(ItemCapabilities.HasDetailedTitleDisplay) self.processor = self.title @@ -423,7 +427,7 @@ class ServiceItem(RegistryProperties): """ Returns the title of the service item. """ - if self.is_text(): + if self.is_text() or ItemCapabilities.CanEditTitle in self.capabilities: return self.title else: if len(self._raw_frames) > 1: diff --git a/openlp/core/lib/ui.py b/openlp/core/lib/ui.py index 3126d1a56..965adb053 100644 --- a/openlp/core/lib/ui.py +++ b/openlp/core/lib/ui.py @@ -173,7 +173,7 @@ def create_button(parent, name, **kwargs): kwargs.setdefault('tooltip', translate('OpenLP.Ui', 'Move selection down one position.')) else: log.warn('The role "%s" is not defined in create_push_button().', role) - if kwargs.pop('class', '') == 'toolbutton': + if kwargs.pop('btn_class', '') == 'toolbutton': button = QtGui.QToolButton(parent) else: button = QtGui.QPushButton(parent) diff --git a/openlp/core/ui/advancedtab.py b/openlp/core/ui/advancedtab.py index b2c8cd14b..a5ce09bdf 100644 --- a/openlp/core/ui/advancedtab.py +++ b/openlp/core/ui/advancedtab.py @@ -511,7 +511,7 @@ class AdvancedTab(SettingsTab): """ Select an image for the default display screen. """ - file_filters = '%s;;%s (*.*) (*)' % (get_images_filter(), UiStrings().AllFiles) + file_filters = '%s;;%s (*.*)' % (get_images_filter(), UiStrings().AllFiles) filename = QtGui.QFileDialog.getOpenFileName(self, translate('OpenLP.AdvancedTab', 'Open File'), '', file_filters) if filename: diff --git a/openlp/core/ui/exceptiondialog.py b/openlp/core/ui/exceptiondialog.py index 4d7abe708..212fee4cd 100644 --- a/openlp/core/ui/exceptiondialog.py +++ b/openlp/core/ui/exceptiondialog.py @@ -95,15 +95,15 @@ class Ui_ExceptionDialog(object): Translate the widgets on the fly. """ exception_dialog.setWindowTitle(translate('OpenLP.ExceptionDialog', 'Error Occurred')) - self.description_explanation.setText(translate('OpenLP.ExceptionDialog', - 'Please enter a description of what you were doing to cause this error ' - '\n(Minimum 20 characters)')) - self.message_label.setText(translate('OpenLP.ExceptionDialog', 'Oops! ' - 'OpenLP hit a problem, and couldn\'t recover. The text in the box ' - 'below contains information that might be helpful to the OpenLP ' - 'developers, so please e-mail it to bugs@openlp.org, along with a ' - 'detailed description of what you were doing when the problem ' - 'occurred.')) + self.description_explanation.setText( + translate('OpenLP.ExceptionDialog', 'Please enter a description of what you were doing to cause this error ' + '\n(Minimum 20 characters)')) + self.message_label.setText( + translate('OpenLP.ExceptionDialog', 'Oops! OpenLP hit a problem, and couldn\'t recover. The text in the ' + 'box below contains information that might be helpful to the OpenLP ' + 'developers, so please e-mail it to bugs@openlp.org, along with a ' + 'detailed description of what you were doing when the problem ' + 'occurred.')) self.send_report_button.setText(translate('OpenLP.ExceptionDialog', 'Send E-Mail')) self.save_report_button.setText(translate('OpenLP.ExceptionDialog', 'Save to File')) self.attach_tile_button.setText(translate('OpenLP.ExceptionDialog', 'Attach File')) diff --git a/openlp/core/ui/exceptionform.py b/openlp/core/ui/exceptionform.py index ae3bc9db0..e0228a43b 100644 --- a/openlp/core/ui/exceptionform.py +++ b/openlp/core/ui/exceptionform.py @@ -228,7 +228,7 @@ class ExceptionForm(QtGui.QDialog, Ui_ExceptionDialog, RegistryProperties): """ files = QtGui.QFileDialog.getOpenFileName(self, translate('ImagePlugin.ExceptionDialog', 'Select Attachment'), Settings().value(self.settings_section + '/last directory'), - '%s (*.*) (*)' % UiStrings().AllFiles) + '%s (*)' % UiStrings().AllFiles) log.info('New files(s) %s', str(files)) if files: self.file_attachment = str(files) diff --git a/openlp/core/ui/firsttimeform.py b/openlp/core/ui/firsttimeform.py index aa89da6c0..b9b5f5997 100644 --- a/openlp/core/ui/firsttimeform.py +++ b/openlp/core/ui/firsttimeform.py @@ -67,7 +67,7 @@ class ThemeScreenshotThread(QtCore.QThread): title = config.get('theme_%s' % theme, 'title') filename = config.get('theme_%s' % theme, 'filename') screenshot = config.get('theme_%s' % theme, 'screenshot') - urllib.request.urlretrieve('%s%s' % (self.parent().web, screenshot), + urllib.request.urlretrieve('%s%s' % (self.parent().themes_url, screenshot), os.path.join(gettempdir(), 'openlp', screenshot)) item = QtGui.QListWidgetItem(title, self.parent().themes_list_widget) item.setData(QtCore.Qt.UserRole, filename) @@ -96,6 +96,10 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard, RegistryProperties): if self.web_access: files = self.web_access.read() self.config.read_string(files.decode()) + self.web = self.config.get('general', 'base url') + self.songs_url = self.web + self.config.get('songs', 'directory') + '/' + self.bibles_url = self.web + self.config.get('bibles', 'directory') + '/' + self.themes_url = self.web + self.config.get('themes', 'directory') + '/' self.update_screen_list_combo() self.was_download_cancelled = False self.theme_screenshot_thread = None @@ -110,10 +114,10 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard, RegistryProperties): """ Run the wizard. """ - self.setDefaults() + self.set_defaults() return QtGui.QWizard.exec_(self) - def setDefaults(self): + def set_defaults(self): """ Set up display at start of theme edit. """ @@ -341,7 +345,7 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard, RegistryProperties): item = self.songs_list_widget.item(i) if item.checkState() == QtCore.Qt.Checked: filename = item.data(QtCore.Qt.UserRole) - size = self._get_file_size('%s%s' % (self.web, filename)) + size = self._get_file_size('%s%s' % (self.songs_url, filename)) self.max_progress += size # Loop through the Bibles list and increase for each selected item iterator = QtGui.QTreeWidgetItemIterator(self.bibles_tree_widget) @@ -350,7 +354,7 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard, RegistryProperties): item = iterator.value() if item.parent() and item.checkState(0) == QtCore.Qt.Checked: filename = item.data(0, QtCore.Qt.UserRole) - size = self._get_file_size('%s%s' % (self.web, filename)) + size = self._get_file_size('%s%s' % (self.bibles_url, filename)) self.max_progress += size iterator += 1 # Loop through the themes list and increase for each selected item @@ -359,7 +363,7 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard, RegistryProperties): item = self.themes_list_widget.item(i) if item.checkState() == QtCore.Qt.Checked: filename = item.data(QtCore.Qt.UserRole) - size = self._get_file_size('%s%s' % (self.web, filename)) + size = self._get_file_size('%s%s' % (self.themes_url, filename)) self.max_progress += size if self.max_progress: # Add on 2 for plugins status setting plus a "finished" point. @@ -435,7 +439,7 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard, RegistryProperties): self._increment_progress_bar(self.downloading % filename, 0) self.previous_size = 0 destination = os.path.join(songs_destination, str(filename)) - self.url_get_file('%s%s' % (self.web, filename), destination) + self.url_get_file('%s%s' % (self.songs_url, filename), destination) # Download Bibles bibles_iterator = QtGui.QTreeWidgetItemIterator( self.bibles_tree_widget) @@ -445,7 +449,7 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard, RegistryProperties): bible = item.data(0, QtCore.Qt.UserRole) self._increment_progress_bar(self.downloading % bible, 0) self.previous_size = 0 - self.url_get_file('%s%s' % (self.web, bible), os.path.join(bibles_destination, bible)) + self.url_get_file('%s%s' % (self.bibles_url, bible), os.path.join(bibles_destination, bible)) bibles_iterator += 1 # Download themes for i in range(self.themes_list_widget.count()): @@ -454,7 +458,7 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard, RegistryProperties): theme = item.data(QtCore.Qt.UserRole) self._increment_progress_bar(self.downloading % theme, 0) self.previous_size = 0 - self.url_get_file('%s%s' % (self.web, theme), os.path.join(themes_destination, theme)) + self.url_get_file('%s%s' % (self.themes_url, theme), os.path.join(themes_destination, theme)) # Set Default Display if self.display_combo_box.currentIndex() != -1: Settings().setValue('core/monitor', self.display_combo_box.currentIndex()) diff --git a/openlp/core/ui/firsttimewizard.py b/openlp/core/ui/firsttimewizard.py index 407842c48..1a270f931 100644 --- a/openlp/core/ui/firsttimewizard.py +++ b/openlp/core/ui/firsttimewizard.py @@ -211,9 +211,9 @@ class Ui_FirstTimeWizard(object): first_time_wizard.setWindowTitle(translate('OpenLP.FirstTimeWizard', 'First Time Wizard')) self.title_label.setText('%s' % translate('OpenLP.FirstTimeWizard', 'Welcome to the First Time Wizard')) - self.information_label.setText(translate('OpenLP.FirstTimeWizard', - 'This wizard will help you to configure OpenLP for initial use. ' - 'Click the next button below to start.')) + self.information_label.setText( + translate('OpenLP.FirstTimeWizard', 'This wizard will help you to configure OpenLP for initial use. ' + 'Click the next button below to start.')) self.plugin_page.setTitle(translate('OpenLP.FirstTimeWizard', 'Activate required Plugins')) self.plugin_page.setSubTitle(translate('OpenLP.FirstTimeWizard', 'Select the Plugins you wish to use. ')) self.songs_check_box.setText(translate('OpenLP.FirstTimeWizard', 'Songs')) diff --git a/openlp/core/ui/formattingtagform.py b/openlp/core/ui/formattingtagform.py index b7153429d..be4247bc1 100644 --- a/openlp/core/ui/formattingtagform.py +++ b/openlp/core/ui/formattingtagform.py @@ -63,7 +63,6 @@ class FormattingTagForm(QtGui.QDialog, Ui_FormattingTagDialog, FormattingTagCont self.services = FormattingTagController() self.tag_table_widget.itemSelectionChanged.connect(self.on_row_selected) self.new_button.clicked.connect(self.on_new_clicked) - #self.save_button.clicked.connect(self.on_saved_clicked) self.delete_button.clicked.connect(self.on_delete_clicked) self.tag_table_widget.currentCellChanged.connect(self.on_current_cell_changed) self.button_box.rejected.connect(self.close) @@ -202,5 +201,4 @@ class FormattingTagForm(QtGui.QDialog, Ui_FormattingTagDialog, FormattingTagCont if errors: QtGui.QMessageBox.warning(self, translate('OpenLP.FormattingTagForm', 'Validation Error'), errors, QtGui.QMessageBox.Ok) - #self.tag_table_widget.selectRow(pre_row - 1) self.tag_table_widget.resizeRowsToContents() diff --git a/openlp/core/ui/maindisplay.py b/openlp/core/ui/maindisplay.py index b9423046e..946299aca 100644 --- a/openlp/core/ui/maindisplay.py +++ b/openlp/core/ui/maindisplay.py @@ -168,8 +168,10 @@ class MainDisplay(OpenLPMixin, Display, RegistryProperties): """ if enabled: self.setAutoFillBackground(False) + self.setStyleSheet("QGraphicsView {background: transparent; border: 0px;}") else: self.setAttribute(QtCore.Qt.WA_NoSystemBackground, False) + self.setStyleSheet("QGraphicsView {}") self.setAttribute(QtCore.Qt.WA_TranslucentBackground, enabled) self.repaint() @@ -350,7 +352,6 @@ class MainDisplay(OpenLPMixin, Display, RegistryProperties): self.hide_display(self.hide_mode) # Only continue if the visibility wasn't changed during method call. elif was_visible == self.isVisible(): - # Single screen active if self.screens.display_count == 1: # Only make visible if setting enabled. diff --git a/openlp/core/ui/mainwindow.py b/openlp/core/ui/mainwindow.py index 42572484b..81e822c16 100644 --- a/openlp/core/ui/mainwindow.py +++ b/openlp/core/ui/mainwindow.py @@ -56,29 +56,27 @@ from openlp.core.ui.firsttimeform import FirstTimeForm log = logging.getLogger(__name__) MEDIA_MANAGER_STYLE = """ - QToolBox { +QToolBox { padding-bottom: 2px; - } - QToolBox::tab { +} +QToolBox::tab { background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, - stop: 0 palette(button), stop: 0.5 palette(button), - stop: 1.0 palette(mid)); - border: 1px groove palette(mid); - border-radius: 5px; - } - QToolBox::tab:selected { + stop: 0 palette(button), stop: 1.0 palette(mid)); + border: 1px solid palette(mid); + border-radius: 3px; +} +QToolBox::tab:selected { background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, - stop: 0 palette(light), stop: 0.5 palette(midlight), - stop: 1.0 palette(dark)); - border: 1px groove palette(dark); + stop: 0 palette(light), stop: 1.0 palette(button)); + border: 1px solid palette(mid); font-weight: bold; - } +} """ PROGRESSBAR_STYLE = """ - QProgressBar{ - height: 10px; - } +QProgressBar{ + height: 10px; +} """ @@ -369,7 +367,7 @@ class Ui_MainWindow(object): self.settings_menu.setTitle(translate('OpenLP.MainWindow', '&Settings')) self.settings_language_menu.setTitle(translate('OpenLP.MainWindow', '&Language')) self.help_menu.setTitle(translate('OpenLP.MainWindow', '&Help')) - self.media_manager_dock.setWindowTitle(translate('OpenLP.MainWindow', 'Media Manager')) + self.media_manager_dock.setWindowTitle(translate('OpenLP.MainWindow', 'Library')) self.service_manager_dock.setWindowTitle(translate('OpenLP.MainWindow', 'Service Manager')) self.theme_manager_dock.setWindowTitle(translate('OpenLP.MainWindow', 'Theme Manager')) self.file_new_item.setText(translate('OpenLP.MainWindow', '&New')) @@ -396,12 +394,12 @@ class Ui_MainWindow(object): self.settings_shortcuts_item.setText(translate('OpenLP.MainWindow', 'Configure &Shortcuts...')) self.formatting_tag_item.setText(translate('OpenLP.MainWindow', 'Configure &Formatting Tags...')) self.settings_configure_item.setText(translate('OpenLP.MainWindow', '&Configure OpenLP...')) - self.settings_export_item.setStatusTip(translate('OpenLP.MainWindow', - 'Export OpenLP settings to a specified *.config file')) + self.settings_export_item.setStatusTip( + translate('OpenLP.MainWindow', 'Export OpenLP settings to a specified *.config file')) self.settings_export_item.setText(translate('OpenLP.MainWindow', 'Settings')) - self.settings_import_item.setStatusTip(translate('OpenLP.MainWindow', - 'Import OpenLP settings from a specified *.config file previously ' - 'exported on this or another machine')) + self.settings_import_item.setStatusTip( + translate('OpenLP.MainWindow', 'Import OpenLP settings from a specified *.config file previously ' + 'exported on this or another machine')) self.settings_import_item.setText(translate('OpenLP.MainWindow', 'Settings')) self.view_media_manager_item.setText(translate('OpenLP.MainWindow', '&Media Manager')) self.view_media_manager_item.setToolTip(translate('OpenLP.MainWindow', 'Toggle Media Manager')) @@ -598,13 +596,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow, RegistryProperties): self.live_controller.display.setFocus() self.activateWindow() if self.arguments: - args = [] - for a in self.arguments: - args.extend([a]) - filename = args[0] - if not isinstance(filename, str): - filename = str(filename, sys.getfilesystemencoding()) - self.service_manager_contents.load_file(filename) + self.open_cmd_line_files() elif Settings().value(self.general_settings_section + '/auto open'): self.service_manager_contents.load_Last_file() self.timer_version_id = self.startTimer(1000) @@ -868,7 +860,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow, RegistryProperties): section = 'general' section_key = section + "/" + key # Make sure it's a valid section for us. - if not section in setting_sections: + if section not in setting_sections: continue # We have a good file, import it. for section_key in import_keys: @@ -1364,3 +1356,17 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow, RegistryProperties): if self.new_data_path == AppLocation.get_directory(AppLocation.DataDir): settings.remove('advanced/data path') self.application.set_normal_cursor() + + def open_cmd_line_files(self): + """ + Open files passed in through command line arguments + """ + args = [] + for a in self.arguments: + args.extend([a]) + for arg in args: + filename = arg + if not isinstance(filename, str): + filename = str(filename, sys.getfilesystemencoding()) + if filename.endswith(('.osz', '.oszl')): + self.service_manager_contents.load_file(filename) diff --git a/openlp/core/ui/media/__init__.py b/openlp/core/ui/media/__init__.py index 1276c8662..b2b6ab0b8 100644 --- a/openlp/core/ui/media/__init__.py +++ b/openlp/core/ui/media/__init__.py @@ -35,7 +35,7 @@ from openlp.core.common import Settings from PyQt4 import QtCore -log = logging.getLogger(__name__+'.__init__') +log = logging.getLogger(__name__ + '.__init__') class MediaState(object): @@ -90,7 +90,7 @@ def get_media_players(): overridden_player = 'auto' else: overridden_player = '' - saved_players_list = saved_players.replace('[', '').replace(']', '').split(',') + saved_players_list = saved_players.replace('[', '').replace(']', '').split(',') if saved_players else [] return saved_players_list, overridden_player diff --git a/openlp/core/ui/media/mediacontroller.py b/openlp/core/ui/media/mediacontroller.py index d846af0e4..596b618cb 100644 --- a/openlp/core/ui/media/mediacontroller.py +++ b/openlp/core/ui/media/mediacontroller.py @@ -137,7 +137,7 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties): for player in list(self.media_players.values()): if player.is_active: for item in player.audio_extensions_list: - if not item in self.audio_extensions_list: + if item not in self.audio_extensions_list: self.audio_extensions_list.append(item) suffix_list.append(item[2:]) self.video_extensions_list = [] @@ -184,8 +184,8 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties): return False saved_players, overridden_player = get_media_players() invalid_media_players = \ - [mediaPlayer for mediaPlayer in saved_players if not mediaPlayer in self.media_players or - not self.media_players[mediaPlayer].check_available()] + [media_player for media_player in saved_players if media_player not in self.media_players or + not self.media_players[media_player].check_available()] if invalid_media_players: for invalidPlayer in invalid_media_players: saved_players.remove(invalidPlayer) @@ -506,7 +506,8 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties): else: self.media_volume(controller, controller.media_info.volume) if status: - display.frame.evaluateJavaScript('show_blank("desktop");') + if not controller.media_info.is_background: + display.frame.evaluateJavaScript('show_blank("desktop");') self.current_media_players[controller.controller_type].set_visible(display, True) # Flash needs to be played and will not AutoPlay if controller.media_info.is_flash: @@ -517,7 +518,7 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties): controller.mediabar.actions['playbackPause'].setVisible(True) controller.mediabar.actions['playbackStop'].setVisible(True) if controller.is_live: - if controller.hide_menu.defaultAction().isChecked(): + if controller.hide_menu.defaultAction().isChecked() and not controller.media_info.is_background: controller.hide_menu.defaultAction().trigger() # Start Timer for ui updates if not self.timer.isActive(): diff --git a/openlp/core/ui/media/vendor/vlc.py b/openlp/core/ui/media/vendor/vlc.py index 6e17c002f..0326e4104 100644 --- a/openlp/core/ui/media/vendor/vlc.py +++ b/openlp/core/ui/media/vendor/vlc.py @@ -48,13 +48,13 @@ import sys from inspect import getargspec __version__ = "N/A" -build_date = "Mon Apr 1 23:47:38 2013" +build_date = "Tue Jul 2 10:35:53 2013" if sys.version_info[0] > 2: str = str - str = str + unicode = str bytes = bytes - str = (str, bytes) + basestring = (str, bytes) PYTHON3 = True def str_to_bytes(s): """Translate string or bytes to bytes. @@ -73,14 +73,14 @@ if sys.version_info[0] > 2: return b else: str = str - str = str + unicode = unicode bytes = str - str = str + basestring = basestring PYTHON3 = False def str_to_bytes(s): """Translate string or bytes to bytes. """ - if isinstance(s, str): + if isinstance(s, unicode): return s.encode(sys.getfilesystemencoding()) else: return s @@ -89,7 +89,7 @@ else: """Translate bytes to unicode string. """ if isinstance(b, str): - return str(b, sys.getfilesystemencoding()) + return unicode(b, sys.getfilesystemencoding()) else: return b @@ -110,7 +110,7 @@ def find_lib(): p = find_library('libvlc.dll') if p is None: try: # some registry settings - import winreg as w # leaner than win32api, win32con + import _winreg as w # leaner than win32api, win32con for r in w.HKEY_LOCAL_MACHINE, w.HKEY_CURRENT_USER: try: r = w.OpenKey(r, 'Software\\VideoLAN\\VLC') @@ -168,7 +168,7 @@ class VLCException(Exception): pass try: - _Ints = (int, int) + _Ints = (int, long) except NameError: # no long in Python 3+ _Ints = int _Seqs = (list, tuple) @@ -327,6 +327,9 @@ class _Enum(ctypes.c_uint): n = self._enum_names_.get(self.value, '') or ('FIXME_(%r)' % (self.value,)) return '.'.join((self.__class__.__name__, n)) + def __hash__(self): + return self.value + def __repr__(self): return '.'.join((self.__class__.__module__, self.__str__())) @@ -1294,7 +1297,7 @@ class Instance(_Ctype): i = args[0] if isinstance(i, _Ints): return _Constructor(cls, i) - elif isinstance(i, str): + elif isinstance(i, basestring): args = i.strip().split() elif isinstance(i, _Seqs): args = i @@ -2078,7 +2081,7 @@ class MediaList(_Ctype): @param mrl: a media instance or a MRL. @return: 0 on success, -1 if the media list is read-only. """ - if isinstance(mrl, str): + if isinstance(mrl, basestring): mrl = (self.get_instance() or get_default_instance()).media_new(mrl) return libvlc_media_list_add_media(self, mrl) @@ -3351,6 +3354,39 @@ def libvlc_event_type_name(event_type): ctypes.c_char_p, ctypes.c_uint) return f(event_type) +def libvlc_log_get_context(ctx): + '''Gets debugging informations about a log message: the name of the VLC module + emitting the message and the message location within the source code. + The returned module name and file name will be NULL if unknown. + The returned line number will similarly be zero if unknown. + @param ctx: message context (as passed to the @ref libvlc_log_cb callback). + @return: module module name storage (or NULL), file source code file name storage (or NULL), line source code file line number storage (or NULL). + @version: LibVLC 2.1.0 or later. + ''' + f = _Cfunctions.get('libvlc_log_get_context', None) or \ + _Cfunction('libvlc_log_get_context', ((1,), (2,), (2,), (2,),), None, + None, Log_ptr, ListPOINTER(ctypes.c_char_p), ListPOINTER(ctypes.c_char_p), ctypes.POINTER(ctypes.c_uint)) + return f(ctx) + +def libvlc_log_get_object(ctx, id): + '''Gets VLC object informations about a log message: the type name of the VLC + object emitting the message, the object header if any and a temporaly-unique + object identifier. These informations are mainly meant for B{manual} + troubleshooting. + The returned type name may be "generic" if unknown, but it cannot be NULL. + The returned header will be NULL if unset; in current versions, the header + is used to distinguish for VLM inputs. + The returned object ID will be zero if the message is not associated with + any VLC object. + @param ctx: message context (as passed to the @ref libvlc_log_cb callback). + @return: name object name storage (or NULL), header object header (or NULL), line source code file line number storage (or NULL). + @version: LibVLC 2.1.0 or later. + ''' + f = _Cfunctions.get('libvlc_log_get_object', None) or \ + _Cfunction('libvlc_log_get_object', ((1,), (2,), (2,), (1,),), None, + None, Log_ptr, ListPOINTER(ctypes.c_char_p), ListPOINTER(ctypes.c_char_p), ctypes.POINTER(ctypes.c_uint)) + return f(ctx, id) + def libvlc_log_unset(p_instance): '''Unsets the logging callback for a LibVLC instance. This is rarely needed: the callback is implicitly unset when the instance is destroyed. @@ -5827,7 +5863,7 @@ def libvlc_vlm_get_event_manager(p_instance): # libvlc_printerr # libvlc_set_exit_handler -# 15 function(s) not wrapped as methods: +# 17 function(s) not wrapped as methods: # libvlc_audio_output_device_list_release # libvlc_audio_output_list_release # libvlc_clearerr @@ -5838,6 +5874,8 @@ def libvlc_vlm_get_event_manager(p_instance): # libvlc_get_changeset # libvlc_get_compiler # libvlc_get_version +# libvlc_log_get_context +# libvlc_log_get_object # libvlc_media_tracks_release # libvlc_module_description_list_release # libvlc_new @@ -5910,9 +5948,9 @@ def debug_callback(event, *args, **kwds): ''' l = ['event %s' % (event.type,)] if args: - l.extend(list(map(str, args))) + l.extend(map(str, args)) if kwds: - l.extend(sorted('%s=%s' % t for t in list(kwds.items()))) + l.extend(sorted('%s=%s' % t for t in kwds.items())) print('Debug callback (%s)' % ', '.join(l)) if __name__ == '__main__': diff --git a/openlp/core/ui/media/webkitplayer.py b/openlp/core/ui/media/webkitplayer.py index e3a522140..38e691494 100644 --- a/openlp/core/ui/media/webkitplayer.py +++ b/openlp/core/ui/media/webkitplayer.py @@ -174,34 +174,11 @@ FLASH_HTML = """ """ -VIDEO_EXT = [ - '*.3gp', - '*.3gpp', - '*.3g2', - '*.3gpp2', - '*.aac', - '*.flv', - '*.f4a', - '*.f4b', - '*.f4p', - '*.f4v', - '*.mov', - '*.m4a', - '*.m4b', - '*.m4p', - '*.m4v', - '*.mkv', - '*.mp4', - '*.ogv', - '*.webm', - '*.mpg', '*.wmv', '*.mpeg', '*.avi', - '*.swf' -] +VIDEO_EXT = ['*.3gp', '*.3gpp', '*.3g2', '*.3gpp2', '*.aac', '*.flv', '*.f4a', '*.f4b', '*.f4p', '*.f4v', '*.mov', + '*.m4a', '*.m4b', '*.m4p', '*.m4v', '*.mkv', '*.mp4', '*.ogv', '*.webm', '*.mpg', '*.wmv', '*.mpeg', + '*.avi', '*.swf'] -AUDIO_EXT = [ - '*.mp3', - '*.ogg' -] +AUDIO_EXT = ['*.mp3', '*.ogg'] class WebkitPlayer(MediaPlayer): @@ -411,10 +388,9 @@ class WebkitPlayer(MediaPlayer): """ Return some information about this player """ - return(translate('Media.player', 'Webkit is a media player which runs ' - 'inside a web browser. This player allows text over video to be ' - 'rendered.') + - '
' + translate('Media.player', 'Audio') + - '
' + str(AUDIO_EXT) + '
' + - translate('Media.player', 'Video') + '
' + - str(VIDEO_EXT) + '
') + part1 = translate('Media.player', 'Webkit is a media player which runs inside a web browser. This player ' + 'allows text over video to be rendered.') + part2 = translate('Media.player', 'Audio') + part3 = translate('Media.player', 'Video') + return part1 + '
' + part2 + '
' + str(AUDIO_EXT) + '
' + part3 + \ + '
' + str(VIDEO_EXT) + '
' diff --git a/openlp/core/ui/pluginform.py b/openlp/core/ui/pluginform.py index 91b98b97a..78bdee4a5 100644 --- a/openlp/core/ui/pluginform.py +++ b/openlp/core/ui/pluginform.py @@ -30,7 +30,6 @@ The actual plugin view form """ import logging -import os from PyQt4 import QtGui diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index c2b36551a..70cbd6141 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -234,6 +234,9 @@ class Ui_ServiceManager(object): self.menu = QtGui.QMenu() self.edit_action = create_widget_action(self.menu, text=translate('OpenLP.ServiceManager', '&Edit Item'), icon=':/general/general_edit.png', triggers=self.remote_edit) + self.rename_action = create_widget_action(self.menu, text=translate('OpenLP.ServiceManager', '&Rename...'), + icon=':/general/general_edit.png', + triggers=self.on_service_item_rename) self.maintain_action = create_widget_action(self.menu, text=translate('OpenLP.ServiceManager', '&Reorder Item'), icon=':/general/general_edit.png', triggers=self.on_service_item_edit_form) @@ -399,7 +402,7 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtGui.QWidget, Ui_ServiceManage :param suffix_list: New Suffix's to be supported """ for suffix in suffix_list: - if not suffix in self.suffixes: + if suffix not in self.suffixes: self.suffixes.append(suffix) def on_new_service_clicked(self, field=None): @@ -629,7 +632,7 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtGui.QWidget, Ui_ServiceManage for item in self.service_items: self.main_window.increment_progress_bar() service_item = item['service_item'].get_service_repr(self._save_lite) - #TODO: check for file item on save. + # TODO: check for file item on save. service.append({'serviceitem': service_item}) self.main_window.increment_progress_bar() service_content = json.dumps(service) @@ -754,8 +757,9 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtGui.QWidget, Ui_ServiceManage items = json.load(file_to) else: critical_error_message_box(message=translate('OpenLP.ServiceManager', - 'The service file you are trying to open is in an old format.\n ' - 'Please save it using OpenLP 2.0.2 or greater.')) + 'The service file you are trying to open is in an old ' + 'format.\n Please save it using OpenLP 2.0.2 or ' + 'greater.')) return file_to.close() self.new_file() @@ -848,6 +852,7 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtGui.QWidget, Ui_ServiceManage pos = item.data(0, QtCore.Qt.UserRole) service_item = self.service_items[pos - 1] self.edit_action.setVisible(False) + self.rename_action.setVisible(False) self.create_custom_action.setVisible(False) self.maintain_action.setVisible(False) self.notes_action.setVisible(False) @@ -855,6 +860,8 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtGui.QWidget, Ui_ServiceManage self.auto_start_action.setVisible(False) if service_item['service_item'].is_capable(ItemCapabilities.CanEdit) and service_item['service_item'].edit_id: self.edit_action.setVisible(True) + if service_item['service_item'].is_capable(ItemCapabilities.CanEditTitle): + self.rename_action.setVisible(True) if service_item['service_item'].is_capable(ItemCapabilities.CanMaintain): self.maintain_action.setVisible(True) if item.parent() is None: @@ -1482,6 +1489,24 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtGui.QWidget, Ui_ServiceManage if new_item: self.add_service_item(new_item, replace=True) + def on_service_item_rename(self, field=None): + """ + Opens a dialog to rename the service item. + + :param field: Not used, but PyQt needs this. + """ + item = self.find_service_item()[0] + if not self.service_items[item]['service_item'].is_capable(ItemCapabilities.CanEditTitle): + return + title = self.service_items[item]['service_item'].title + title, ok = QtGui.QInputDialog.getText(self, translate('OpenLP.ServiceManager', 'Rename item title'), + translate('OpenLP.ServiceManager', 'Title:'), + QtGui.QLineEdit.Normal, self.trUtf8(title)) + if ok: + self.service_items[item]['service_item'].title = title + self.repaint_service_list(item, -1) + self.set_modified() + def create_custom(self, field=None): """ Saves the current text item as a custom slide diff --git a/openlp/core/ui/settingsform.py b/openlp/core/ui/settingsform.py index 5aba66ef2..a1077e1f4 100644 --- a/openlp/core/ui/settingsform.py +++ b/openlp/core/ui/settingsform.py @@ -150,5 +150,5 @@ class SettingsForm(QtGui.QDialog, Ui_SettingsDialog, RegistryProperties): :param function: The function to be called """ - if not function in self.processes: + if function not in self.processes: self.processes.append(function) diff --git a/openlp/core/ui/slidecontroller.py b/openlp/core/ui/slidecontroller.py index 7faf10ca2..bf92e9d76 100644 --- a/openlp/core/ui/slidecontroller.py +++ b/openlp/core/ui/slidecontroller.py @@ -495,14 +495,14 @@ class SlideController(DisplayController, RegistryProperties): self.on_theme_display(False) self.on_hide_display(False) - def service_previous(self): + def service_previous(self, field=None): """ Live event to select the previous service item from the service manager. """ self.keypress_queue.append(ServiceItemAction.Previous) self._process_queue() - def service_next(self): + def service_next(self, field=None): """ Live event to select the next service item from the service manager. """ @@ -1039,7 +1039,6 @@ class SlideController(DisplayController, RegistryProperties): """ self.preview_widget.change_slide(row) self.update_preview() - Registry().execute('slidecontroller_%s_changed' % self.type_prefix, row) def update_preview(self): """ diff --git a/openlp/core/ui/themeform.py b/openlp/core/ui/themeform.py index fbfc1035c..321071c49 100644 --- a/openlp/core/ui/themeform.py +++ b/openlp/core/ui/themeform.py @@ -90,7 +90,7 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard, RegistryProperties): self.footer_font_combo_box.activated.connect(self.update_theme) self.footer_size_spin_box.valueChanged.connect(self.update_theme) - def setDefaults(self): + def set_defaults(self): """ Set up display at start of theme edit. """ @@ -261,7 +261,7 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard, RegistryProperties): log.debug('Editing theme %s' % self.theme.theme_name) self.temp_background_filename = '' self.update_theme_allowed = False - self.setDefaults() + self.set_defaults() self.update_theme_allowed = True self.theme_name_label.setVisible(not edit) self.theme_name_edit.setVisible(not edit) @@ -432,7 +432,7 @@ class ThemeForm(QtGui.QWizard, Ui_ThemeWizard, RegistryProperties): Background Image button pushed. """ images_filter = get_images_filter() - images_filter = '%s;;%s (*.*) (*)' % (images_filter, UiStrings().AllFiles) + images_filter = '%s;;%s (*.*)' % (images_filter, UiStrings().AllFiles) filename = QtGui.QFileDialog.getOpenFileName(self, translate('OpenLP.ThemeWizard', 'Select Image'), '', images_filter) if filename: diff --git a/openlp/core/ui/themelayoutdialog.py b/openlp/core/ui/themelayoutdialog.py index bf6c1069a..023d6259c 100644 --- a/openlp/core/ui/themelayoutdialog.py +++ b/openlp/core/ui/themelayoutdialog.py @@ -44,7 +44,6 @@ class Ui_ThemeLayoutDialog(object): Set up the UI """ themeLayoutDialog.setObjectName('themeLayoutDialogDialog') - #themeLayoutDialog.resize(300, 200) self.preview_layout = QtGui.QVBoxLayout(themeLayoutDialog) self.preview_layout.setObjectName('preview_layout') self.preview_area = QtGui.QWidget(themeLayoutDialog) diff --git a/openlp/core/ui/themestab.py b/openlp/core/ui/themestab.py index 32ed30303..1f54f984b 100644 --- a/openlp/core/ui/themestab.py +++ b/openlp/core/ui/themestab.py @@ -114,17 +114,19 @@ class ThemesTab(SettingsTab): self.global_group_box.setTitle(translate('OpenLP.ThemesTab', 'Global Theme')) self.level_group_box.setTitle(translate('OpenLP.ThemesTab', 'Theme Level')) self.song_level_radio_button.setText(translate('OpenLP.ThemesTab', 'S&ong Level')) - self.song_level_label.setText(translate('OpenLP.ThemesTab', 'Use the theme from each song ' - 'in the database. If a song doesn\'t have a theme associated with ' - 'it, then use the service\'s theme. If the service doesn\'t have ' - 'a theme, then use the global theme.')) + self.song_level_label.setText( + translate('OpenLP.ThemesTab', 'Use the theme from each song in the database. If a song doesn\'t have a ' + 'theme associated with it, then use the service\'s theme. If the service ' + 'doesn\'t have a theme, then use the global theme.')) self.service_level_radio_button.setText(translate('OpenLP.ThemesTab', '&Service Level')) - self.service_level_label.setText(translate('OpenLP.ThemesTab', 'Use the theme from the service, ' - 'overriding any of the individual songs\' themes. If the ' - 'service doesn\'t have a theme, then use the global theme.')) + self.service_level_label.setText( + translate('OpenLP.ThemesTab', 'Use the theme from the service, overriding any of the individual ' + 'songs\' themes. If the service doesn\'t have a theme, then use the global ' + 'theme.')) self.global_level_radio_button.setText(translate('OpenLP.ThemesTab', '&Global Level')) - self.global_level_label.setText(translate('OpenLP.ThemesTab', 'Use the global theme, overriding ' - 'any themes associated with either the service or the songs.')) + self.global_level_label.setText(translate('OpenLP.ThemesTab', 'Use the global theme, overriding any themes ' + 'associated with either the service or the ' + 'songs.')) def load(self): """ diff --git a/openlp/core/ui/wizard.py b/openlp/core/ui/wizard.py index 05951d14a..5815457b5 100644 --- a/openlp/core/ui/wizard.py +++ b/openlp/core/ui/wizard.py @@ -197,7 +197,7 @@ class OpenLPWizard(QtGui.QWizard, RegistryProperties): """ Run the wizard. """ - self.setDefaults() + self.set_defaults() return QtGui.QWizard.exec_(self) def reject(self): diff --git a/openlp/core/utils/__init__.py b/openlp/core/utils/__init__.py index 746c50d71..a5b5f356a 100644 --- a/openlp/core/utils/__init__.py +++ b/openlp/core/utils/__init__.py @@ -56,7 +56,7 @@ if sys.platform != 'win32' and sys.platform != 'darwin': from openlp.core.common import translate -log = logging.getLogger(__name__+'.__init__') +log = logging.getLogger(__name__ + '.__init__') APPLICATION_VERSION = {} IMAGES_FILTER = None diff --git a/openlp/core/utils/languagemanager.py b/openlp/core/utils/languagemanager.py index cd5ce7add..bb584f7bd 100644 --- a/openlp/core/utils/languagemanager.py +++ b/openlp/core/utils/languagemanager.py @@ -74,7 +74,7 @@ class LanguageManager(object): log.debug('Translation files: %s', AppLocation.get_directory( AppLocation.LanguageDir)) trans_dir = QtCore.QDir(AppLocation.get_directory(AppLocation.LanguageDir)) - file_names = trans_dir.entryList('*.qm', QtCore.QDir.Files, QtCore.QDir.Name) + file_names = trans_dir.entryList(['*.qm'], QtCore.QDir.Files, QtCore.QDir.Name) # Remove qm files from the list which start with "qt_". file_names = [file_ for file_ in file_names if not file_.startswith('qt_')] return list(map(trans_dir.filePath, file_names)) diff --git a/openlp/plugins/alerts/alertsplugin.py b/openlp/plugins/alerts/alertsplugin.py index cf3b7d0ce..76efadf32 100644 --- a/openlp/plugins/alerts/alertsplugin.py +++ b/openlp/plugins/alerts/alertsplugin.py @@ -207,12 +207,12 @@ class AlertsPlugin(Plugin): """ Called to define all translatable texts of the plugin """ - ## Name PluginList ## + # Name PluginList self.text_strings[StringContent.Name] = { 'singular': translate('AlertsPlugin', 'Alert', 'name singular'), 'plural': translate('AlertsPlugin', 'Alerts', 'name plural') } - ## Name for MediaDockManager, SettingsManager ## + # Name for MediaDockManager, SettingsManager self.text_strings[StringContent.VisibleName] = { 'title': translate('AlertsPlugin', 'Alerts', 'container title') } diff --git a/openlp/plugins/bibles/bibleplugin.py b/openlp/plugins/bibles/bibleplugin.py index 5a51be163..e7f1fdd56 100644 --- a/openlp/plugins/bibles/bibleplugin.py +++ b/openlp/plugins/bibles/bibleplugin.py @@ -88,8 +88,6 @@ class BiblePlugin(Plugin): self.import_bible_item.setVisible(True) action_list = ActionList.get_instance() action_list.add_action(self.import_bible_item, UiStrings().Import) - # Do not add the action to the list yet. - #action_list.add_action(self.export_bible_item, UiStrings().Export) # Set to invisible until we can export bibles self.export_bible_item.setVisible(False) self.tools_upgrade_item.setVisible(bool(self.manager.old_bible_databases)) @@ -104,7 +102,6 @@ class BiblePlugin(Plugin): action_list = ActionList.get_instance() action_list.remove_action(self.import_bible_item, UiStrings().Import) self.import_bible_item.setVisible(False) - #action_list.remove_action(self.export_bible_item, UiStrings().Export) self.export_bible_item.setVisible(False) def app_startup(self): @@ -115,19 +112,27 @@ class BiblePlugin(Plugin): if self.manager.old_bible_databases: if QtGui.QMessageBox.information( self.main_window, translate('OpenLP', 'Information'), - translate('OpenLP', 'Bible format has changed.\nYou have to upgrade your existing Bibles.\n' - 'Should OpenLP upgrade now?'), - QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Yes | QtGui.QMessageBox.No)) == \ + translate('OpenLP', 'Bible format has changed.\nYou have to upgrade your ' + 'existing Bibles.\nShould OpenLP upgrade now?'), + QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Yes | QtGui.QMessageBox.No)) == \ QtGui.QMessageBox.Yes: self.on_tools_upgrade_Item_triggered() def add_import_menu_item(self, import_menu): + """ + + :param import_menu: + """ self.import_bible_item = create_action(import_menu, 'importBibleItem', text=translate('BiblesPlugin', '&Bible'), visible=False, triggers=self.on_bible_import_click) import_menu.addAction(self.import_bible_item) - def add_export_menu_Item(self, export_menu): + def add_export_menu_item(self, export_menu): + """ + + :param export_menu: + """ self.export_bible_item = create_action(export_menu, 'exportBibleItem', text=translate('BiblesPlugin', '&Bible'), visible=False) export_menu.addAction(self.export_bible_item) @@ -190,12 +195,12 @@ class BiblePlugin(Plugin): """ Called to define all translatable texts of the plugin """ - ## Name PluginList ## + # Name PluginList self.text_strings[StringContent.Name] = { 'singular': translate('BiblesPlugin', 'Bible', 'name singular'), 'plural': translate('BiblesPlugin', 'Bibles', 'name plural') } - ## Name for MediaDockManager, SettingsManager ## + # Name for MediaDockManager, SettingsManager self.text_strings[StringContent.VisibleName] = { 'title': translate('BiblesPlugin', 'Bibles', 'container title') } diff --git a/openlp/plugins/bibles/forms/bibleimportform.py b/openlp/plugins/bibles/forms/bibleimportform.py index ee5bee2d0..79b0bc699 100644 --- a/openlp/plugins/bibles/forms/bibleimportform.py +++ b/openlp/plugins/bibles/forms/bibleimportform.py @@ -465,7 +465,7 @@ class BibleImportForm(OpenLPWizard): self.license_details_page.registerField('license_copyright', self.copyright_edit) self.license_details_page.registerField('license_permissions', self.permissions_edit) - def setDefaults(self): + def set_defaults(self): """ Set default values for the wizard pages. """ diff --git a/openlp/plugins/bibles/forms/bibleupgradeform.py b/openlp/plugins/bibles/forms/bibleupgradeform.py index 9925b1ebc..09c0942b7 100644 --- a/openlp/plugins/bibles/forms/bibleupgradeform.py +++ b/openlp/plugins/bibles/forms/bibleupgradeform.py @@ -78,7 +78,7 @@ class BibleUpgradeForm(OpenLPWizard): Set up the UI for the bible wizard. """ super(BibleUpgradeForm, self).setupUi(image) - Registry().execute('openlp_stop_wizard', self.stop_import) + Registry().register_function('openlp_stop_wizard', self.stop_import) def stop_import(self): """ @@ -307,7 +307,7 @@ class BibleUpgradeForm(OpenLPWizard): if self.currentPage() == self.progress_page: return True - def setDefaults(self): + def set_defaults(self): """ Set default values for the wizard pages. """ diff --git a/openlp/plugins/bibles/lib/db.py b/openlp/plugins/bibles/lib/db.py index 9ffa5d53e..203d86406 100644 --- a/openlp/plugins/bibles/lib/db.py +++ b/openlp/plugins/bibles/lib/db.py @@ -32,9 +32,11 @@ import logging import os import re import sqlite3 +import time from PyQt4 import QtCore from sqlalchemy import Column, ForeignKey, Table, or_, types, func +from sqlalchemy.exc import OperationalError from sqlalchemy.orm import class_mapper, mapper, relation from sqlalchemy.orm.exc import UnmappedClassError @@ -154,7 +156,7 @@ class BibleDB(QtCore.QObject, Manager, RegistryProperties): if 'path' in kwargs: self.path = kwargs['path'] self.wizard = None - Registry().execute('openlp_stop_wizard', self.stop_import) + Registry().register_function('openlp_stop_wizard', self.stop_import) def stop_import(self): """ @@ -191,7 +193,7 @@ class BibleDB(QtCore.QObject, Manager, RegistryProperties): :param testament: *Defaults to 1.* The testament_reference_id from bibles_resources.sqlite of the testament this book belongs to. """ - log.debug('BibleDB.create_book("%s", "%s")', name, bk_ref_id) + log.debug('BibleDB.create_book("%s", "%s")' % (name, bk_ref_id)) book = Book.populate(name=name, book_reference_id=bk_ref_id, testament_reference_id=testament) self.save_object(book) return book @@ -202,7 +204,7 @@ class BibleDB(QtCore.QObject, Manager, RegistryProperties): :param book: The book object """ - log.debug('BibleDB.update_book("%s")', book.name) + log.debug('BibleDB.update_book("%s")' % book.name) return self.save_object(book) def delete_book(self, db_book): @@ -211,7 +213,7 @@ class BibleDB(QtCore.QObject, Manager, RegistryProperties): :param db_book: The book object. """ - log.debug('BibleDB.delete_book("%s")', db_book.name) + log.debug('BibleDB.delete_book("%s")' % db_book.name) if self.delete_object(Book, db_book.id): return True return False @@ -225,7 +227,7 @@ class BibleDB(QtCore.QObject, Manager, RegistryProperties): :param text_list: A dict of the verses to be inserted. The key is the verse number, and the value is the verse text. """ - log.debug('BibleDBcreate_chapter("%s", "%s")', book_id, chapter) + log.debug('BibleDBcreate_chapter("%s", "%s")' % (book_id, chapter)) # Text list has book and chapter as first two elements of the array. for verse_number, verse_text in text_list.items(): verse = Verse.populate( @@ -235,7 +237,12 @@ class BibleDB(QtCore.QObject, Manager, RegistryProperties): text=verse_text ) self.session.add(verse) - self.session.commit() + try: + self.session.commit() + except OperationalError: + # Wait 10ms and try again (lp#1154467) + time.sleep(0.01) + self.session.commit() def create_verse(self, book_id, chapter, verse, text): """ @@ -267,7 +274,7 @@ class BibleDB(QtCore.QObject, Manager, RegistryProperties): """ if not isinstance(value, str): value = str(value) - log.debug('BibleDB.save_meta("%s/%s")', key, value) + log.debug('BibleDB.save_meta("%s/%s")' % (key, value)) meta = self.get_object(BibleMeta, key) if meta: meta.value = value @@ -281,7 +288,7 @@ class BibleDB(QtCore.QObject, Manager, RegistryProperties): :param book: The name of the book to return. """ - log.debug('BibleDB.get_book("%s")', book) + log.debug('BibleDB.get_book("%s")' % book) return self.get_object_filtered(Book, Book.name.like(book + '%')) def get_books(self): @@ -292,17 +299,17 @@ class BibleDB(QtCore.QObject, Manager, RegistryProperties): log.debug('BibleDB.get_books()') return self.get_all_objects(Book, order_by_ref=Book.id) - def get_book_by_book_ref_id(self, id): + def get_book_by_book_ref_id(self, ref_id): """ Return a book object from the database. - :param id: The reference id of the book to return. + :param ref_id: The reference id of the book to return. """ - log.debug('BibleDB.get_book_by_book_ref_id("%s")', id) - return self.get_object_filtered(Book, Book.book_reference_id.like(id)) + log.debug('BibleDB.get_book_by_book_ref_id("%s")' % ref_id) + return self.get_object_filtered(Book, Book.book_reference_id.like(ref_id)) def get_book_ref_id_by_name(self, book, maxbooks, language_id=None): - log.debug('BibleDB.get_book_ref_id_by_name:("%s", "%s")', book, language_id) + log.debug('BibleDB.get_book_ref_id_by_name:("%s", "%s")' % (book, language_id)) book_id = None if BiblesResourcesDB.get_book(book, True): book_temp = BiblesResourcesDB.get_book(book, True) @@ -328,7 +335,7 @@ class BibleDB(QtCore.QObject, Manager, RegistryProperties): :param book: The name of the book, according to the selected language. :param language_selection: The language selection the user has chosen in the settings section of the Bible. """ - log.debug('get_book_ref_id_by_localised_name("%s", "%s")', book, language_selection) + log.debug('get_book_ref_id_by_localised_name("%s", "%s")' % (book, language_selection)) from openlp.plugins.bibles.lib import LanguageSelection, BibleStrings book_names = BibleStrings().BookNames # escape reserved characters @@ -376,14 +383,14 @@ class BibleDB(QtCore.QObject, Manager, RegistryProperties): [(u'35', 1, 1, 1), (u'35', 2, 2, 3)] :param show_error: """ - log.debug('BibleDB.get_verses("%s")', reference_list) + log.debug('BibleDB.get_verses("%s")' % reference_list) verse_list = [] book_error = False for book_id, chapter, start_verse, end_verse in reference_list: db_book = self.get_book_by_book_ref_id(book_id) if db_book: book_id = db_book.book_reference_id - log.debug('Book name corrected to "%s"', db_book.name) + log.debug('Book name corrected to "%s"' % db_book.name) if end_verse == -1: end_verse = self.get_verse_count(book_id, chapter) verses = self.session.query(Verse) \ @@ -395,7 +402,7 @@ class BibleDB(QtCore.QObject, Manager, RegistryProperties): .all() verse_list.extend(verses) else: - log.debug('OpenLP failed to find book with id "%s"', book_id) + log.debug('OpenLP failed to find book with id "%s"' % book_id) book_error = True if book_error and show_error: critical_error_message_box( @@ -414,7 +421,7 @@ class BibleDB(QtCore.QObject, Manager, RegistryProperties): contains spaces, it will split apart and AND'd on the list of values. """ - log.debug('BibleDB.verse_search("%s")', text) + log.debug('BibleDB.verse_search("%s")' % text) verses = self.session.query(Verse) if text.find(',') > -1: keywords = ['%%%s%%' % keyword.strip() for keyword in text.split(',')] @@ -433,7 +440,7 @@ class BibleDB(QtCore.QObject, Manager, RegistryProperties): :param book: The book object to get the chapter count for. """ - log.debug('BibleDB.get_chapter_count("%s")', book.name) + log.debug('BibleDB.get_chapter_count("%s")' % book.name) count = self.session.query(func.max(Verse.chapter)).join(Book).filter( Book.book_reference_id == book.book_reference_id).scalar() if not count: @@ -447,7 +454,7 @@ class BibleDB(QtCore.QObject, Manager, RegistryProperties): :param book_ref_id: The book reference id. :param chapter: The chapter to get the verse count for. """ - log.debug('BibleDB.get_verse_count("%s", "%s")', book_ref_id, chapter) + log.debug('BibleDB.get_verse_count("%s", "%s")' % (book_ref_id, chapter)) count = self.session.query(func.max(Verse.verse)).join(Book) \ .filter(Book.book_reference_id == book_ref_id) \ .filter(Verse.chapter == chapter) \ @@ -563,7 +570,7 @@ class BiblesResourcesDB(QtCore.QObject, Manager): :param name: The name or abbreviation of the book. :param lower: True if the comparison should be only lowercase """ - log.debug('BiblesResourcesDB.get_book("%s")', name) + log.debug('BiblesResourcesDB.get_book("%s")' % name) if not isinstance(name, str): name = str(name) if lower: @@ -592,7 +599,7 @@ class BiblesResourcesDB(QtCore.QObject, Manager): :param string: The string to search for in the book names or abbreviations. """ - log.debug('BiblesResourcesDB.get_book_like("%s")', string) + log.debug('BiblesResourcesDB.get_book_like("%s")' % string) if not isinstance(string, str): name = str(string) books = BiblesResourcesDB.run_sql( @@ -611,17 +618,17 @@ class BiblesResourcesDB(QtCore.QObject, Manager): return None @staticmethod - def get_book_by_id(id): + def get_book_by_id(book_id): """ Return a book by id. - :param id: The id of the book. + :param book_id: The id of the book. """ - log.debug('BiblesResourcesDB.get_book_by_id("%s")', id) - if not isinstance(id, int): - id = int(id) + log.debug('BiblesResourcesDB.get_book_by_id("%s")' % book_id) + if not isinstance(book_id, int): + book_id = int(book_id) books = BiblesResourcesDB.run_sql( - 'SELECT id, testament_id, name, abbreviation, chapters FROM book_reference WHERE id = ?', (id, )) + 'SELECT id, testament_id, name, abbreviation, chapters FROM book_reference WHERE id = ?', (book_id, )) if books: return { 'id': books[0][0], @@ -641,7 +648,7 @@ class BiblesResourcesDB(QtCore.QObject, Manager): :param book_ref_id: The id of a book. :param chapter: The chapter number. """ - log.debug('BiblesResourcesDB.get_chapter("%s", "%s")', book_ref_id, chapter) + log.debug('BiblesResourcesDB.get_chapter("%s", "%s")' % (book_ref_id, chapter)) if not isinstance(chapter, int): chapter = int(chapter) chapters = BiblesResourcesDB.run_sql( @@ -649,10 +656,10 @@ class BiblesResourcesDB(QtCore.QObject, Manager): 'chapter, verse_count FROM chapters WHERE book_reference_id = ?', (book_ref_id,)) try: return { - 'id': chapters[chapter-1][0], - 'book_reference_id': chapters[chapter-1][1], - 'chapter': chapters[chapter-1][2], - 'verse_count': chapters[chapter-1][3] + 'id': chapters[chapter - 1][0], + 'book_reference_id': chapters[chapter - 1][1], + 'chapter': chapters[chapter - 1][2], + 'verse_count': chapters[chapter - 1][3] } except (IndexError, TypeError): return None @@ -664,7 +671,7 @@ class BiblesResourcesDB(QtCore.QObject, Manager): :param book_ref_id: The id of the book. """ - log.debug('BiblesResourcesDB.get_chapter_count("%s")', book_ref_id) + log.debug('BiblesResourcesDB.get_chapter_count("%s")' % book_ref_id) details = BiblesResourcesDB.get_book_by_id(book_ref_id) if details: return details['chapters'] @@ -678,7 +685,7 @@ class BiblesResourcesDB(QtCore.QObject, Manager): :param book_ref_id: The id of the book. :param chapter: The number of the chapter. """ - log.debug('BiblesResourcesDB.get_verse_count("%s", "%s")', book_ref_id, chapter) + log.debug('BiblesResourcesDB.get_verse_count("%s", "%s")' % (book_ref_id, chapter)) details = BiblesResourcesDB.get_chapter(book_ref_id, chapter) if details: return details['verse_count'] @@ -691,7 +698,7 @@ class BiblesResourcesDB(QtCore.QObject, Manager): :param source: The name or abbreviation of the book. """ - log.debug('BiblesResourcesDB.get_download_source("%s")', source) + log.debug('BiblesResourcesDB.get_download_source("%s")' % source) if not isinstance(source, str): source = str(source) source = source.title() @@ -712,7 +719,7 @@ class BiblesResourcesDB(QtCore.QObject, Manager): :param source: The source of the web_bible. """ - log.debug('BiblesResourcesDB.get_webbibles("%s")', source) + log.debug('BiblesResourcesDB.get_webbibles("%s")' % source) if not isinstance(source, str): source = str(source) source = BiblesResourcesDB.get_download_source(source) @@ -737,7 +744,7 @@ class BiblesResourcesDB(QtCore.QObject, Manager): :param abbreviation: The abbreviation of the web_bible. :param source: The source of the web_bible. """ - log.debug('BiblesResourcesDB.get_webbibles("%s", "%s")', abbreviation, source) + log.debug('BiblesResourcesDB.get_webbibles("%s", "%s")' % (abbreviation, source)) if not isinstance(abbreviation, str): abbreviation = str(abbreviation) if not isinstance(source, str): @@ -765,7 +772,7 @@ class BiblesResourcesDB(QtCore.QObject, Manager): :param name: The name to search the id. :param language_id: The language_id for which language should be searched """ - log.debug('BiblesResourcesDB.get_alternative_book_name("%s", "%s")', name, language_id) + log.debug('BiblesResourcesDB.get_alternative_book_name("%s", "%s")' % (name, language_id)) if language_id: books = BiblesResourcesDB.run_sql( 'SELECT book_reference_id, name FROM alternative_book_names WHERE language_id = ? ORDER BY id', @@ -784,7 +791,7 @@ class BiblesResourcesDB(QtCore.QObject, Manager): :param name: The name or abbreviation of the language. """ - log.debug('BiblesResourcesDB.get_language("%s")', name) + log.debug('BiblesResourcesDB.get_language("%s")' % name) if not isinstance(name, str): name = str(name) language = BiblesResourcesDB.run_sql( @@ -846,13 +853,13 @@ class AlternativeBookNamesDB(QtCore.QObject, Manager): file_path = os.path.join( AppLocation.get_directory(AppLocation.DataDir), 'bibles', 'alternative_book_names.sqlite') if not os.path.exists(file_path): - #create new DB, create table alternative_book_names + # create new DB, create table alternative_book_names AlternativeBookNamesDB.conn = sqlite3.connect(file_path) AlternativeBookNamesDB.conn.execute( 'CREATE TABLE alternative_book_names(id INTEGER NOT NULL, ' 'book_reference_id INTEGER, language_id INTEGER, name VARCHAR(50), PRIMARY KEY (id))') else: - #use existing DB + # use existing DB AlternativeBookNamesDB.conn = sqlite3.connect(file_path) AlternativeBookNamesDB.cursor = AlternativeBookNamesDB.conn.cursor() return AlternativeBookNamesDB.cursor @@ -880,7 +887,7 @@ class AlternativeBookNamesDB(QtCore.QObject, Manager): :param name: The name to search the id. :param language_id: The language_id for which language should be searched """ - log.debug('AlternativeBookNamesDB.get_book_reference_id("%s", "%s")', name, language_id) + log.debug('AlternativeBookNamesDB.get_book_reference_id("%s", "%s")' % (name, language_id)) if language_id: books = AlternativeBookNamesDB.run_sql( 'SELECT book_reference_id, name FROM alternative_book_names WHERE language_id = ?', (language_id, )) @@ -901,8 +908,8 @@ class AlternativeBookNamesDB(QtCore.QObject, Manager): :param book_reference_id: The book_reference_id of the book. :param language_id: The language to which the alternative book name belong. """ - log.debug('AlternativeBookNamesDB.create_alternative_book_name("%s", "%s", "%s")', - name, book_reference_id, language_id) + log.debug('AlternativeBookNamesDB.create_alternative_book_name("%s", "%s", "%s")' % + (name, book_reference_id, language_id)) return AlternativeBookNamesDB.run_sql( 'INSERT INTO alternative_book_names(book_reference_id, language_id, name) ' 'VALUES (?, ?, ?)', (book_reference_id, language_id, name), True) diff --git a/openlp/plugins/bibles/lib/http.py b/openlp/plugins/bibles/lib/http.py index 340d8ef92..b15e11738 100644 --- a/openlp/plugins/bibles/lib/http.py +++ b/openlp/plugins/bibles/lib/http.py @@ -552,10 +552,10 @@ class HTTPBible(BibleDB, RegistryProperties): self.application.set_busy_cursor() search_results = self.get_chapter(book, reference[1]) if search_results and search_results.has_verse_list(): - ## We have found a book of the bible lets check to see - ## if it was there. By reusing the returned book name - ## we get a correct book. For example it is possible - ## to request ac and get Acts back. + # We have found a book of the bible lets check to see + # if it was there. By reusing the returned book name + # we get a correct book. For example it is possible + # to request ac and get Acts back. book_name = search_results.book self.application.process_events() # Check to see if book/chapter exists. diff --git a/openlp/plugins/bibles/lib/mediaitem.py b/openlp/plugins/bibles/lib/mediaitem.py index 10fef8e31..a2cb1b594 100644 --- a/openlp/plugins/bibles/lib/mediaitem.py +++ b/openlp/plugins/bibles/lib/mediaitem.py @@ -480,6 +480,10 @@ class BibleMediaItem(MediaManagerItem): self.reload_bibles() def on_delete_click(self): + """ + When the delete button is pressed + """ + bible = None if self.quickTab.isVisible(): bible = self.quickVersionComboBox.currentText() elif self.advancedTab.isVisible(): @@ -488,8 +492,9 @@ class BibleMediaItem(MediaManagerItem): if QtGui.QMessageBox.question( self, UiStrings().ConfirmDelete, translate('BiblesPlugin.MediaItem', 'Are you sure you want to completely delete "%s" Bible from ' - 'OpenLP?\n\nYou will need to re-import this Bible to use it again.') % bible, - QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Yes | QtGui.QMessageBox.No), + 'OpenLP?\n\nYou will need to re-import this Bible to use it ' + 'again.') % bible, + QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Yes | QtGui.QMessageBox.No), QtGui.QMessageBox.Yes) == QtGui.QMessageBox.No: return self.plugin.manager.delete_bible(bible) @@ -752,7 +757,7 @@ class BibleMediaItem(MediaManagerItem): log.exception('The second_search_results does not have as many verses as the search_results.') break bible_text = '%s %d%s%d (%s, %s)' % (book, verse.chapter, verse_separator, verse.verse, version, - second_version) + second_version) else: bible_text = '%s %d%s%d (%s)' % (book, verse.chapter, verse_separator, verse.verse, version) bible_verse = QtGui.QListWidgetItem(bible_text) @@ -840,6 +845,7 @@ class BibleMediaItem(MediaManagerItem): service_item.add_capability(ItemCapabilities.CanPreview) service_item.add_capability(ItemCapabilities.CanLoop) service_item.add_capability(ItemCapabilities.CanWordSplit) + service_item.add_capability(ItemCapabilities.CanEditTitle) # Service Item: Title service_item.title = create_separated_list(raw_title) # Service Item: Theme diff --git a/openlp/plugins/bibles/lib/opensong.py b/openlp/plugins/bibles/lib/opensong.py index a522c1c57..c7bfa01a2 100644 --- a/openlp/plugins/bibles/lib/opensong.py +++ b/openlp/plugins/bibles/lib/opensong.py @@ -73,13 +73,13 @@ class OpenSongBible(BibleDB): log.debug('Starting OpenSong import from "%s"' % self.filename) if not isinstance(self.filename, str): self.filename = str(self.filename, 'utf8') - file = None + import_file = None success = True try: # NOTE: We don't need to do any of the normal encoding detection here, because lxml does it's own encoding # detection, and the two mechanisms together interfere with each other. - file = open(self.filename, 'r') - opensong = objectify.parse(file) + import_file = open(self.filename, 'rb') + opensong = objectify.parse(import_file) bible = opensong.getroot() language_id = self.get_language(bible_name) if not language_id: @@ -93,7 +93,7 @@ class OpenSongBible(BibleDB): log.error('Importing books from "%s" failed' % self.filename) return False book_details = BiblesResourcesDB.get_book_by_id(book_ref_id) - db_book = self.create_book(str(book.attrib['n']), book_ref_id, book_details['testament_id']) + db_book = self.create_book(book.attrib['n'], book_ref_id, book_details['testament_id']) chapter_number = 0 for chapter in book.c: if self.stop_import_flag: @@ -122,8 +122,8 @@ class OpenSongBible(BibleDB): verse_number += 1 self.create_verse(db_book.id, chapter_number, verse_number, self.get_text(verse)) self.wizard.increment_progress_bar( - translate('BiblesPlugin.Opensong', 'Importing %s %s...', - 'Importing ...')) % (db_book.name, chapter_number) + translate('BiblesPlugin.Opensong', 'Importing %(bookname)s %(chapter)s...' % + {'bookname': db_book.name, 'chapter': chapter_number})) self.session.commit() self.application.process_events() except etree.XMLSyntaxError as inst: @@ -137,8 +137,8 @@ class OpenSongBible(BibleDB): log.exception('Loading Bible from OpenSong file failed') success = False finally: - if file: - file.close() + if import_file: + import_file.close() if self.stop_import_flag: return False else: diff --git a/openlp/plugins/bibles/lib/versereferencelist.py b/openlp/plugins/bibles/lib/versereferencelist.py index 1d3877d1d..7cbba68fb 100644 --- a/openlp/plugins/bibles/lib/versereferencelist.py +++ b/openlp/plugins/bibles/lib/versereferencelist.py @@ -94,5 +94,5 @@ class VerseReferenceList(object): result = result + ', ' + version['permission'] result = result.rstrip() if result.endswith(','): - return result[:len(result)-1] + return result[:len(result) - 1] return result diff --git a/openlp/plugins/custom/customplugin.py b/openlp/plugins/custom/customplugin.py index eb7de530a..2065717cb 100644 --- a/openlp/plugins/custom/customplugin.py +++ b/openlp/plugins/custom/customplugin.py @@ -43,7 +43,7 @@ log = logging.getLogger(__name__) __default_settings__ = { 'custom/db type': 'sqlite', - 'custom/last search type': CustomSearch.Titles, + 'custom/last search type': CustomSearch.Titles, 'custom/display footer': True, 'custom/add custom from service': True } @@ -97,12 +97,12 @@ class CustomPlugin(Plugin): """ Called to define all translatable texts of the plugin """ - ## Name PluginList ## + # Name PluginList self.text_strings[StringContent.Name] = { 'singular': translate('CustomPlugin', 'Custom Slide', 'name singular'), 'plural': translate('CustomPlugin', 'Custom Slides', 'name plural') } - ## Name for MediaDockManager, SettingsManager ## + # Name for MediaDockManager, SettingsManager self.text_strings[StringContent.VisibleName] = { 'title': translate('CustomPlugin', 'Custom Slides', 'container title') } diff --git a/openlp/plugins/custom/forms/editcustomslideform.py b/openlp/plugins/custom/forms/editcustomslideform.py index afce4887b..e6834dafb 100644 --- a/openlp/plugins/custom/forms/editcustomslideform.py +++ b/openlp/plugins/custom/forms/editcustomslideform.py @@ -1,4 +1,3 @@ -#lint:disable # -*- coding: utf-8 -*- # vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 diff --git a/openlp/plugins/custom/lib/customxmlhandler.py b/openlp/plugins/custom/lib/customxmlhandler.py index ca42a705b..3e1880055 100644 --- a/openlp/plugins/custom/lib/customxmlhandler.py +++ b/openlp/plugins/custom/lib/customxmlhandler.py @@ -51,7 +51,7 @@ from lxml import etree, objectify log = logging.getLogger(__name__) -#TODO: These classes need to be refactored into a single class. +# TODO: These classes need to be refactored into a single class. class CustomXMLBuilder(object): """ This class builds the XML used to describe songs. diff --git a/openlp/plugins/images/imageplugin.py b/openlp/plugins/images/imageplugin.py index 693449530..08c31f53c 100644 --- a/openlp/plugins/images/imageplugin.py +++ b/openlp/plugins/images/imageplugin.py @@ -95,12 +95,12 @@ class ImagePlugin(Plugin): """ Called to define all translatable texts of the plugin. """ - ## Name PluginList ## + # Name PluginList self.text_strings[StringContent.Name] = { 'singular': translate('ImagePlugin', 'Image', 'name singular'), 'plural': translate('ImagePlugin', 'Images', 'name plural') } - ## Name for MediaDockManager, SettingsManager ## + # Name for MediaDockManager, SettingsManager self.text_strings[StringContent.VisibleName] = {'title': translate('ImagePlugin', 'Images', 'container title')} # Middle Header Bar tooltips = { diff --git a/openlp/plugins/images/lib/mediaitem.py b/openlp/plugins/images/lib/mediaitem.py index b2eb5b816..c28f1e834 100644 --- a/openlp/plugins/images/lib/mediaitem.py +++ b/openlp/plugins/images/lib/mediaitem.py @@ -75,7 +75,7 @@ class ImageMediaItem(MediaManagerItem): def retranslateUi(self): self.on_new_prompt = translate('ImagePlugin.MediaItem', 'Select Image(s)') file_formats = get_images_filter() - self.on_new_file_masks = '%s;;%s (*.*) (*)' % (file_formats, UiStrings().AllFiles) + self.on_new_file_masks = '%s;;%s (*)' % (file_formats, UiStrings().AllFiles) self.add_group_action.setText(UiStrings().AddGroup) self.add_group_action.setToolTip(UiStrings().AddGroup) self.replace_action.setText(UiStrings().ReplaceBG) @@ -550,6 +550,7 @@ class ImageMediaItem(MediaManagerItem): service_item.add_capability(ItemCapabilities.CanPreview) service_item.add_capability(ItemCapabilities.CanLoop) service_item.add_capability(ItemCapabilities.CanAppend) + service_item.add_capability(ItemCapabilities.CanEditTitle) # force a nonexistent theme service_item.theme = -1 missing_items_file_names = [] diff --git a/openlp/plugins/media/lib/mediaitem.py b/openlp/plugins/media/lib/mediaitem.py index 56d3a49f4..87d8e3311 100644 --- a/openlp/plugins/media/lib/mediaitem.py +++ b/openlp/plugins/media/lib/mediaitem.py @@ -95,7 +95,6 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties): self.reset_action.setToolTip(UiStrings().ResetLiveBG) self.automatic = UiStrings().Automatic self.display_type_label.setText(translate('MediaPlugin.MediaItem', 'Use Player:')) - #self.rebuild_players() def required_icons(self): """ @@ -141,7 +140,7 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties): if index == 0: set_media_players(player) else: - set_media_players(player, player[index-1]) + set_media_players(player, player[index - 1]) def on_reset_click(self): """ @@ -216,6 +215,7 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties): if not self.media_controller.media_length(service_item): return False service_item.add_capability(ItemCapabilities.CanAutoStartForLive) + service_item.add_capability(ItemCapabilities.CanEditTitle) service_item.add_capability(ItemCapabilities.RequiresMedia) if Settings().value(self.settings_section + '/media auto start') == QtCore.Qt.Checked: service_item.will_auto_start = True diff --git a/openlp/plugins/media/mediaplugin.py b/openlp/plugins/media/mediaplugin.py index 6cf241d1d..d0e082f53 100644 --- a/openlp/plugins/media/mediaplugin.py +++ b/openlp/plugins/media/mediaplugin.py @@ -73,12 +73,12 @@ class MediaPlugin(Plugin): """ Called to define all translatable texts of the plugin """ - ## Name PluginList ## + # Name PluginList self.text_strings[StringContent.Name] = { 'singular': translate('MediaPlugin', 'Media', 'name singular'), 'plural': translate('MediaPlugin', 'Media', 'name plural') } - ## Name for MediaDockManager, SettingsManager ## + # Name for MediaDockManager, SettingsManager self.text_strings[StringContent.VisibleName] = { 'title': translate('MediaPlugin', 'Media', 'container title') } diff --git a/openlp/plugins/presentations/lib/impresscontroller.py b/openlp/plugins/presentations/lib/impresscontroller.py index c55873c5f..584c1401f 100644 --- a/openlp/plugins/presentations/lib/impresscontroller.py +++ b/openlp/plugins/presentations/lib/impresscontroller.py @@ -377,8 +377,6 @@ class ImpressDocument(PresentationDocument): Stop the presentation, remove from screen. """ log.debug('stop presentation OpenOffice') - # deactivate should hide the screen according to docs, but doesn't - #self.control.deactivate() self.presentation.end() self.control = None diff --git a/openlp/plugins/presentations/lib/mediaitem.py b/openlp/plugins/presentations/lib/mediaitem.py index 1894cbf1f..5b503d50f 100644 --- a/openlp/plugins/presentations/lib/mediaitem.py +++ b/openlp/plugins/presentations/lib/mediaitem.py @@ -263,6 +263,7 @@ class PresentationMediaItem(MediaManagerItem): file_type = os.path.splitext(filename)[1][1:] if not self.display_type_combo_box.currentText(): return False + service_item.add_capability(ItemCapabilities.CanEditTitle) if (file_type == 'pdf' or file_type == 'xps') and context != ServiceItemContext.Service: service_item.add_capability(ItemCapabilities.CanMaintain) service_item.add_capability(ItemCapabilities.CanPreview) diff --git a/openlp/plugins/presentations/lib/pdfcontroller.py b/openlp/plugins/presentations/lib/pdfcontroller.py index 597b7d78b..5ed11d2ab 100644 --- a/openlp/plugins/presentations/lib/pdfcontroller.py +++ b/openlp/plugins/presentations/lib/pdfcontroller.py @@ -74,7 +74,7 @@ class PdfController(PresentationController): runlog = '' log.debug('testing program_path: %s', program_path) try: - runlog = check_output([program_path, '--help'], stderr=STDOUT) + runlog = check_output([program_path, '--help'], stderr=STDOUT) except CalledProcessError as e: runlog = e.output except Exception: @@ -183,7 +183,7 @@ class PdfDocument(PresentationDocument): self.image_files = [] self.num_pages = -1 - def gs_get_resolution(self, size): + def gs_get_resolution(self, size): """ Only used when using ghostscript Ghostscript can't scale automatically while keeping aspect like mupdf, so we need @@ -204,19 +204,19 @@ class PdfDocument(PresentationDocument): log.debug(' '.join(e.cmd)) log.debug(e.output) # Extract the pdf resolution from output, the format is " Size: x: , y: " - width = 0 - height = 0 + width = 0.0 + height = 0.0 for line in runlog.splitlines(): try: - width = int(re.search('.*Size: x: (\d+\.?\d*), y: \d+.*', line.decode()).group(1)) - height = int(re.search('.*Size: x: \d+\.?\d*, y: (\d+\.?\d*).*', line.decode()).group(1)) + width = float(re.search('.*Size: x: (\d+\.?\d*), y: \d+.*', line.decode()).group(1)) + height = float(re.search('.*Size: x: \d+\.?\d*, y: (\d+\.?\d*).*', line.decode()).group(1)) break except AttributeError: - pass + continue # Calculate the ratio from pdf to screen if width > 0 and height > 0: - width_ratio = size.right() / float(width) - height_ratio = size.bottom() / float(height) + width_ratio = size.right() / width + height_ratio = size.bottom() / height # return the resolution that should be used. 72 is default. if width_ratio > height_ratio: return int(height_ratio * 72) @@ -236,7 +236,7 @@ class PdfDocument(PresentationDocument): if os.path.isfile(os.path.join(self.get_temp_folder(), 'mainslide001.png')): created_files = sorted(os.listdir(self.get_temp_folder())) for fn in created_files: - if os.path.isfile(os.path.join(self.get_temp_folder(), fn)): + if os.path.isfile(os.path.join(self.get_temp_folder(), fn)): self.image_files.append(os.path.join(self.get_temp_folder(), fn)) self.num_pages = len(self.image_files) return True diff --git a/openlp/plugins/presentations/lib/powerpointcontroller.py b/openlp/plugins/presentations/lib/powerpointcontroller.py index d6447adce..09fdf2fcc 100644 --- a/openlp/plugins/presentations/lib/powerpointcontroller.py +++ b/openlp/plugins/presentations/lib/powerpointcontroller.py @@ -247,7 +247,7 @@ class PowerpointDocument(PresentationDocument): Starts a presentation from the beginning. """ log.debug('start_presentation') - #SlideShowWindow measures its size/position by points, not pixels + # SlideShowWindow measures its size/position by points, not pixels try: dpi = win32ui.GetActiveWindow().GetDC().GetDeviceCaps(88) except win32ui.error: diff --git a/openlp/plugins/presentations/lib/presentationcontroller.py b/openlp/plugins/presentations/lib/presentationcontroller.py index fd636b468..6c8d7fb8c 100644 --- a/openlp/plugins/presentations/lib/presentationcontroller.py +++ b/openlp/plugins/presentations/lib/presentationcontroller.py @@ -122,8 +122,10 @@ class PresentationDocument(object): a file, e.g. thumbnails """ try: - shutil.rmtree(self.get_thumbnail_folder()) - shutil.rmtree(self.get_temp_folder()) + if os.path.exists(self.get_thumbnail_folder()): + shutil.rmtree(self.get_thumbnail_folder()) + if os.path.exists(self.get_temp_folder()): + shutil.rmtree(self.get_temp_folder()) except OSError: log.exception('Failed to delete presentation controller files') diff --git a/openlp/plugins/presentations/lib/presentationtab.py b/openlp/plugins/presentations/lib/presentationtab.py index 7d5c81366..590289014 100644 --- a/openlp/plugins/presentations/lib/presentationtab.py +++ b/openlp/plugins/presentations/lib/presentationtab.py @@ -175,10 +175,10 @@ class PresentationTab(SettingsTab): if pdf_program == '': enable_pdf_program = 0 if pdf_program != Settings().value(self.settings_section + '/pdf_program'): - Settings().setValue(self.settings_section + '/pdf_program', pdf_program) + Settings().setValue(self.settings_section + '/pdf_program', pdf_program) changed = True if enable_pdf_program != Settings().value(self.settings_section + '/enable_pdf_program'): - Settings().setValue(self.settings_section + '/enable_pdf_program', enable_pdf_program) + Settings().setValue(self.settings_section + '/enable_pdf_program', enable_pdf_program) changed = True if changed: self.settings_form.register_post_process('mediaitem_suffix_reset') diff --git a/openlp/plugins/presentations/presentationplugin.py b/openlp/plugins/presentations/presentationplugin.py index de0a0c5a5..5e0d7395d 100644 --- a/openlp/plugins/presentations/presentationplugin.py +++ b/openlp/plugins/presentations/presentationplugin.py @@ -156,12 +156,12 @@ class PresentationPlugin(Plugin): """ Called to define all translatable texts of the plugin. """ - ## Name PluginList ## + # Name PluginList self.text_strings[StringContent.Name] = { 'singular': translate('PresentationPlugin', 'Presentation', 'name singular'), 'plural': translate('PresentationPlugin', 'Presentations', 'name plural') } - ## Name for MediaDockManager, SettingsManager ## + # Name for MediaDockManager, SettingsManager self.text_strings[StringContent.VisibleName] = { 'title': translate('PresentationPlugin', 'Presentations', 'container title') } diff --git a/openlp/plugins/remotes/lib/httprouter.py b/openlp/plugins/remotes/lib/httprouter.py index 5a10a14ae..4241b34dc 100644 --- a/openlp/plugins/remotes/lib/httprouter.py +++ b/openlp/plugins/remotes/lib/httprouter.py @@ -149,11 +149,11 @@ class HttpRouter(RegistryProperties): """ Initialise the router stack and any other variables. """ - authcode = "%s:%s" % (Settings().value('remotes/user id'), Settings().value('remotes/password')) + auth_code = "%s:%s" % (Settings().value('remotes/user id'), Settings().value('remotes/password')) try: - self.auth = base64.b64encode(authcode) + self.auth = base64.b64encode(auth_code) except TypeError: - self.auth = base64.b64encode(authcode.encode()).decode() + self.auth = base64.b64encode(auth_code.encode()).decode() self.routes = [ ('^/$', {'function': self.serve_file, 'secure': False}), ('^/(stage)$', {'function': self.serve_file, 'secure': False}), @@ -376,7 +376,6 @@ class HttpRouter(RegistryProperties): Examines the extension of the file and determines what the content_type should be, defaults to text/plain Returns the extension and the content_type """ - content_type = 'text/plain' ext = os.path.splitext(file_name)[1] content_type = FILE_TYPES.get(ext, 'text/plain') return ext, content_type @@ -439,7 +438,7 @@ class HttpRouter(RegistryProperties): if plugin.status == PluginStatus.Active: try: text = json.loads(self.request_data)['request']['text'] - except KeyError as ValueError: + except KeyError: return self.do_http_error() text = urllib.parse.unquote(text) self.alerts_manager.emit(QtCore.SIGNAL('alerts_text'), [text]) @@ -453,6 +452,7 @@ class HttpRouter(RegistryProperties): """ Perform an action on the slide controller. """ + log.debug("controller_text var = %s" % var) current_item = self.live_controller.service_item data = [] if current_item: @@ -488,7 +488,7 @@ class HttpRouter(RegistryProperties): if self.request_data: try: data = json.loads(self.request_data)['request']['id'] - except KeyError as ValueError: + except KeyError: return self.do_http_error() log.info(data) # This slot expects an int within a list. @@ -547,7 +547,7 @@ class HttpRouter(RegistryProperties): """ try: text = json.loads(self.request_data)['request']['text'] - except KeyError as ValueError: + except KeyError: return self.do_http_error() text = urllib.parse.unquote(text) plugin = self.plugin_manager.get_plugin_by_name(plugin_name) @@ -563,12 +563,12 @@ class HttpRouter(RegistryProperties): Go live on an item of type ``plugin``. """ try: - id = json.loads(self.request_data)['request']['id'] - except KeyError as ValueError: + request_id = json.loads(self.request_data)['request']['id'] + except KeyError: return self.do_http_error() plugin = self.plugin_manager.get_plugin_by_name(plugin_name) if plugin.status == PluginStatus.Active and plugin.media_item: - plugin.media_item.emit(QtCore.SIGNAL('%s_go_live' % plugin_name), [id, True]) + plugin.media_item.emit(QtCore.SIGNAL('%s_go_live' % plugin_name), [request_id, True]) return self.do_http_success() def add_to_service(self, plugin_name): @@ -576,11 +576,11 @@ class HttpRouter(RegistryProperties): Add item of type ``plugin_name`` to the end of the service. """ try: - id = json.loads(self.request_data)['request']['id'] - except KeyError as ValueError: + request_id = json.loads(self.request_data)['request']['id'] + except KeyError: return self.do_http_error() plugin = self.plugin_manager.get_plugin_by_name(plugin_name) if plugin.status == PluginStatus.Active and plugin.media_item: - item_id = plugin.media_item.create_item_from_id(id) + item_id = plugin.media_item.create_item_from_id(request_id) plugin.media_item.emit(QtCore.SIGNAL('%s_add_to_service' % plugin_name), [item_id, True]) self.do_http_success() diff --git a/openlp/plugins/remotes/lib/httpserver.py b/openlp/plugins/remotes/lib/httpserver.py index 22d0349f8..9a904090d 100644 --- a/openlp/plugins/remotes/lib/httpserver.py +++ b/openlp/plugins/remotes/lib/httpserver.py @@ -40,7 +40,7 @@ import time from PyQt4 import QtCore -from openlp.core.common import AppLocation, Settings +from openlp.core.common import AppLocation, Settings, RegistryProperties from openlp.plugins.remotes.lib import HttpRouter @@ -94,13 +94,18 @@ class HttpThread(QtCore.QThread): """ self.http_server.start_server() + def stop(self): + log.debug("stop called") + self.http_server.stop = True -class OpenLPServer(): + +class OpenLPServer(RegistryProperties): def __init__(self): """ Initialise the http server, and start the server of the correct type http / https """ - log.debug('Initialise httpserver') + super(OpenLPServer, self).__init__() + log.debug('Initialise OpenLP') self.settings_section = 'remotes' self.http_thread = HttpThread(self) self.http_thread.start() @@ -110,32 +115,49 @@ class OpenLPServer(): Start the correct server and save the handler """ address = Settings().value(self.settings_section + '/ip address') - if Settings().value(self.settings_section + '/https enabled'): + self.address = address + self.is_secure = Settings().value(self.settings_section + '/https enabled') + self.needs_authentication = Settings().value(self.settings_section + '/authentication enabled') + if self.is_secure: port = Settings().value(self.settings_section + '/https port') - self.httpd = HTTPSServer((address, port), CustomHandler) - log.debug('Started ssl httpd...') + self.port = port + self.start_server_instance(address, port, HTTPSServer) else: port = Settings().value(self.settings_section + '/port') - loop = 1 - while loop < 3: - try: - self.httpd = ThreadingHTTPServer((address, port), CustomHandler) - except OSError: - loop += 1 - time.sleep(0.1) - except: - log.error('Failed to start server ') - log.debug('Started non ssl httpd...') + self.port = port + self.start_server_instance(address, port, ThreadingHTTPServer) if hasattr(self, 'httpd') and self.httpd: self.httpd.serve_forever() else: log.debug('Failed to start server') + def start_server_instance(self, address, port, server_class): + """ + Start the server + + :param address: The server address + :param port: The run port + :param server_class: the class to start + """ + loop = 1 + while loop < 4: + try: + self.httpd = server_class((address, port), CustomHandler) + log.debug("Server started for class %s %s %d" % (server_class, address, port)) + except OSError: + log.debug("failed to start http server thread state %d %s" % + (loop, self.http_thread.isRunning())) + loop += 1 + time.sleep(0.1) + except: + log.error('Failed to start server ') + def stop_server(self): """ Stop the server """ - self.http_thread.exit(0) + if self.http_thread.isRunning(): + self.http_thread.stop() self.httpd = None log.debug('Stopped the server.') diff --git a/openlp/plugins/remotes/lib/remotetab.py b/openlp/plugins/remotes/lib/remotetab.py index d6b96cc1c..4db25cfc2 100644 --- a/openlp/plugins/remotes/lib/remotetab.py +++ b/openlp/plugins/remotes/lib/remotetab.py @@ -32,7 +32,7 @@ import os.path from PyQt4 import QtCore, QtGui, QtNetwork from openlp.core.common import AppLocation, Settings, translate -from openlp.core.lib import SettingsTab +from openlp.core.lib import SettingsTab, build_icon ZERO_URL = '0.0.0.0' @@ -234,6 +234,7 @@ class RemoteTab(SettingsTab): """ Load the configuration and update the server configuration if necessary """ + self.is_secure = Settings().value(self.settings_section + '/https enabled') self.port_spin_box.setValue(Settings().value(self.settings_section + '/port')) self.https_port_spin_box.setValue(Settings().value(self.settings_section + '/https port')) self.address_edit.setText(Settings().value(self.settings_section + '/ip address')) @@ -263,9 +264,7 @@ class RemoteTab(SettingsTab): Settings().value(self.settings_section + '/port') != self.port_spin_box.value() or \ Settings().value(self.settings_section + '/https port') != self.https_port_spin_box.value() or \ Settings().value(self.settings_section + '/https enabled') != \ - self.https_settings_group_box.isChecked() or \ - Settings().value(self.settings_section + '/authentication enabled') != \ - self.user_login_group_box.isChecked(): + self.https_settings_group_box.isChecked(): self.settings_form.register_post_process('remotes_config_updated') Settings().setValue(self.settings_section + '/port', self.port_spin_box.value()) Settings().setValue(self.settings_section + '/https port', self.https_port_spin_box.value()) @@ -275,6 +274,7 @@ class RemoteTab(SettingsTab): Settings().setValue(self.settings_section + '/authentication enabled', self.user_login_group_box.isChecked()) Settings().setValue(self.settings_section + '/user id', self.user_id.text()) Settings().setValue(self.settings_section + '/password', self.password.text()) + self.generate_icon() def on_twelve_hour_check_box_changed(self, check_state): """ @@ -290,3 +290,25 @@ class RemoteTab(SettingsTab): Invert the HTTP group box based on Https group settings """ self.http_settings_group_box.setEnabled(not self.https_settings_group_box.isChecked()) + + def generate_icon(self): + """ + Generate icon for main window + """ + self.remote_server_icon.hide() + icon = QtGui.QImage(':/remote/network_server.png') + icon = icon.scaled(80, 80, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation) + if self.is_secure: + overlay = QtGui.QImage(':/remote/network_ssl.png') + overlay = overlay.scaled(60, 60, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation) + painter = QtGui.QPainter(icon) + painter.drawImage(0, 0, overlay) + painter.end() + if Settings().value(self.settings_section + '/authentication enabled'): + overlay = QtGui.QImage(':/remote/network_auth.png') + overlay = overlay.scaled(60, 60, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation) + painter = QtGui.QPainter(icon) + painter.drawImage(20, 0, overlay) + painter.end() + self.remote_server_icon.setPixmap(QtGui.QPixmap.fromImage(icon)) + self.remote_server_icon.show() diff --git a/openlp/plugins/remotes/remoteplugin.py b/openlp/plugins/remotes/remoteplugin.py index 393f08dd9..582192df4 100644 --- a/openlp/plugins/remotes/remoteplugin.py +++ b/openlp/plugins/remotes/remoteplugin.py @@ -28,7 +28,8 @@ ############################################################################### import logging -import time + +from PyQt4 import QtGui from openlp.core.lib import Plugin, StringContent, translate, build_icon from openlp.plugins.remotes.lib import RemoteTab, OpenLPServer @@ -67,6 +68,21 @@ class RemotesPlugin(Plugin): log.debug('initialise') super(RemotesPlugin, self).initialise() self.server = OpenLPServer() + if not hasattr(self, 'remote_server_icon'): + self.remote_server_icon = QtGui.QLabel(self.main_window.status_bar) + size_policy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) + size_policy.setHorizontalStretch(0) + size_policy.setVerticalStretch(0) + size_policy.setHeightForWidth(self.remote_server_icon.sizePolicy().hasHeightForWidth()) + self.remote_server_icon.setSizePolicy(size_policy) + self.remote_server_icon.setFrameShadow(QtGui.QFrame.Plain) + self.remote_server_icon.setLineWidth(1) + self.remote_server_icon.setScaledContents(True) + self.remote_server_icon.setFixedSize(20, 20) + self.remote_server_icon.setObjectName('remote_server_icon') + self.main_window.status_bar.insertPermanentWidget(2, self.remote_server_icon) + self.settings_tab.remote_server_icon = self.remote_server_icon + self.settings_tab.generate_icon() def finalise(self): """ @@ -92,21 +108,23 @@ class RemotesPlugin(Plugin): """ Called to define all translatable texts of the plugin """ - ## Name PluginList ## + # Name PluginList self.text_strings[StringContent.Name] = { 'singular': translate('RemotePlugin', 'Remote', 'name singular'), 'plural': translate('RemotePlugin', 'Remotes', 'name plural') } - ## Name for MediaDockManager, SettingsManager ## + # Name for MediaDockManager, SettingsManager self.text_strings[StringContent.VisibleName] = { 'title': translate('RemotePlugin', 'Remote', 'container title') } def config_update(self): """ - Called when Config is changed to restart the server on new address or port + Called when Config is changed to requests a restart with the server on new address or port """ log.debug('remote config changed') - self.finalise() - time.sleep(0.5) - self.initialise() + QtGui.QMessageBox.information(self.main_window, + translate('RemotePlugin', 'Server Config Change'), + translate('RemotePlugin', 'Server configuration changes will require a restart ' + 'to take effect.'), + QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Ok)) diff --git a/openlp/plugins/songs/forms/duplicatesongremovalform.py b/openlp/plugins/songs/forms/duplicatesongremovalform.py index 22299cde5..c411c8c1c 100644 --- a/openlp/plugins/songs/forms/duplicatesongremovalform.py +++ b/openlp/plugins/songs/forms/duplicatesongremovalform.py @@ -31,6 +31,7 @@ The duplicate song removal logic for OpenLP. """ import logging +import multiprocessing import os from PyQt4 import QtCore, QtGui @@ -45,6 +46,18 @@ from openlp.plugins.songs.lib.songcompare import songs_probably_equal log = logging.getLogger(__name__) +def song_generator(songs): + """ + This is a generator function to return tuples of two songs. When completed then all songs have once been returned + combined with any other songs. + + :param songs: All songs in the database. + """ + for outer_song_counter in range(len(songs) - 1): + for inner_song_counter in range(outer_song_counter + 1, len(songs)): + yield (songs[outer_song_counter], songs[inner_song_counter]) + + class DuplicateSongRemovalForm(OpenLPWizard, RegistryProperties): """ This is the Duplicate Song Removal Wizard. It provides functionality to search for and remove duplicate songs @@ -114,7 +127,7 @@ class DuplicateSongRemovalForm(OpenLPWizard, RegistryProperties): self.review_layout.addWidget(self.review_scroll_area) self.review_page_id = self.addPage(self.review_page) # Add a dummy page to the end, to prevent the finish button to appear and the next button do disappear on the - #review page. + # review page. self.dummy_page = QtGui.QWizardPage() self.dummy_page_id = self.addPage(self.dummy_page) @@ -167,24 +180,31 @@ class DuplicateSongRemovalForm(OpenLPWizard, RegistryProperties): max_progress_count = max_songs * (max_songs - 1) // 2 self.duplicate_search_progress_bar.setMaximum(max_progress_count) songs = self.plugin.manager.get_all_objects(Song) - for outer_song_counter in range(max_songs - 1): - for inner_song_counter in range(outer_song_counter + 1, max_songs): - if songs_probably_equal(songs[outer_song_counter], songs[inner_song_counter]): - duplicate_added = self.add_duplicates_to_song_list( - songs[outer_song_counter], songs[inner_song_counter]) - if duplicate_added: - self.found_duplicates_edit.appendPlainText( - songs[outer_song_counter].title + " = " + songs[inner_song_counter].title) - self.duplicate_search_progress_bar.setValue(self.duplicate_search_progress_bar.value() + 1) - # The call to process_events() will keep the GUI responsive. - self.application.process_events() - if self.break_search: - return + # Create a worker/process pool to check the songs. + process_number = max(1, multiprocessing.cpu_count() - 1) + pool = multiprocessing.Pool(process_number) + result = pool.imap_unordered(songs_probably_equal, song_generator(songs), 30) + # Do not accept any further tasks. Also this closes the processes if all tasks are done. + pool.close() + # While the processes are still working, start to look at the results. + for song_tuple in result: + self.duplicate_search_progress_bar.setValue(self.duplicate_search_progress_bar.value() + 1) + # The call to process_events() will keep the GUI responsive. + self.application.process_events() + if self.break_search: + pool.terminate() + return + if song_tuple is None: + continue + song1, song2 = song_tuple + duplicate_added = self.add_duplicates_to_song_list(song1, song2) + if duplicate_added: + self.found_duplicates_edit.appendPlainText(song1.title + " = " + song2.title) self.review_total_count = len(self.duplicate_song_list) - if self.review_total_count == 0: - self.notify_no_duplicates() - else: + if self.duplicate_song_list: self.button(QtGui.QWizard.NextButton).show() + else: + self.notify_no_duplicates() finally: self.application.set_normal_cursor() elif page_id == self.review_page_id: @@ -217,12 +237,12 @@ class DuplicateSongRemovalForm(OpenLPWizard, RegistryProperties): duplicate_added = False for duplicate_group in self.duplicate_song_list: # Skip the first song in the duplicate lists, since the first one has to be an earlier song. - if search_song in duplicate_group and not duplicate_song in duplicate_group: + if search_song in duplicate_group and duplicate_song not in duplicate_group: duplicate_group.append(duplicate_song) duplicate_group_found = True duplicate_added = True break - elif not search_song in duplicate_group and duplicate_song in duplicate_group: + elif search_song not in duplicate_group and duplicate_song in duplicate_group: duplicate_group.append(search_song) duplicate_group_found = True duplicate_added = True @@ -244,7 +264,7 @@ class DuplicateSongRemovalForm(OpenLPWizard, RegistryProperties): self.break_search = True self.plugin.media_item.on_search_text_button_clicked() - def setDefaults(self): + def set_defaults(self): """ Set default form values for the song import wizard. """ diff --git a/openlp/plugins/songs/forms/editsongdialog.py b/openlp/plugins/songs/forms/editsongdialog.py index f2ef5af06..d0fb51a2d 100644 --- a/openlp/plugins/songs/forms/editsongdialog.py +++ b/openlp/plugins/songs/forms/editsongdialog.py @@ -118,13 +118,18 @@ class Ui_EditSongDialog(object): self.authors_group_box.setObjectName('authors_group_box') self.authors_layout = QtGui.QVBoxLayout(self.authors_group_box) self.authors_layout.setObjectName('authors_layout') - self.author_add_layout = QtGui.QHBoxLayout() + self.author_add_layout = QtGui.QVBoxLayout() self.author_add_layout.setObjectName('author_add_layout') + self.author_type_layout = QtGui.QHBoxLayout() + self.author_type_layout.setObjectName('author_type_layout') self.authors_combo_box = create_combo_box(self.authors_group_box, 'authors_combo_box') self.author_add_layout.addWidget(self.authors_combo_box) + self.author_types_combo_box = create_combo_box(self.authors_group_box, 'author_types_combo_box', editable=False) + self.author_type_layout.addWidget(self.author_types_combo_box) self.author_add_button = QtGui.QPushButton(self.authors_group_box) self.author_add_button.setObjectName('author_add_button') - self.author_add_layout.addWidget(self.author_add_button) + self.author_type_layout.addWidget(self.author_add_button) + self.author_add_layout.addLayout(self.author_type_layout) self.authors_layout.addLayout(self.author_add_layout) self.authors_list_view = QtGui.QListWidget(self.authors_group_box) self.authors_list_view.setAlternatingRowColors(True) @@ -330,7 +335,7 @@ class Ui_EditSongDialog(object): translate('SongsPlugin.EditSongForm', 'Warning: You have not entered a verse order.') -def create_combo_box(parent, name): +def create_combo_box(parent, name, editable=True): """ Utility method to generate a standard combo box for this dialog. @@ -340,7 +345,7 @@ def create_combo_box(parent, name): combo_box = QtGui.QComboBox(parent) combo_box.setSizeAdjustPolicy(QtGui.QComboBox.AdjustToMinimumContentsLength) combo_box.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed) - combo_box.setEditable(True) + combo_box.setEditable(editable) combo_box.setInsertPolicy(QtGui.QComboBox.NoInsert) combo_box.setObjectName(name) return combo_box diff --git a/openlp/plugins/songs/forms/editsongform.py b/openlp/plugins/songs/forms/editsongform.py index b655c0f73..1814655ea 100644 --- a/openlp/plugins/songs/forms/editsongform.py +++ b/openlp/plugins/songs/forms/editsongform.py @@ -42,7 +42,7 @@ from openlp.core.common import Registry, RegistryProperties, AppLocation, UiStri from openlp.core.lib import FileDialog, PluginStatus, MediaType, create_separated_list from openlp.core.lib.ui import set_case_insensitive_completer, critical_error_message_box, find_and_set_in_combo_box from openlp.plugins.songs.lib import VerseType, clean_song -from openlp.plugins.songs.lib.db import Book, Song, Author, Topic, MediaFile +from openlp.plugins.songs.lib.db import Book, Song, Author, AuthorSong, AuthorType, Topic, MediaFile from openlp.plugins.songs.lib.ui import SongStrings from openlp.plugins.songs.lib.xml import SongXML from openlp.plugins.songs.forms.editsongdialog import Ui_EditSongDialog @@ -122,12 +122,12 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog, RegistryProperties): combo.setItemData(row, obj.id) set_case_insensitive_completer(cache, combo) - def _add_author_to_list(self, author): + def _add_author_to_list(self, author, author_type): """ Add an author to the author list. """ - author_item = QtGui.QListWidgetItem(str(author.display_name)) - author_item.setData(QtCore.Qt.UserRole, author.id) + author_item = QtGui.QListWidgetItem(author.get_display_name(author_type)) + author_item.setData(QtCore.Qt.UserRole, (author.id, author_type)) self.authors_list_view.addItem(author_item) def _extract_verse_order(self, verse_order): @@ -217,8 +217,8 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog, RegistryProperties): if self.authors_list_view.count() == 0: self.song_tab_widget.setCurrentIndex(1) self.authors_list_view.setFocus() - critical_error_message_box( - message=translate('SongsPlugin.EditSongForm', 'You need to have an author for this song.')) + critical_error_message_box(message=translate('SongsPlugin.EditSongForm', + 'You need to have an author for this song.')) return False if self.verse_order_edit.text(): result = self._validate_verse_list(self.verse_order_edit.text(), self.verse_list_widget.rowCount()) @@ -302,6 +302,15 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog, RegistryProperties): self.authors.append(author.display_name) set_case_insensitive_completer(self.authors, self.authors_combo_box) + # Types + self.author_types_combo_box.clear() + self.author_types_combo_box.addItem('') + # Don't iterate over the dictionary to give them this specific order + self.author_types_combo_box.addItem(AuthorType.Types[AuthorType.Words], AuthorType.Words) + self.author_types_combo_box.addItem(AuthorType.Types[AuthorType.Music], AuthorType.Music) + self.author_types_combo_box.addItem(AuthorType.Types[AuthorType.WordsAndMusic], AuthorType.WordsAndMusic) + self.author_types_combo_box.addItem(AuthorType.Types[AuthorType.Translation], AuthorType.Translation) + def load_topics(self): """ Load the topics into the combobox. @@ -454,10 +463,8 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog, RegistryProperties): self.tag_rows() # clear the results self.authors_list_view.clear() - for author in self.song.authors: - author_name = QtGui.QListWidgetItem(str(author.display_name)) - author_name.setData(QtCore.Qt.UserRole, author.id) - self.authors_list_view.addItem(author_name) + for author_song in self.song.authors_songs: + self._add_author_to_list(author_song.author, author_song.author_type) # clear the results self.topics_list_view.clear() for topic in self.song.topics: @@ -496,6 +503,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog, RegistryProperties): """ item = int(self.authors_combo_box.currentIndex()) text = self.authors_combo_box.currentText().strip(' \r\n\t') + author_type = self.author_types_combo_box.itemData(self.author_types_combo_box.currentIndex()) # This if statement is for OS X, which doesn't seem to work well with # the QCompleter auto-completion class. See bug #812628. if text in self.authors: @@ -513,7 +521,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog, RegistryProperties): author = Author.populate(first_name=text.rsplit(' ', 1)[0], last_name=text.rsplit(' ', 1)[1], display_name=text) self.manager.save_object(author) - self._add_author_to_list(author) + self._add_author_to_list(author, author_type) self.load_authors() self.authors_combo_box.setCurrentIndex(0) else: @@ -521,11 +529,11 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog, RegistryProperties): elif item > 0: item_id = (self.authors_combo_box.itemData(item)) author = self.manager.get_object(Author, item_id) - if self.authors_list_view.findItems(str(author.display_name), QtCore.Qt.MatchExactly): + if self.authors_list_view.findItems(author.get_display_name(author_type), QtCore.Qt.MatchExactly): critical_error_message_box( message=translate('SongsPlugin.EditSongForm', 'This author is already in the list.')) else: - self._add_author_to_list(author) + self._add_author_to_list(author, author_type) self.authors_combo_box.setCurrentIndex(0) else: QtGui.QMessageBox.warning( @@ -675,14 +683,13 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog, RegistryProperties): separator = parts.find(':') if separator >= 0: verse_name = parts[0:separator].strip() - verse_num = parts[separator+1:].strip() + verse_num = parts[separator + 1:].strip() else: verse_name = parts verse_num = '1' verse_index = VerseType.from_loose_input(verse_name) verse_tag = VerseType.tags[verse_index] # Later we need to handle v1a as well. - #regex = re.compile(r'(\d+\w.)') regex = re.compile(r'\D*(\d+)\D*') match = regex.match(verse_num) if match: @@ -906,13 +913,13 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog, RegistryProperties): else: self.song.theme_name = None self._process_lyrics() - self.song.authors = [] + self.song.authors_songs = [] for row in range(self.authors_list_view.count()): item = self.authors_list_view.item(row) - author_id = (item.data(QtCore.Qt.UserRole)) - author = self.manager.get_object(Author, author_id) - if author is not None: - self.song.authors.append(author) + author_song = AuthorSong() + author_song.author_id = item.data(QtCore.Qt.UserRole)[0] + author_song.author_type = item.data(QtCore.Qt.UserRole)[1] + self.song.authors_songs.append(author_song) self.song.topics = [] for row in range(self.topics_list_view.count()): item = self.topics_list_view.item(row) diff --git a/openlp/plugins/songs/forms/editverseform.py b/openlp/plugins/songs/forms/editverseform.py index 038cff539..79a69a015 100644 --- a/openlp/plugins/songs/forms/editverseform.py +++ b/openlp/plugins/songs/forms/editverseform.py @@ -75,7 +75,7 @@ class EditVerseForm(QtGui.QDialog, Ui_EditVerseDialog): text = self.verse_text_edit.toPlainText() position = self.verse_text_edit.textCursor().position() insert_string = '[---]' - if position and text[position-1] != '\n': + if position and text[position - 1] != '\n': insert_string = '\n' + insert_string if position == len(text) or text[position] != '\n': insert_string += '\n' diff --git a/openlp/plugins/songs/forms/mediafilesdialog.py b/openlp/plugins/songs/forms/mediafilesdialog.py index a0392e3db..3a1db39ed 100644 --- a/openlp/plugins/songs/forms/mediafilesdialog.py +++ b/openlp/plugins/songs/forms/mediafilesdialog.py @@ -66,8 +66,10 @@ class Ui_MediaFilesDialog(object): def retranslateUi(self, media_files_dialog): """ Translate the UI on the fly. + + :param media_files_dialog: """ media_files_dialog.setWindowTitle(translate('SongsPlugin.MediaFilesForm', 'Select Media File(s)')) - self.select_label.setText(translate('SongsPlugin.MediaFilesForm', - 'Select one or more audio files from the list below, and click OK to import them ' - 'into this song.')) + self.select_label.setText(translate('SongsPlugin.MediaFilesForm', 'Select one or more audio files from the ' + 'list below, and click OK to import them ' + 'into this song.')) diff --git a/openlp/plugins/songs/forms/songexportform.py b/openlp/plugins/songs/forms/songexportform.py index 4e345b750..589da4d33 100644 --- a/openlp/plugins/songs/forms/songexportform.py +++ b/openlp/plugins/songs/forms/songexportform.py @@ -152,9 +152,9 @@ class SongExportForm(OpenLPWizard): self.setWindowTitle(translate('SongsPlugin.ExportWizardForm', 'Song Export Wizard')) self.title_label.setText(WizardStrings.HeaderStyle % translate('OpenLP.Ui', 'Welcome to the Song Export Wizard')) - self.information_label.setText(translate('SongsPlugin.ExportWizardForm', 'This wizard will help to' - ' export your songs to the open and free OpenLyrics worship ' - 'song format.')) + self.information_label.setText( + translate('SongsPlugin.ExportWizardForm', 'This wizard will help to export your songs to the open and free ' + 'OpenLyrics worship song format.')) self.available_songs_page.setTitle(translate('SongsPlugin.ExportWizardForm', 'Select Songs')) self.available_songs_page.setSubTitle(translate('SongsPlugin.ExportWizardForm', 'Check the songs you want to export.')) diff --git a/openlp/plugins/songs/forms/songimportform.py b/openlp/plugins/songs/forms/songimportform.py index 27f0d9343..21569a034 100644 --- a/openlp/plugins/songs/forms/songimportform.py +++ b/openlp/plugins/songs/forms/songimportform.py @@ -304,7 +304,7 @@ class SongImportForm(OpenLPWizard, RegistryProperties): """ self.source_page.emit(QtCore.SIGNAL('completeChanged()')) - def setDefaults(self): + def set_defaults(self): """ Set default form values for the song import wizard. """ diff --git a/openlp/plugins/songs/lib/__init__.py b/openlp/plugins/songs/lib/__init__.py index dc198d4b7..aa9fbc4c9 100644 --- a/openlp/plugins/songs/lib/__init__.py +++ b/openlp/plugins/songs/lib/__init__.py @@ -390,7 +390,7 @@ def clean_song(manager, song): verses = SongXML().get_verses(song.lyrics) song.search_lyrics = ' '.join([clean_string(verse[1]) for verse in verses]) # The song does not have any author, add one. - if not song.authors: + if not song.authors and not song.authors_songs: # Need to check both relations name = SongStrings.AuthorUnknown author = manager.get_object_filtered(Author, Author.display_name == name) if author is None: diff --git a/openlp/plugins/songs/lib/cclifileimport.py b/openlp/plugins/songs/lib/cclifileimport.py index 0866a1cc7..5c1fbdcb2 100644 --- a/openlp/plugins/songs/lib/cclifileimport.py +++ b/openlp/plugins/songs/lib/cclifileimport.py @@ -270,13 +270,13 @@ class CCLIFileImport(SongImport): verse_text = '' verse_start = False else: - #line_number=0, song title + # line_number=0, song title if line_number == 0: self.title = clean_line line_number += 1 - #line_number=1, verses + # line_number=1, verses elif line_number == 1: - #line_number=1, ccli number, first line after verses + # line_number=1, ccli number, first line after verses if clean_line.startswith('CCLI'): line_number += 1 ccli_parts = clean_line.split(' ') @@ -319,21 +319,21 @@ class CCLIFileImport(SongImport): # last part. Add l so as to keep the CRLF verse_text = verse_text + line else: - #line_number=2, copyright + # line_number=2, copyright if line_number == 2: line_number += 1 if clean_line.startswith('©'): self.copyright = clean_line else: song_author = clean_line - #n=3, authors + # n=3, authors elif line_number == 3: line_number += 1 if song_author: self.copyright = clean_line else: song_author = clean_line - #line_number=4, comments lines before last line + # line_number=4, comments lines before last line elif line_number == 4 and not clean_line.startswith('CCL'): self.comments += clean_line # split on known separators diff --git a/openlp/plugins/songs/lib/db.py b/openlp/plugins/songs/lib/db.py index c3965e2ed..91649c951 100644 --- a/openlp/plugins/songs/lib/db.py +++ b/openlp/plugins/songs/lib/db.py @@ -35,19 +35,52 @@ import re from sqlalchemy import Column, ForeignKey, Table, types from sqlalchemy.orm import mapper, relation, reconstructor -from sqlalchemy.sql.expression import func +from sqlalchemy.sql.expression import func, text from openlp.core.lib.db import BaseModel, init_db from openlp.core.utils import get_natural_key +from openlp.core.lib import translate class Author(BaseModel): """ Author model """ + def get_display_name(self, author_type=None): + if author_type: + return "%s (%s)" % (self.display_name, AuthorType.Types[author_type]) + return self.display_name + + +class AuthorSong(BaseModel): + """ + Relationship between Authors and Songs (many to many). + Need to define this relationship table explicit to get access to the + Association Object (author_type). + http://docs.sqlalchemy.org/en/latest/orm/relationships.html#association-object + """ pass +class AuthorType(object): + """ + Enumeration for Author types. + They are defined by OpenLyrics: http://openlyrics.info/dataformat.html#authors + + The 'words+music' type is not an official type, but is provided for convenience. + """ + Words = 'words' + Music = 'music' + WordsAndMusic = 'words+music' + Translation = 'translation' + Types = { + Words: translate('OpenLP.Ui', 'Words'), + Music: translate('OpenLP.Ui', 'Music'), + WordsAndMusic: translate('OpenLP.Ui', 'Words and Music'), + Translation: translate('OpenLP.Ui', 'Translation') + } + + class Book(BaseModel): """ Book model @@ -67,6 +100,7 @@ class Song(BaseModel): """ Song model """ + def __init__(self): self.sort_key = [] @@ -120,6 +154,7 @@ def init_schema(url): * author_id * song_id + * author_type **media_files Table** * id @@ -230,7 +265,8 @@ def init_schema(url): authors_songs_table = Table( 'authors_songs', metadata, Column('author_id', types.Integer(), ForeignKey('authors.id'), primary_key=True), - Column('song_id', types.Integer(), ForeignKey('songs.id'), primary_key=True) + Column('song_id', types.Integer(), ForeignKey('songs.id'), primary_key=True), + Column('author_type', types.String(), primary_key=True, nullable=False, server_default=text('""')) ) # Definition of the "songs_topics" table @@ -241,10 +277,15 @@ def init_schema(url): ) mapper(Author, authors_table) + mapper(AuthorSong, authors_songs_table, properties={ + 'author': relation(Author) + }) mapper(Book, song_books_table) mapper(MediaFile, media_files_table) mapper(Song, songs_table, properties={ - 'authors': relation(Author, backref='songs', secondary=authors_songs_table, lazy=False), + # Use the authors_songs relation when you need access to the 'author_type' attribute. + 'authors_songs': relation(AuthorSong, cascade="all, delete-orphan"), + 'authors': relation(Author, secondary=authors_songs_table), 'book': relation(Book, backref='songs'), 'media_files': relation(MediaFile, backref='songs', order_by=media_files_table.c.weight), 'topics': relation(Topic, backref='songs', secondary=songs_topics_table) diff --git a/openlp/plugins/songs/lib/ewimport.py b/openlp/plugins/songs/lib/ewimport.py index cde0b1692..ca201279a 100644 --- a/openlp/plugins/songs/lib/ewimport.py +++ b/openlp/plugins/songs/lib/ewimport.py @@ -305,7 +305,7 @@ class EasyWorshipSongImport(SongImport): elif field_desc.field_type == FieldType.Logical: return field ^ 0x80 == 1 elif field_desc.field_type == FieldType.Memo or field_desc.field_type == FieldType.Blob: - block_start, blob_size = struct.unpack_from('`` - OpenLP does not support the attribute *type* and *lang*. + OpenLP does not support the attribute *lang*. ```` This property is not supported. @@ -269,10 +269,18 @@ class OpenLyrics(object): 'verseOrder', properties, song.verse_order.lower()) if song.ccli_number: self._add_text_to_element('ccliNo', properties, song.ccli_number) - if song.authors: + if song.authors_songs: authors = etree.SubElement(properties, 'authors') - for author in song.authors: - self._add_text_to_element('author', authors, author.display_name) + for author_song in song.authors_songs: + element = self._add_text_to_element('author', authors, author_song.author.display_name) + if author_song.author_type: + # Handle the special case 'words+music': Need to create two separate authors for that + if author_song.author_type == AuthorType.WordsAndMusic: + element.set('type', AuthorType.Words) + element = self._add_text_to_element('author', authors, author_song.author.display_name) + element.set('type', AuthorType.Music) + else: + element.set('type', author_song.author_type) book = self.manager.get_object_filtered(Book, Book.id == song.song_book_id) if book is not None: book = book.name @@ -501,16 +509,20 @@ class OpenLyrics(object): if hasattr(properties, 'authors'): for author in properties.authors.author: display_name = self._text(author) + author_type = author.get('type', '') if display_name: - authors.append(display_name) - for display_name in authors: + authors.append((display_name, author_type)) + for (display_name, author_type) in authors: author = self.manager.get_object_filtered(Author, Author.display_name == display_name) if author is None: # We need to create a new author, as the author does not exist. author = Author.populate(display_name=display_name, last_name=display_name.split(' ')[-1], first_name=' '.join(display_name.split(' ')[:-1])) - song.authors.append(author) + author_song = AuthorSong() + author_song.author = author + author_song.author_type = author_type + song.authors_songs.append(author_song) def _process_cclinumber(self, properties, song): """ @@ -675,6 +687,7 @@ class OpenLyrics(object): sxml = SongXML() verses = {} verse_def_list = [] + verse_order = self._text(properties.verseOrder).split(' ') if hasattr(properties, 'verseOrder') else [] try: lyrics = song_xml.lyrics except AttributeError: @@ -717,13 +730,17 @@ class OpenLyrics(object): else: verses[(verse_tag, verse_number, lang, translit, verse_part)] = text verse_def_list.append((verse_tag, verse_number, lang, translit, verse_part)) + # Update verse order when the verse name has changed + if verse_def != verse_tag + verse_number + verse_part: + for i in range(len(verse_order)): + if verse_order[i] == verse_def: + verse_order[i] = verse_tag + verse_number + verse_part # We have to use a list to keep the order, as dicts are not sorted. for verse in verse_def_list: sxml.add_verse_to_lyrics(verse[0], verse[1], verses[verse], verse[2]) song_obj.lyrics = str(sxml.extract_xml(), 'utf-8') # Process verse order - if hasattr(properties, 'verseOrder'): - song_obj.verse_order = self._text(properties.verseOrder) + song_obj.verse_order = ' '.join(verse_order) def _process_songbooks(self, properties, song): """ diff --git a/openlp/plugins/songs/songsplugin.py b/openlp/plugins/songs/songsplugin.py index 1c000338c..b1ddaf412 100644 --- a/openlp/plugins/songs/songsplugin.py +++ b/openlp/plugins/songs/songsplugin.py @@ -132,7 +132,7 @@ class SongsPlugin(Plugin): ) import_menu.addAction(self.import_songselect_item) - def add_export_menu_Item(self, export_menu): + def add_export_menu_item(self, export_menu): """ Give the Songs plugin the opportunity to add items to the **Export** menu. @@ -261,12 +261,12 @@ class SongsPlugin(Plugin): """ Called to define all translatable texts of the plugin """ - ## Name PluginList ## + # Name PluginList self.text_strings[StringContent.Name] = { 'singular': translate('SongsPlugin', 'Song', 'name singular'), 'plural': translate('SongsPlugin', 'Songs', 'name plural') } - ## Name for MediaDockManager, SettingsManager ## + # Name for MediaDockManager, SettingsManager self.text_strings[StringContent.VisibleName] = { 'title': translate('SongsPlugin', 'Songs', 'container title') } diff --git a/openlp/plugins/songusage/songusageplugin.py b/openlp/plugins/songusage/songusageplugin.py index 4d38de6dd..8c0fc5762 100644 --- a/openlp/plugins/songusage/songusageplugin.py +++ b/openlp/plugins/songusage/songusageplugin.py @@ -246,12 +246,12 @@ class SongUsagePlugin(Plugin): """ Called to define all translatable texts of the plugin """ - ## Name PluginList ## + # Name PluginList self.text_strings[StringContent.Name] = { 'singular': translate('SongUsagePlugin', 'SongUsage', 'name singular'), 'plural': translate('SongUsagePlugin', 'SongUsage', 'name plural') } - ## Name for MediaDockManager, SettingsManager ## + # Name for MediaDockManager, SettingsManager self.text_strings[StringContent.VisibleName] = { 'title': translate('SongUsagePlugin', 'SongUsage', 'container title') } diff --git a/resources/images/network_auth.png b/resources/images/network_auth.png new file mode 100644 index 000000000..45e7a5c17 Binary files /dev/null and b/resources/images/network_auth.png differ diff --git a/resources/images/network_server.png b/resources/images/network_server.png new file mode 100644 index 000000000..25b95f3b0 Binary files /dev/null and b/resources/images/network_server.png differ diff --git a/resources/images/network_ssl.png b/resources/images/network_ssl.png new file mode 100644 index 000000000..1169de67a Binary files /dev/null and b/resources/images/network_ssl.png differ diff --git a/resources/images/openlp-2.qrc b/resources/images/openlp-2.qrc index 6af0e77a5..79036f08f 100644 --- a/resources/images/openlp-2.qrc +++ b/resources/images/openlp-2.qrc @@ -149,6 +149,11 @@ messagebox_info.png messagebox_warning.png + + network_server.png + network_ssl.png + network_auth.png + song_usage_active.png song_usage_inactive.png diff --git a/scripts/jenkins_script.py b/scripts/jenkins_script.py index 386ab69ef..eeafbfe23 100644 --- a/scripts/jenkins_script.py +++ b/scripts/jenkins_script.py @@ -40,6 +40,7 @@ You can look up the token in the Branch-01-Pull job configuration or ask in IRC. """ from optparse import OptionParser +import re from requests.exceptions import HTTPError from subprocess import Popen, PIPE import sys @@ -49,6 +50,9 @@ from jenkins import Jenkins JENKINS_URL = 'http://ci.openlp.org/' +REPO_REGEX = r'(.*/+)(~.*)' +# Allows us to black list token. So when we change the token, we can display a proper message to the user. +OLD_TOKENS = [] class OpenLPJobs(object): @@ -59,9 +63,20 @@ class OpenLPJobs(object): Branch_Functional = 'Branch-02-Functional-Tests' Branch_Interface = 'Branch-03-Interface-Tests' Branch_Windows = 'Branch-04-Windows_Tests' - Branch_PEP = 'Branch-05-Code-Analysis' + Branch_PEP = 'Branch-05a-Code_Analysis' + Branch_Coverage = 'Branch-05b-Test_Coverage' - Jobs = [Branch_Pull, Branch_Functional, Branch_Interface, Branch_Windows, Branch_PEP] + Jobs = [Branch_Pull, Branch_Functional, Branch_Interface, Branch_Windows, Branch_PEP, Branch_Coverage] + + +class Colour(object): + """ + This class holds values which can be used to print coloured text. + """ + RED_START = '\033[1;31m' + RED_END = '\033[1;m' + GREEN_START = '\033[1;32m' + GREEN_END = '\033[1;m' class JenkinsTrigger(object): @@ -79,14 +94,25 @@ class JenkinsTrigger(object): """ Ask our jenkins server to build the "Branch-01-Pull" job. """ - self.jenkins_instance.job(OpenLPJobs.Branch_Pull).build({'BRANCH_NAME': self.repo_name}, token=self.token) + bzr = Popen(('bzr', 'whoami'), stdout=PIPE, stderr=PIPE) + raw_output, error = bzr.communicate() + # We just want the name (not the email). + name = ' '.join(raw_output.decode().split()[:-1]) + cause = 'Build triggered by %s (%s)' % (name, self.repo_name) + self.jenkins_instance.job(OpenLPJobs.Branch_Pull).build( + {'BRANCH_NAME': self.repo_name, 'cause': cause}, token=self.token) def print_output(self): """ Print the status information of the build tirggered. """ - print("Add this to your merge proposal:") - print("--------------------------------") + print('Add this to your merge proposal:') + print('--------------------------------') + bzr = Popen(('bzr', 'revno'), stdout=PIPE, stderr=PIPE) + raw_output, error = bzr.communicate() + revno = raw_output.decode().strip() + print('%s (revision %s)' % (get_repo_name(), revno)) + for job in OpenLPJobs.Jobs: self.__print_build_info(job) @@ -107,22 +133,22 @@ class JenkinsTrigger(object): """ job = self.jenkins_instance.job(job_name) while job.info['inQueue']: - # Give other processes the possibility to take over. Like Thread.yield(). - time.sleep(0) + time.sleep(1) build = job.last_build build.wait() - result_string = build.info['result'] + if build.info['result'] == 'SUCCESS': + # Make 'SUCCESS' green. + result_string = '%s%s%s' % (Colour.GREEN_START, build.info['result'], Colour.GREEN_END) + else: + # Make 'FAILURE' red. + result_string = '%s%s%s' % (Colour.RED_START, build.info['result'], Colour.RED_END) url = build.info['url'] print('[%s] %s' % (result_string, url)) - # On failure open the browser. - #if result_string == "FAILURE": - # url += 'console' - # Popen(('xdg-open', url), stderr=PIPE) def get_repo_name(): """ - This returns the name of branch of the wokring directory. For example it returns *lp:~googol/openlp/render*. + This returns the name of branch of the working directory. For example it returns *lp:~googol/openlp/render*. """ # Run the bzr command. bzr = Popen(('bzr', 'info'), stdout=PIPE, stderr=PIPE) @@ -139,46 +165,41 @@ def get_repo_name(): for line in output_list: # Check if it is remote branch. if 'push branch' in line: - repo_name = line.replace('push branch: bzr+ssh://bazaar.launchpad.net/', 'lp:') - break + match = re.match(REPO_REGEX, line) + if match: + repo_name = 'lp:%s' % match.group(2) + break elif 'checkout of branch' in line: - repo_name = line.replace('checkout of branch: bzr+ssh://bazaar.launchpad.net/', 'lp:') - break - repo_name = repo_name.strip('/') - - # Did we find the branch name? - if not repo_name: - for line in output_list: - # Check if the branch was pushed. - if 'Shared repository with trees (format: 2a)' in line: - print('Not a branch. cd to a branch.') - return - print('Not a branch. Have you pushed it to launchpad?') - return - return repo_name + match = re.match(REPO_REGEX, line) + if match: + repo_name = 'lp:%s' % match.group(2) + break + return repo_name.strip('/') def main(): usage = 'Usage: python %prog TOKEN [options]' parser = OptionParser(usage=usage) - parser.add_option('-d', '--disable-output', dest='enable_output', action="store_false", default=True, + parser.add_option('-d', '--disable-output', dest='enable_output', action='store_false', default=True, help='Disable output.') - parser.add_option('-b', '--open-browser', dest='open_browser', action="store_true", default=False, + parser.add_option('-b', '--open-browser', dest='open_browser', action='store_true', default=False, help='Opens the jenkins page in your browser.') - #parser.add_option('-e', '--open-browser-on-error', dest='open_browser_on_error', action="store_true", - # default=False, help='Opens the jenkins page in your browser in case a test fails.') options, args = parser.parse_args(sys.argv) if len(args) == 2: if not get_repo_name(): + print('Not a branch. Have you pushed it to launchpad? Did you cd to the branch?') return token = args[-1] + if token in OLD_TOKENS: + print('Your token is not valid anymore. Get the most recent one.') + return jenkins_trigger = JenkinsTrigger(token) try: jenkins_trigger.trigger_build() - except HTTPError as e: - print("Wrong token.") + except HTTPError: + print('Wrong token.') return # Open the browser before printing the output. if options.open_browser: diff --git a/scripts/translation_utils.py b/scripts/translation_utils.py index 5aa320806..ad3edcaa3 100755 --- a/scripts/translation_utils.py +++ b/scripts/translation_utils.py @@ -96,7 +96,7 @@ class CommandStack(object): return len(self.data) def __getitem__(self, index): - if not index in self.data: + if index not in self.data: return None elif self.data[index].get('arguments'): return self.data[index]['command'], self.data[index]['arguments'] diff --git a/tests/functional/openlp_core_common/test_init.py b/tests/functional/openlp_core_common/test_common.py similarity index 76% rename from tests/functional/openlp_core_common/test_init.py rename to tests/functional/openlp_core_common/test_common.py index dcc4fe32a..ab2d11b3a 100644 --- a/tests/functional/openlp_core_common/test_init.py +++ b/tests/functional/openlp_core_common/test_common.py @@ -32,12 +32,13 @@ Functional tests to test the AppLocation class and related methods. from unittest import TestCase -from openlp.core.common import de_hump +from openlp.core.common import de_hump, trace_error_handler +from tests.functional import MagicMock, patch -class TestInitFunctions(TestCase): +class TestCommonFunctions(TestCase): """ - A test suite to test out various functions in the __init__ class. + A test suite to test out various functions in the openlp.core.common module. """ def de_hump_conversion_test(self): """ @@ -64,3 +65,19 @@ class TestInitFunctions(TestCase): # THEN: the new string should be converted to python format self.assertTrue(new_string == "my_class", 'The class name should have been preserved') + + def trace_error_handler_test(self): + """ + Test the trace_error_handler() method + """ + # GIVEN: Mocked out objects + with patch('openlp.core.common.traceback') as mocked_traceback: + mocked_traceback.extract_stack.return_value = [('openlp.fake', 56, None, 'trace_error_handler_test')] + mocked_logger = MagicMock() + + # WHEN: trace_error_handler() is called + trace_error_handler(mocked_logger) + + # THEN: The mocked_logger.error() method should have been called with the correct parameters + mocked_logger.error.assert_called_with( + 'OpenLP Error trace\n File openlp.fake at line 56 \n\t called trace_error_handler_test') diff --git a/tests/functional/openlp_core_common/test_registryproperties.py b/tests/functional/openlp_core_common/test_registryproperties.py index fa8a2b540..c2e430a95 100644 --- a/tests/functional/openlp_core_common/test_registryproperties.py +++ b/tests/functional/openlp_core_common/test_registryproperties.py @@ -52,7 +52,7 @@ class TestRegistryProperties(TestCase, RegistryProperties): # GIVEN an Empty Registry # WHEN there is no Application # THEN the application should be none - self.assertEquals(self.application, None, 'The application value should be None') + self.assertEqual(self.application, None, 'The application value should be None') def application_test(self): """ @@ -63,4 +63,4 @@ class TestRegistryProperties(TestCase, RegistryProperties): # WHEN the application is registered Registry().register('application', application) # THEN the application should be none - self.assertEquals(self.application, application, 'The application value should match') + self.assertEqual(self.application, application, 'The application value should match') diff --git a/tests/functional/openlp_core_lib/test_db.py b/tests/functional/openlp_core_lib/test_db.py index f11292209..071a3904b 100644 --- a/tests/functional/openlp_core_lib/test_db.py +++ b/tests/functional/openlp_core_lib/test_db.py @@ -50,8 +50,8 @@ class TestDB(TestCase): """ # GIVEN: Mocked out SQLAlchemy calls and return objects, and an in-memory SQLite database URL with patch('openlp.core.lib.db.create_engine') as mocked_create_engine, \ - patch('openlp.core.lib.db.MetaData') as MockedMetaData, \ - patch('openlp.core.lib.db.sessionmaker') as mocked_sessionmaker, \ + patch('openlp.core.lib.db.MetaData') as MockedMetaData, \ + patch('openlp.core.lib.db.sessionmaker') as mocked_sessionmaker, \ patch('openlp.core.lib.db.scoped_session') as mocked_scoped_session: mocked_engine = MagicMock() mocked_metadata = MagicMock() diff --git a/tests/functional/openlp_core_lib/test_file_dialog.py b/tests/functional/openlp_core_lib/test_file_dialog.py index b2c2178a9..ab7663a83 100644 --- a/tests/functional/openlp_core_lib/test_file_dialog.py +++ b/tests/functional/openlp_core_lib/test_file_dialog.py @@ -53,8 +53,8 @@ class TestFileDialog(TestCase): self.mocked_os.rest() self.mocked_qt_gui.reset() - # GIVEN: A List of known values as a return value from QFileDialog.getOpenFileNames and a list of valid - # file names. + # GIVEN: A List of known values as a return value from QFileDialog.getOpenFileNames and a list of valid file + # names. self.mocked_qt_gui.QFileDialog.getOpenFileNames.return_value = [ '/Valid File', '/url%20encoded%20file%20%231', '/non-existing'] self.mocked_os.path.exists.side_effect = lambda file_name: file_name in [ diff --git a/tests/functional/openlp_core_lib/test_image_manager.py b/tests/functional/openlp_core_lib/test_image_manager.py index e9968e1c7..37b6d6fdd 100644 --- a/tests/functional/openlp_core_lib/test_image_manager.py +++ b/tests/functional/openlp_core_lib/test_image_manager.py @@ -30,12 +30,16 @@ Package to test the openlp.core.ui package. """ import os +import time +from threading import Lock from unittest import TestCase from PyQt4 import QtGui from openlp.core.common import Registry from openlp.core.lib import ImageManager, ScreenList +from openlp.core.lib.imagemanager import Priority +from tests.functional import patch from tests.helpers.testmixin import TestMixin TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'resources')) @@ -51,6 +55,8 @@ class TestImageManager(TestCase, TestMixin): self.get_application() ScreenList.create(self.app.desktop()) self.image_manager = ImageManager() + self.lock = Lock() + self.sleep_time = 0.1 def tearDown(self): """ @@ -82,3 +88,87 @@ class TestImageManager(TestCase, TestMixin): with self.assertRaises(KeyError) as context: self.image_manager.get_image(TEST_PATH, 'church1.jpg') self.assertNotEquals(context.exception, '', 'KeyError exception should have been thrown for missing image') + + def process_cache_test(self): + """ + Test the process_cache method + """ + with patch('openlp.core.lib.imagemanager.resize_image') as mocked_resize_image, \ + patch('openlp.core.lib.imagemanager.image_to_byte') as mocked_image_to_byte: + # GIVEN: Mocked functions + mocked_resize_image.side_effect = self.mocked_resize_image + mocked_image_to_byte.side_effect = self.mocked_image_to_byte + image1 = 'church.jpg' + image2 = 'church2.jpg' + image3 = 'church3.jpg' + image4 = 'church4.jpg' + + # WHEN: Add the images. Then get the lock (=queue can not be processed). + self.lock.acquire() + self.image_manager.add_image(TEST_PATH, image1, None) + self.image_manager.add_image(TEST_PATH, image2, None) + + # THEN: All images have been added to the queue, and only the first image is not be in the list anymore, but + # is being processed (see mocked methods/functions). + # Note: Priority.Normal means, that the resize_image() was not completed yet (because afterwards the # + # priority is adjusted to Priority.Lowest). + self.assertEqual(self.get_image_priority(image1), Priority.Normal, + "image1's priority should be 'Priority.Normal'") + self.assertEqual(self.get_image_priority(image2), Priority.Normal, + "image2's priority should be 'Priority.Normal'") + + # WHEN: Add more images. + self.image_manager.add_image(TEST_PATH, image3, None) + self.image_manager.add_image(TEST_PATH, image4, None) + # Allow the queue to process. + self.lock.release() + # Request some "data". + image_bytes = self.image_manager.get_image_bytes(TEST_PATH, image4) + image_object = self.image_manager.get_image(TEST_PATH, image3) + # Now the mocked methods/functions do not have to sleep anymore. + self.sleep_time = 0 + # Wait for the queue to finish. + while not self.image_manager._conversion_queue.empty(): + time.sleep(0.1) + # Because empty() is not reliable, wait a litte; just to make sure. + time.sleep(0.1) + # THEN: The images' priority reflect how they were processed. + self.assertEqual(self.image_manager._conversion_queue.qsize(), 0, "The queue should be empty.") + self.assertEqual(self.get_image_priority(image1), Priority.Lowest, + "The image should have not been requested (=Lowest)") + self.assertEqual(self.get_image_priority(image2), Priority.Lowest, + "The image should have not been requested (=Lowest)") + self.assertEqual(self.get_image_priority(image3), Priority.Low, + "Only the QImage should have been requested (=Low).") + self.assertEqual(self.get_image_priority(image4), Priority.Urgent, + "The image bytes should have been requested (=Urgent).") + + def get_image_priority(self, image): + """ + This is a help method to get the priority of the given image out of the image_manager's cache. + + NOTE: This requires, that the image has been added to the image manager using the *TEST_PATH*. + + :param image: The name of the image. E. g. ``image1`` + """ + return self.image_manager._cache[(TEST_PATH, image)].priority + + def mocked_resize_image(self, *args): + """ + This is a mocked method, so that we can control the work flow of the image manager. + """ + self.lock.acquire() + self.lock.release() + # The sleep time is adjusted in the test case. + time.sleep(self.sleep_time) + return QtGui.QImage() + + def mocked_image_to_byte(self, *args): + """ + This is a mocked method, so that we can control the work flow of the image manager. + """ + self.lock.acquire() + self.lock.release() + # The sleep time is adjusted in the test case. + time.sleep(self.sleep_time) + return '' \ No newline at end of file diff --git a/tests/functional/openlp_core_lib/test_lib.py b/tests/functional/openlp_core_lib/test_lib.py index bb3a17ebb..b4334a728 100644 --- a/tests/functional/openlp_core_lib/test_lib.py +++ b/tests/functional/openlp_core_lib/test_lib.py @@ -482,7 +482,7 @@ class TestLib(TestCase): # WHEN: we run the validate_thumb() function # THEN: we should have called a few functions, and the result should be True - #mocked_os.path.exists.assert_called_with(thumb_path) + # mocked_os.path.exists.assert_called_with(thumb_path) def validate_thumb_file_exists_and_older_test(self): """ diff --git a/tests/functional/openlp_core_lib/test_pluginmanager.py b/tests/functional/openlp_core_lib/test_pluginmanager.py index ce5c240b2..d98a522b8 100644 --- a/tests/functional/openlp_core_lib/test_pluginmanager.py +++ b/tests/functional/openlp_core_lib/test_pluginmanager.py @@ -212,9 +212,9 @@ class TestPluginManager(TestCase): # WHEN: We run hook_export_menu() plugin_manager.hook_export_menu() - # THEN: The add_export_menu_Item() method should not have been called - self.assertEqual(0, mocked_plugin.add_export_menu_Item.call_count, - 'The add_export_menu_Item() method should not have been called.') + # THEN: The add_export_menu_item() method should not have been called + self.assertEqual(0, mocked_plugin.add_export_menu_item.call_count, + 'The add_export_menu_item() method should not have been called.') def hook_export_menu_with_active_plugin_test(self): """ @@ -229,8 +229,8 @@ class TestPluginManager(TestCase): # WHEN: We run hook_export_menu() plugin_manager.hook_export_menu() - # THEN: The add_export_menu_Item() method should have been called - mocked_plugin.add_export_menu_Item.assert_called_with(self.mocked_main_window.file_export_menu) + # THEN: The add_export_menu_item() method should have been called + mocked_plugin.add_export_menu_item.assert_called_with(self.mocked_main_window.file_export_menu) def hook_upgrade_plugin_settings_with_disabled_plugin_test(self): """ @@ -264,7 +264,7 @@ class TestPluginManager(TestCase): # WHEN: We run hook_upgrade_plugin_settings() plugin_manager.hook_upgrade_plugin_settings(settings) - # THEN: The add_export_menu_Item() method should have been called + # THEN: The add_export_menu_item() method should have been called mocked_plugin.upgrade_settings.assert_called_with(settings) def hook_tools_menu_with_disabled_plugin_test(self): diff --git a/tests/functional/openlp_core_lib/test_renderer.py b/tests/functional/openlp_core_lib/test_renderer.py index 51e915e3f..8814a21a0 100644 --- a/tests/functional/openlp_core_lib/test_renderer.py +++ b/tests/functional/openlp_core_lib/test_renderer.py @@ -49,7 +49,7 @@ class TestRenderer(TestCase): def setUp(self): """ - Set up the components need for all tests. + Set up the components need for all tests """ # Mocked out desktop object self.desktop = MagicMock() @@ -67,7 +67,7 @@ class TestRenderer(TestCase): def initial_renderer_test(self): """ - Test the initial renderer state . + Test the initial renderer state """ # GIVEN: A new renderer instance. renderer = Renderer() @@ -77,7 +77,7 @@ class TestRenderer(TestCase): def default_screen_layout_test(self): """ - Test the default layout calculations. + Test the default layout calculations """ # GIVEN: A new renderer instance. renderer = Renderer() @@ -87,3 +87,35 @@ class TestRenderer(TestCase): self.assertEqual(renderer.height, 768, 'The base renderer should be a live controller') self.assertEqual(renderer.screen_ratio, 0.75, 'The base renderer should be a live controller') self.assertEqual(renderer.footer_start, 691, 'The base renderer should be a live controller') + + def _get_start_tags_test(self): + """ + Test the _get_start_tags() method + """ + # GIVEN: A new renderer instance. Broken raw_text (missing closing tags). + renderer = Renderer() + given_raw_text = '{st}{r}Text text text' + expected_tuple = ('{st}{r}Text text text{/r}{/st}', '{st}{r}', + '') + + # WHEN: + result = renderer._get_start_tags(given_raw_text) + + # THEN: Check if the correct tuple is returned. + self.assertEqual(result, expected_tuple), 'A tuple should be returned containing the text with correct ' \ + 'tags, the opening tags, and the opening html tags.' + + def _word_split_test(self): + """ + Test the _word_split() method + """ + # GIVEN: A line of text + renderer = Renderer() + given_line = 'beginning asdf \n end asdf' + expected_words = ['beginning', 'asdf', 'end', 'asdf'] + + # WHEN: Split the line + result_words = renderer._words_split(given_line) + + # THEN: The word lists should be the same. + self.assertListEqual(result_words, expected_words) diff --git a/tests/functional/openlp_core_lib/test_ui.py b/tests/functional/openlp_core_lib/test_ui.py index babc94a81..025b1a638 100644 --- a/tests/functional/openlp_core_lib/test_ui.py +++ b/tests/functional/openlp_core_lib/test_ui.py @@ -81,3 +81,92 @@ class TestUi(TestCase): self.assertIsInstance(btnbox, QtGui.QDialogButtonBox) self.assertEqual(1, len(btnbox.buttons())) self.assertEqual(QtGui.QDialogButtonBox.HelpRole, btnbox.buttonRole(btnbox.buttons()[0])) + + def test_create_button(self): + """ + Test creating a button + """ + # GIVEN: A dialog + dialog = QtGui.QDialog() + + # WHEN: We create the button + btn = create_button(dialog, 'my_btn') + + # THEN: We should get a button with a name + self.assertIsInstance(btn, QtGui.QPushButton) + self.assertEqual('my_btn', btn.objectName()) + self.assertTrue(btn.isEnabled()) + + # WHEN: We create a button with some attributes + btn = create_button(dialog, 'my_btn', text='Hello', tooltip='How are you?', enabled=False) + + # THEN: We should get a button with those attributes + self.assertIsInstance(btn, QtGui.QPushButton) + self.assertEqual('Hello', btn.text()) + self.assertEqual('How are you?', btn.toolTip()) + self.assertFalse(btn.isEnabled()) + + # WHEN: We create a toolbutton + btn = create_button(dialog, 'my_btn', btn_class='toolbutton') + + # THEN: We should get a toolbutton + self.assertIsInstance(btn, QtGui.QToolButton) + self.assertEqual('my_btn', btn.objectName()) + self.assertTrue(btn.isEnabled()) + + def test_create_valign_selection_widgets(self): + """ + Test creating a combo box for valign selection + """ + # GIVEN: A dialog + dialog = QtGui.QDialog() + + # WHEN: We create the widgets + label, combo = create_valign_selection_widgets(dialog) + + # THEN: We should get a label and a combobox. + self.assertEqual(translate('OpenLP.Ui', '&Vertical Align:'), label.text()) + self.assertIsInstance(combo, QtGui.QComboBox) + self.assertEqual(combo, label.buddy()) + for text in [UiStrings().Top, UiStrings().Middle, UiStrings().Bottom]: + self.assertTrue(combo.findText(text) >= 0) + + def test_create_horizontal_adjusting_combo_box(self): + """ + Test creating a horizontal adjusting combo box + """ + # GIVEN: A dialog + dialog = QtGui.QDialog() + + # WHEN: We create the combobox + combo = create_horizontal_adjusting_combo_box(dialog, 'combo1') + + # THEN: We should get a ComboBox + self.assertIsInstance(combo, QtGui.QComboBox) + self.assertEqual('combo1', combo.objectName()) + self.assertEqual(QtGui.QComboBox.AdjustToMinimumContentsLength, combo.sizeAdjustPolicy()) + + def test_create_action(self): + """ + Test creating an action + """ + # GIVEN: A dialog + dialog = QtGui.QDialog() + + # WHEN: We create an action + action = create_action(dialog, 'my_action') + + # THEN: We should get a QAction + self.assertIsInstance(action, QtGui.QAction) + self.assertEqual('my_action', action.objectName()) + + # WHEN: We create an action with some properties + action = create_action(dialog, 'my_action', text='my text', icon=':/wizards/wizard_firsttime.bmp', + tooltip='my tooltip', statustip='my statustip') + + # THEN: These properties should be set + self.assertIsInstance(action, QtGui.QAction) + self.assertEqual('my text', action.text()) + self.assertIsInstance(action.icon(), QtGui.QIcon) + self.assertEqual('my tooltip', action.toolTip()) + self.assertEqual('my statustip', action.statusTip()) diff --git a/tests/functional/openlp_core_ui/test_firsttimeform.py b/tests/functional/openlp_core_ui/test_firsttimeform.py new file mode 100644 index 000000000..9fc6f5137 --- /dev/null +++ b/tests/functional/openlp_core_ui/test_firsttimeform.py @@ -0,0 +1,72 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2014 Raoul Snyman # +# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan # +# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, # +# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. # +# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, # +# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # +# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, # +# Frode Woldsund, Martin Zibricky, Patrick Zimmermann # +# --------------------------------------------------------------------------- # +# 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 # +############################################################################### +""" +Package to test the openlp.core.ui.firsttimeform package. +""" +from unittest import TestCase + +from tests.functional import MagicMock + +from tests.helpers.testmixin import TestMixin +from openlp.core.common import Registry +from openlp.core.ui.firsttimeform import FirstTimeForm + + +class TestFirstTimeForm(TestCase, TestMixin): + + def setUp(self): + screens = MagicMock() + self.get_application() + Registry.create() + Registry().register('application', self.app) + self.first_time_form = FirstTimeForm(screens) + + def test_access_to_config(self): + """ + Test if we can access the First Time Form's config file + """ + # GIVEN A new First Time Form instance. + + # WHEN The default First Time Form is built. + + # THEN The First Time Form web configuration file should be accessable. + self.assertTrue(self.first_time_form.web_access, + 'First Time Wizard\'s web configuration file should be available') + + def test_parsable_config(self): + """ + Test if the First Time Form's config file is parsable + """ + # GIVEN A new First Time Form instance. + + # WHEN The default First Time Form is built. + + # THEN The First Time Form web configuration file should be parsable + self.assertTrue(self.first_time_form.songs_url, + 'First Time Wizard\'s web configuration file should be parsable') diff --git a/tests/functional/openlp_core_ui/test_formattingtagsform.py b/tests/functional/openlp_core_ui/test_formattingtagsform.py index 05b5fed74..e71a75651 100644 --- a/tests/functional/openlp_core_ui/test_formattingtagsform.py +++ b/tests/functional/openlp_core_ui/test_formattingtagsform.py @@ -70,7 +70,7 @@ class TestFormattingTagForm(TestCase): form.save_button = MagicMock() # WHEN: on_text_edited is called with an arbitrary value - #form.on_text_edited('text') + # form.on_text_edited('text') # THEN: setEnabled and setDefault should have been called on save_push_button - #form.save_button.setEnabled.assert_called_with(True) + # form.save_button.setEnabled.assert_called_with(True) diff --git a/tests/functional/openlp_core_ui/test_maindisplay.py b/tests/functional/openlp_core_ui/test_maindisplay.py index dffc6ec16..6d67a3b67 100644 --- a/tests/functional/openlp_core_ui/test_maindisplay.py +++ b/tests/functional/openlp_core_ui/test_maindisplay.py @@ -79,3 +79,31 @@ class TestMainDisplay(TestCase): # THEN: The controller should not be a live controller. self.assertEqual(main_display.is_live, True, 'The main display should be a live controller') + + def set_transparency_test(self): + """ + Test creating an instance of the MainDisplay class + """ + # GIVEN: get an instance of MainDisplay + display = MagicMock() + main_display = MainDisplay(display) + + # WHEN: We enable transparency + main_display.set_transparency(True) + + # THEN: There should be a Stylesheet + self.assertEqual('QGraphicsView {background: transparent; border: 0px;}', main_display.styleSheet(), + 'MainDisplay instance should be transparent') + self.assertFalse(main_display.autoFillBackground(), + 'MainDisplay instance should be without background auto fill') + self.assertTrue(main_display.testAttribute(QtCore.Qt.WA_TranslucentBackground), + 'MainDisplay hasnt translucent background') + + # WHEN: We disable transparency + main_display.set_transparency(False) + + # THEN: The Stylesheet should be empty + self.assertEqual('QGraphicsView {}', main_display.styleSheet(), + 'MainDisplay instance should not be transparent') + self.assertFalse(main_display.testAttribute(QtCore.Qt.WA_TranslucentBackground), + 'MainDisplay hasnt translucent background') diff --git a/tests/functional/openlp_core_ui/test_mainwindow.py b/tests/functional/openlp_core_ui/test_mainwindow.py new file mode 100644 index 000000000..0b17828b9 --- /dev/null +++ b/tests/functional/openlp_core_ui/test_mainwindow.py @@ -0,0 +1,97 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2014 Raoul Snyman # +# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan # +# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, # +# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. # +# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, # +# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # +# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, # +# Frode Woldsund, Martin Zibricky, Patrick Zimmermann # +# --------------------------------------------------------------------------- # +# 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 # +############################################################################### +""" +Package to test openlp.core.ui.mainwindow package. +""" +import os + +from unittest import TestCase + +from openlp.core.ui.mainwindow import MainWindow +from openlp.core.common.registry import Registry +from tests.utils.constants import TEST_RESOURCES_PATH +from tests.helpers.testmixin import TestMixin +from tests.functional import MagicMock, patch + + +class TestMainWindow(TestCase, TestMixin): + + def setUp(self): + Registry.create() + self.registry = Registry() + self.get_application() + # Mock cursor busy/normal methods. + self.app.set_busy_cursor = MagicMock() + self.app.set_normal_cursor = MagicMock() + self.app.args = [] + Registry().register('application', self.app) + # Mock classes and methods used by mainwindow. + with patch('openlp.core.ui.mainwindow.SettingsForm') as mocked_settings_form, \ + patch('openlp.core.ui.mainwindow.ImageManager') as mocked_image_manager, \ + patch('openlp.core.ui.mainwindow.LiveController') as mocked_live_controller, \ + patch('openlp.core.ui.mainwindow.PreviewController') as mocked_preview_controller, \ + patch('openlp.core.ui.mainwindow.OpenLPDockWidget') as mocked_dock_widget, \ + patch('openlp.core.ui.mainwindow.QtGui.QToolBox') as mocked_q_tool_box_class, \ + patch('openlp.core.ui.mainwindow.QtGui.QMainWindow.addDockWidget') as mocked_add_dock_method, \ + patch('openlp.core.ui.mainwindow.ThemeManager') as mocked_theme_manager, \ + patch('openlp.core.ui.mainwindow.Renderer') as mocked_renderer: + self.main_window = MainWindow() + + def tearDown(self): + del self.main_window + + def cmd_line_file_test(self): + """ + Test that passing a service file from the command line loads the service. + """ + # GIVEN a service as an argument to openlp + service = os.path.join(TEST_RESOURCES_PATH, 'service', 'test.osz') + self.main_window.arguments = [service] + with patch('openlp.core.ui.servicemanager.ServiceManager.load_file') as mocked_load_path: + + # WHEN the argument is processed + self.main_window.open_cmd_line_files() + + # THEN the service from the arguments is loaded + mocked_load_path.assert_called_with(service), 'load_path should have been called with the service\'s path' + + def cmd_line_arg_test(self): + """ + Test that passing a non service file does nothing. + """ + # GIVEN a non service file as an argument to openlp + service = os.path.join('openlp.py') + self.main_window.arguments = [service] + with patch('openlp.core.ui.servicemanager.ServiceManager.load_file') as mocked_load_path: + + # WHEN the argument is processed + self.main_window.open_cmd_line_files() + + # THEN the file should not be opened + assert not mocked_load_path.called, 'load_path should not have been called' diff --git a/tests/functional/openlp_core_ui/test_media.py b/tests/functional/openlp_core_ui/test_media.py new file mode 100644 index 000000000..d59690949 --- /dev/null +++ b/tests/functional/openlp_core_ui/test_media.py @@ -0,0 +1,128 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2014 Raoul Snyman # +# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan # +# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, # +# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. # +# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, # +# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # +# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, # +# Frode Woldsund, Martin Zibricky, Patrick Zimmermann # +# --------------------------------------------------------------------------- # +# 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 # +############################################################################### +""" +Package to test the openlp.core.ui package. +""" +from PyQt4 import QtCore +from unittest import TestCase + +from openlp.core.ui.media import get_media_players + +from tests.functional import MagicMock, patch +from tests.helpers.testmixin import TestMixin + + +class TestMedia(TestCase, TestMixin): + + def setUp(self): + pass + + def test_get_media_players_no_config(self): + """ + Test that when there's no config, get_media_players() returns an empty list of players (not a string) + """ + def value_results(key): + if key == 'media/players': + return '' + else: + return False + + # GIVEN: A mocked out Settings() object + with patch('openlp.core.ui.media.Settings.value') as mocked_value: + mocked_value.side_effect = value_results + + # WHEN: get_media_players() is called + used_players, overridden_player = get_media_players() + + # THEN: the used_players should be an empty list, and the overridden player should be an empty string + self.assertEqual([], used_players, 'Used players should be an empty list') + self.assertEqual('', overridden_player, 'Overridden player should be an empty string') + + def test_get_media_players_no_players(self): + """ + Test that when there's no players but overridden player is set, get_media_players() returns 'auto' + """ + def value_results(key): + if key == 'media/override player': + return QtCore.Qt.Checked + else: + return '' + + # GIVEN: A mocked out Settings() object + with patch('openlp.core.ui.media.Settings.value') as mocked_value: + mocked_value.side_effect = value_results + + # WHEN: get_media_players() is called + used_players, overridden_player = get_media_players() + + # THEN: the used_players should be an empty list, and the overridden player should be an empty string + self.assertEqual([], used_players, 'Used players should be an empty list') + self.assertEqual('auto', overridden_player, 'Overridden player should be "auto"') + + def test_get_media_players_with_valid_list(self): + """ + Test that when get_media_players() is called the string list is interpreted correctly + """ + def value_results(key): + if key == 'media/players': + return '[vlc,webkit,phonon]' + else: + return False + + # GIVEN: A mocked out Settings() object + with patch('openlp.core.ui.media.Settings.value') as mocked_value: + mocked_value.side_effect = value_results + + # WHEN: get_media_players() is called + used_players, overridden_player = get_media_players() + + # THEN: the used_players should be an empty list, and the overridden player should be an empty string + self.assertEqual(['vlc', 'webkit', 'phonon'], used_players, 'Used players should be correct') + self.assertEqual('', overridden_player, 'Overridden player should be an empty string') + + def test_get_media_players_with_overridden_player(self): + """ + Test that when get_media_players() is called the overridden player is correctly set + """ + def value_results(key): + if key == 'media/players': + return '[vlc,webkit,phonon]' + else: + return QtCore.Qt.Checked + + # GIVEN: A mocked out Settings() object + with patch('openlp.core.ui.media.Settings.value') as mocked_value: + mocked_value.side_effect = value_results + + # WHEN: get_media_players() is called + used_players, overridden_player = get_media_players() + + # THEN: the used_players should be an empty list, and the overridden player should be an empty string + self.assertEqual(['vlc', 'webkit', 'phonon'], used_players, 'Used players should be correct') + self.assertEqual('vlc,webkit,phonon', overridden_player, 'Overridden player should be a string of players') \ No newline at end of file diff --git a/tests/functional/openlp_plugins/bibles/test_http.py b/tests/functional/openlp_plugins/bibles/test_http.py index b9bb8f11c..060c00d02 100644 --- a/tests/functional/openlp_plugins/bibles/test_http.py +++ b/tests/functional/openlp_plugins/bibles/test_http.py @@ -35,7 +35,7 @@ from bs4 import BeautifulSoup from tests.functional import patch, MagicMock from openlp.plugins.bibles.lib.http import BSExtract -#TODO: Items left to test +# TODO: Items left to test # BGExtract # __init__ # _remove_elements @@ -68,7 +68,7 @@ class TestBSExtract(TestCase): """ Test the BSExtractClass """ - #TODO: Items left to test + # TODO: Items left to test # BSExtract # __init__ # get_bible_chapter @@ -180,4 +180,4 @@ class TestBSExtract(TestCase): 'http://m.bibleserver.com/overlay/selectBook?translation=NIV') self.assertFalse(self.mock_log.error.called, 'log.error should not have been called') self.assertFalse(self.mock_send_error_message.called, 'send_error_message should not have been called') - self.assertEquals(result, ['Genesis', 'Leviticus']) + self.assertEqual(result, ['Genesis', 'Leviticus']) diff --git a/tests/functional/openlp_plugins/presentations/test_pptviewcontroller.py b/tests/functional/openlp_plugins/presentations/test_pptviewcontroller.py index 8a8897cec..c3d0912c0 100644 --- a/tests/functional/openlp_plugins/presentations/test_pptviewcontroller.py +++ b/tests/functional/openlp_plugins/presentations/test_pptviewcontroller.py @@ -47,7 +47,7 @@ class TestPptviewController(TestCase, TestMixin): """ Test the PptviewController Class """ -#TODO: Items left to test +# TODO: Items left to test # PptviewController # start_process(self) # kill @@ -108,7 +108,7 @@ class TestPptviewDocument(TestCase): """ Test the PptviewDocument Class """ - #TODO: Items left to test + # TODO: Items left to test # PptviewDocument # __init__ # create_thumbnails diff --git a/tests/functional/openlp_plugins/songs/test_ewimport.py b/tests/functional/openlp_plugins/songs/test_ewimport.py index 9e327517c..182a6b04a 100644 --- a/tests/functional/openlp_plugins/songs/test_ewimport.py +++ b/tests/functional/openlp_plugins/songs/test_ewimport.py @@ -139,10 +139,10 @@ class TestEasyWorshipSongImport(TestCase): # THEN: self.assertIsNotNone(field_desc_entry, 'Import should not be none') - self.assertEquals(field_desc_entry.name, name, 'FieldDescEntry.name should be the same as the name argument') - self.assertEquals(field_desc_entry.field_type, field_type, - 'FieldDescEntry.type should be the same as the type argument') - self.assertEquals(field_desc_entry.size, size, 'FieldDescEntry.size should be the same as the size argument') + self.assertEqual(field_desc_entry.name, name, 'FieldDescEntry.name should be the same as the name argument') + self.assertEqual(field_desc_entry.field_type, field_type, + 'FieldDescEntry.type should be the same as the type argument') + self.assertEqual(field_desc_entry.size, size, 'FieldDescEntry.size should be the same as the size argument') def create_importer_test(self): """ @@ -174,7 +174,7 @@ class TestEasyWorshipSongImport(TestCase): for field_name in existing_fields: # THEN: The item corresponding the index returned should have the same name attribute - self.assertEquals(importer.field_descriptions[importer.find_field(field_name)].name, field_name) + self.assertEqual(importer.field_descriptions[importer.find_field(field_name)].name, field_name) def find_non_existing_field_test(self): """ @@ -229,10 +229,10 @@ class TestEasyWorshipSongImport(TestCase): for field_index, result in field_results: return_value = importer.get_field(field_index) - # THEN: get_field should return the known results - self.assertEquals(return_value, result, - 'get_field should return "%s" when called with "%s"' % - (result, TEST_FIELDS[field_index])) + # THEN: get_field should return the known results + self.assertEqual(return_value, result, + 'get_field should return "%s" when called with "%s"' % + (result, TEST_FIELDS[field_index])) def get_memo_field_test(self): """ @@ -257,7 +257,7 @@ class TestEasyWorshipSongImport(TestCase): get_field_seek_calls = test_results[2]['seek'] # THEN: get_field should return the appropriate value with the appropriate mocked objects being called - self.assertEquals(importer.get_field(field_index), get_field_result) + self.assertEqual(importer.get_field(field_index), get_field_result) for call in get_field_read_calls: mocked_memo_file.read.assert_any_call(call) for call in get_field_seek_calls: @@ -403,11 +403,11 @@ class TestEasyWorshipSongImport(TestCase): if song_copyright: self.assertEqual(importer.copyright, song_copyright) if ccli_number: - self.assertEquals(importer.ccli_number, ccli_number, 'ccli_number for %s should be %s' - % (title, ccli_number)) + self.assertEqual(importer.ccli_number, ccli_number, + 'ccli_number for %s should be %s' % (title, ccli_number)) for verse_text, verse_tag in add_verse_calls: mocked_add_verse.assert_any_call(verse_text, verse_tag) if verse_order_list: - self.assertEquals(importer.verse_order_list, verse_order_list, - 'verse_order_list for %s should be %s' % (title, verse_order_list)) + self.assertEqual(importer.verse_order_list, verse_order_list, + 'verse_order_list for %s should be %s' % (title, verse_order_list)) mocked_finish.assert_called_with() diff --git a/tests/functional/openlp_plugins/songs/test_foilpresenterimport.py b/tests/functional/openlp_plugins/songs/test_foilpresenterimport.py index fbd339cf3..61206b9fa 100644 --- a/tests/functional/openlp_plugins/songs/test_foilpresenterimport.py +++ b/tests/functional/openlp_plugins/songs/test_foilpresenterimport.py @@ -44,7 +44,7 @@ class TestFoilPresenter(TestCase): """ Test the functions in the :mod:`foilpresenterimport` module. """ - #TODO: The following modules still need tests written for + # TODO: The following modules still need tests written for # xml_to_song # _child # _process_authors diff --git a/tests/functional/openlp_plugins/songs/test_lib.py b/tests/functional/openlp_plugins/songs/test_lib.py index f6e5d98b9..b67c1a4be 100644 --- a/tests/functional/openlp_plugins/songs/test_lib.py +++ b/tests/functional/openlp_plugins/songs/test_lib.py @@ -96,10 +96,10 @@ class TestLib(TestCase): self.song2.search_lyrics = self.full_lyrics # WHEN: We compare those songs for equality. - result = songs_probably_equal(self.song1, self.song2) + result = songs_probably_equal((self.song1, self.song2)) - # THEN: The result should be True. - assert result is True, 'The result should be True' + # THEN: The result should be a tuple.. + assert result == (self.song1, self.song2), 'The result should be the tuble of songs' def songs_probably_equal_short_song_test(self): """ @@ -110,10 +110,10 @@ class TestLib(TestCase): self.song2.search_lyrics = self.short_lyrics # WHEN: We compare those songs for equality. - result = songs_probably_equal(self.song1, self.song2) + result = songs_probably_equal((self.song1, self.song2)) - # THEN: The result should be True. - assert result is True, 'The result should be True' + # THEN: The result should be a tuple.. + assert result == (self.song1, self.song2), 'The result should be the tuble of songs' def songs_probably_equal_error_song_test(self): """ @@ -124,10 +124,10 @@ class TestLib(TestCase): self.song2.search_lyrics = self.error_lyrics # WHEN: We compare those songs for equality. - result = songs_probably_equal(self.song1, self.song2) + result = songs_probably_equal((self.song1, self.song2)) - # THEN: The result should be True. - assert result is True, 'The result should be True' + # THEN: The result should be a tuple of songs.. + assert result == (self.song1, self.song2), 'The result should be the tuble of songs' def songs_probably_equal_different_song_test(self): """ @@ -138,10 +138,10 @@ class TestLib(TestCase): self.song2.search_lyrics = self.different_lyrics # WHEN: We compare those songs for equality. - result = songs_probably_equal(self.song1, self.song2) + result = songs_probably_equal((self.song1, self.song2)) - # THEN: The result should be False. - assert result is False, 'The result should be False' + # THEN: The result should be None. + assert result is None, 'The result should be None' def remove_typos_beginning_test(self): """ diff --git a/tests/functional/openlp_plugins/songs/test_mediaitem.py b/tests/functional/openlp_plugins/songs/test_mediaitem.py index 2b5f02483..308881c2e 100644 --- a/tests/functional/openlp_plugins/songs/test_mediaitem.py +++ b/tests/functional/openlp_plugins/songs/test_mediaitem.py @@ -10,6 +10,7 @@ from PyQt4 import QtCore, QtGui from openlp.core.common import Registry, Settings from openlp.core.lib import ServiceItem from openlp.plugins.songs.lib.mediaitem import SongMediaItem +from openlp.plugins.songs.lib.db import AuthorType from tests.functional import patch, MagicMock from tests.helpers.testmixin import TestMixin @@ -45,10 +46,12 @@ class TestMediaItem(TestCase, TestMixin): # GIVEN: A Song and a Service Item mock_song = MagicMock() mock_song.title = 'My Song' + mock_song.authors_songs = [] mock_author = MagicMock() mock_author.display_name = 'my author' - mock_song.authors = [] - mock_song.authors.append(mock_author) + mock_author_song = MagicMock() + mock_author_song.author = mock_author + mock_song.authors_songs.append(mock_author_song) mock_song.copyright = 'My copyright' service_item = ServiceItem(None) @@ -56,7 +59,7 @@ class TestMediaItem(TestCase, TestMixin): author_list = self.media_item.generate_footer(service_item, mock_song) # THEN: I get the following Array returned - self.assertEqual(service_item.raw_footer, ['My Song', 'my author', 'My copyright'], + self.assertEqual(service_item.raw_footer, ['My Song', 'Written by: my author', 'My copyright'], 'The array should be returned correctly with a song, one author and copyright') self.assertEqual(author_list, ['my author'], 'The author list should be returned correctly with one author') @@ -68,13 +71,25 @@ class TestMediaItem(TestCase, TestMixin): # GIVEN: A Song and a Service Item mock_song = MagicMock() mock_song.title = 'My Song' + mock_song.authors_songs = [] mock_author = MagicMock() mock_author.display_name = 'my author' - mock_song.authors = [] - mock_song.authors.append(mock_author) + mock_author_song = MagicMock() + mock_author_song.author = mock_author + mock_author_song.author_type = AuthorType.Music + mock_song.authors_songs.append(mock_author_song) mock_author = MagicMock() mock_author.display_name = 'another author' - mock_song.authors.append(mock_author) + mock_author_song = MagicMock() + mock_author_song.author = mock_author + mock_author_song.author_type = AuthorType.Words + mock_song.authors_songs.append(mock_author_song) + mock_author = MagicMock() + mock_author.display_name = 'translator' + mock_author_song = MagicMock() + mock_author_song.author = mock_author + mock_author_song.author_type = AuthorType.Translation + mock_song.authors_songs.append(mock_author_song) mock_song.copyright = 'My copyright' service_item = ServiceItem(None) @@ -82,22 +97,19 @@ class TestMediaItem(TestCase, TestMixin): author_list = self.media_item.generate_footer(service_item, mock_song) # THEN: I get the following Array returned - self.assertEqual(service_item.raw_footer, ['My Song', 'my author and another author', 'My copyright'], + self.assertEqual(service_item.raw_footer, ['My Song', 'Words: another author', 'Music: my author', + 'Translation: translator', 'My copyright'], 'The array should be returned correctly with a song, two authors and copyright') - self.assertEqual(author_list, ['my author', 'another author'], + self.assertEqual(author_list, ['another author', 'my author', 'translator'], 'The author list should be returned correctly with two authors') def build_song_footer_base_ccli_test(self): """ - Test build songs footer with basic song and two authors + Test build songs footer with basic song and a CCLI number """ # GIVEN: A Song and a Service Item and a configured CCLI license mock_song = MagicMock() mock_song.title = 'My Song' - mock_author = MagicMock() - mock_author.display_name = 'my author' - mock_song.authors = [] - mock_song.authors.append(mock_author) mock_song.copyright = 'My copyright' service_item = ServiceItem(None) Settings().setValue('core/ccli number', '1234') @@ -106,7 +118,7 @@ class TestMediaItem(TestCase, TestMixin): self.media_item.generate_footer(service_item, mock_song) # THEN: I get the following Array returned - self.assertEqual(service_item.raw_footer, ['My Song', 'my author', 'My copyright', 'CCLI License: 1234'], + self.assertEqual(service_item.raw_footer, ['My Song', 'My copyright', 'CCLI License: 1234'], 'The array should be returned correctly with a song, an author, copyright and ccli') # WHEN: I amend the CCLI value @@ -114,5 +126,5 @@ class TestMediaItem(TestCase, TestMixin): self.media_item.generate_footer(service_item, mock_song) # THEN: I would get an amended footer string - self.assertEqual(service_item.raw_footer, ['My Song', 'my author', 'My copyright', 'CCLI License: 4321'], + self.assertEqual(service_item.raw_footer, ['My Song', 'My copyright', 'CCLI License: 4321'], 'The array should be returned correctly with a song, an author, copyright and amended ccli') diff --git a/tests/functional/openlp_plugins/songs/test_openlyricsimport.py b/tests/functional/openlp_plugins/songs/test_openlyricsimport.py new file mode 100644 index 000000000..93ecafb78 --- /dev/null +++ b/tests/functional/openlp_plugins/songs/test_openlyricsimport.py @@ -0,0 +1,99 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2014 Raoul Snyman # +# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan # +# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, # +# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. # +# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, # +# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # +# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, # +# Frode Woldsund, Martin Zibricky, Patrick Zimmermann # +# --------------------------------------------------------------------------- # +# 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 OpenLyrics song importer. +""" + +import os +from unittest import TestCase + +from tests.functional import MagicMock, patch +from openlp.plugins.songs.lib.openlyricsimport import OpenLyricsImport +from openlp.plugins.songs.lib.songimport import SongImport + +TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), + '..', '..', '..', 'resources', 'openlyricssongs')) +SONG_TEST_DATA = { + 'What a friend we have in Jesus.xml': { + 'title': 'What A Friend We Have In Jesus', + 'verses': [ + ('What a friend we have in Jesus, All ours sins and griefs to bear;\n\ + What a privilege to carry, Everything to God in prayer!\n\ + O what peace we often forfeit, O what needless pain we bear;\n\ + All because we do not carry, Everything to God in prayer!', 'v1'), + ('Have we trials and temptations? Is there trouble anywhere?\n\ + We should never be discouraged, Take it to the Lord in prayer.\n\ + Can we find a friend so faithful? Who will all our sorrows share?\n\ + Jesus knows our every weakness; Take it to the Lord in prayer.', 'v2'), + ('Are we weak and heavy laden, Cumbered with a load of care?\n\ + Precious Saviour still our refuge; Take it to the Lord in prayer.\n\ + Do thy friends despise forsake thee? Take it to the Lord in prayer!\n\ + In His arms He’ll take and shield thee; Thou wilt find a solace there.', 'v3') + ] + } +} + + +class TestOpenLyricsImport(TestCase): + """ + Test the functions in the :mod:`openlyricsimport` module. + """ + def create_importer_test(self): + """ + Test creating an instance of the OpenLyrics file importer + """ + # GIVEN: A mocked out SongImport class, and a mocked out "manager" + with patch('openlp.plugins.songs.lib.songbeamerimport.SongImport'): + mocked_manager = MagicMock() + + # WHEN: An importer object is created + importer = OpenLyricsImport(mocked_manager, filenames=[]) + + # THEN: The importer should be an instance of SongImport + self.assertIsInstance(importer, SongImport) + + def file_import_test(self): + """ + Test the actual import of real song files + """ + # GIVEN: Test files with a mocked out "manager" and a mocked out "import_wizard" + for song_file in SONG_TEST_DATA: + mocked_manager = MagicMock() + mocked_import_wizard = MagicMock() + importer = OpenLyricsImport(mocked_manager, filenames=[]) + importer.import_wizard = mocked_import_wizard + importer.open_lyrics = MagicMock() + importer.open_lyrics.xml_to_song = MagicMock() + + # WHEN: Importing each file + importer.import_source = [os.path.join(TEST_PATH, song_file)] + importer.do_import() + + # THEN: The xml_to_song() method should have been called + self.assertTrue(importer.open_lyrics.xml_to_song.called) diff --git a/tests/functional/openlp_plugins/songs/test_songbeamerimport.py b/tests/functional/openlp_plugins/songs/test_songbeamerimport.py index e7bd891d3..a69d4a86c 100644 --- a/tests/functional/openlp_plugins/songs/test_songbeamerimport.py +++ b/tests/functional/openlp_plugins/songs/test_songbeamerimport.py @@ -35,6 +35,7 @@ from unittest import TestCase from tests.functional import MagicMock, patch from openlp.plugins.songs.lib.songbeamerimport import SongBeamerImport +from openlp.plugins.songs.lib import VerseType TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..', 'resources', 'songbeamersongs')) @@ -48,7 +49,8 @@ SONG_TEST_DATA = { ('4. Lobsingt seiner Treu´,\ndie immerdar neu,\nbis Er uns zur Herrlichket führet!\n\n', 'v') ], 'song_book_name': 'Glaubenslieder I', - 'song_number': "1" + 'song_number': "1", + 'authors': ['Carl Brockhaus', 'Johann Jakob Vetter'] } } @@ -89,8 +91,8 @@ class TestSongBeamerImport(TestCase): # THEN: do_import should return none and the progress bar maximum should not be set. self.assertIsNone(importer.do_import(), 'do_import should return None when import_source is not a list') - self.assertEquals(mocked_import_wizard.progress_bar.setMaximum.called, False, - 'setMaxium on import_wizard.progress_bar should not have been called') + self.assertEqual(mocked_import_wizard.progress_bar.setMaximum.called, False, + 'setMaxium on import_wizard.progress_bar should not have been called') def valid_import_source_test(self): """ @@ -139,17 +141,98 @@ class TestSongBeamerImport(TestCase): add_verse_calls = SONG_TEST_DATA[song_file]['verses'] song_book_name = SONG_TEST_DATA[song_file]['song_book_name'] song_number = SONG_TEST_DATA[song_file]['song_number'] + song_authors = SONG_TEST_DATA[song_file]['authors'] # THEN: do_import should return none, the song data should be as expected, and finish should have been # called. self.assertIsNone(importer.do_import(), 'do_import should return None when it has completed') - self.assertEquals(importer.title, title, 'title for %s should be "%s"' % (song_file, title)) + self.assertEqual(importer.title, title, 'title for %s should be "%s"' % (song_file, title)) for verse_text, verse_tag in add_verse_calls: mocked_add_verse.assert_any_call(verse_text, verse_tag) if song_book_name: - self.assertEquals(importer.song_book_name, song_book_name, 'song_book_name for %s should be "%s"' % - (song_file, song_book_name)) + self.assertEqual(importer.song_book_name, song_book_name, + 'song_book_name for %s should be "%s"' % (song_file, song_book_name)) if song_number: - self.assertEquals(importer.song_number, song_number, 'song_number for %s should be %s' % - (song_file, song_number)) + self.assertEqual(importer.song_number, song_number, + 'song_number for %s should be %s' % (song_file, song_number)) + if song_authors: + for author in importer.authors: + self.assertIn(author, song_authors) mocked_finish.assert_called_with() + + def check_verse_marks_test(self): + """ + Tests different lines to see if a verse mark is detected or not + """ + + # GIVEN: line with unnumbered verse-type + line = 'Refrain' + self.current_verse_type = None + # WHEN: line is being checked for verse marks + result = SongBeamerImport.check_verse_marks(self, line) + # THEN: we should get back true and c as self.current_verse_type + self.assertTrue(result, 'Versemark for should be found, value true') + self.assertEqual(self.current_verse_type, 'c', ' should be interpreted as ') + + # GIVEN: line with unnumbered verse-type and trailing space + line = 'Refrain ' + self.current_verse_type = None + # WHEN: line is being checked for verse marks + result = SongBeamerImport.check_verse_marks(self, line) + # THEN: we should get back true and c as self.current_verse_type + self.assertTrue(result, 'Versemark for should be found, value true') + self.assertEqual(self.current_verse_type, 'c', ' should be interpreted as ') + + # GIVEN: line with numbered verse-type + line = 'Verse 1' + self.current_verse_type = None + # WHEN: line is being checked for verse marks + result = SongBeamerImport.check_verse_marks(self, line) + # THEN: we should get back true and v1 as self.current_verse_type + self.assertTrue(result, 'Versemark for should be found, value true') + self.assertEqual(self.current_verse_type, 'v1', u' should be interpreted as ') + + # GIVEN: line with special unnumbered verse-mark (used in Songbeamer to allow usage of non-supported tags) + line = '$$M=special' + self.current_verse_type = None + # WHEN: line is being checked for verse marks + result = SongBeamerImport.check_verse_marks(self, line) + # THEN: we should get back true and o as self.current_verse_type + self.assertTrue(result, 'Versemark for <$$M=special> should be found, value true') + self.assertEqual(self.current_verse_type, 'o', u'<$$M=special> should be interpreted as ') + + # GIVEN: line with song-text with 3 words + line = 'Jesus my saviour' + self.current_verse_type = None + # WHEN: line is being checked for verse marks + result = SongBeamerImport.check_verse_marks(self, line) + # THEN: we should get back false and none as self.current_verse_type + self.assertFalse(result, 'No versemark for should be found, value false') + self.assertIsNone(self.current_verse_type, ' should be interpreted as none versemark') + + # GIVEN: line with song-text with 2 words + line = 'Praise him' + self.current_verse_type = None + # WHEN: line is being checked for verse marks + result = SongBeamerImport.check_verse_marks(self, line) + # THEN: we should get back false and none as self.current_verse_type + self.assertFalse(result, 'No versemark for should be found, value false') + self.assertIsNone(self.current_verse_type, ' should be interpreted as none versemark') + + # GIVEN: line with only a space (could occur, nothing regular) + line = ' ' + self.current_verse_type = None + # WHEN: line is being checked for verse marks + result = SongBeamerImport.check_verse_marks(self, line) + # THEN: we should get back false and none as self.current_verse_type + self.assertFalse(result, 'No versemark for < > should be found, value false') + self.assertIsNone(self.current_verse_type, '< > should be interpreted as none versemark') + + # GIVEN: blank line (could occur, nothing regular) + line = '' + self.current_verse_type = None + # WHEN: line is being checked for verse marks + result = SongBeamerImport.check_verse_marks(self, line) + # THEN: we should get back false and none as self.current_verse_type + self.assertFalse(result, 'No versemark for <> should be found, value false') + self.assertIsNone(self.current_verse_type, '<> should be interpreted as none versemark') diff --git a/tests/functional/openlp_plugins/songs/test_songshowplusimport.py b/tests/functional/openlp_plugins/songs/test_songshowplusimport.py index 7876558e9..7292bb2b0 100644 --- a/tests/functional/openlp_plugins/songs/test_songshowplusimport.py +++ b/tests/functional/openlp_plugins/songs/test_songshowplusimport.py @@ -95,8 +95,8 @@ class TestSongShowPlusImport(TestCase): # THEN: do_import should return none and the progress bar maximum should not be set. self.assertIsNone(importer.do_import(), 'do_import should return None when import_source is not a list') - self.assertEquals(mocked_import_wizard.progress_bar.setMaximum.called, False, - 'setMaximum on import_wizard.progress_bar should not have been called') + self.assertEqual(mocked_import_wizard.progress_bar.setMaximum.called, False, + 'setMaximum on import_wizard.progress_bar should not have been called') def valid_import_source_test(self): """ @@ -116,7 +116,7 @@ class TestSongShowPlusImport(TestCase): # THEN: do_import should return none and the progress bar setMaximum should be called with the length of # import_source. self.assertIsNone(importer.do_import(), 'do_import should return None when import_source is a list ' - 'and stop_import_flag is True') + 'and stop_import_flag is True') mocked_import_wizard.progress_bar.setMaximum.assert_called_with(len(importer.import_source)) def to_openlp_verse_tag_test(self): @@ -143,9 +143,9 @@ class TestSongShowPlusImport(TestCase): # THEN: The returned value should should correlate with the input arguments for original_tag, openlp_tag in test_values: - self.assertEquals(importer.to_openlp_verse_tag(original_tag), openlp_tag, - 'SongShowPlusImport.to_openlp_verse_tag should return "%s" when called with "%s"' % - (openlp_tag, original_tag)) + self.assertEqual(importer.to_openlp_verse_tag(original_tag), openlp_tag, + 'SongShowPlusImport.to_openlp_verse_tag should return "%s" when called with "%s"' % + (openlp_tag, original_tag)) def to_openlp_verse_tag_verse_order_test(self): """ @@ -172,6 +172,6 @@ class TestSongShowPlusImport(TestCase): # THEN: The returned value should should correlate with the input arguments for original_tag, openlp_tag in test_values: - self.assertEquals(importer.to_openlp_verse_tag(original_tag, ignore_unique=True), openlp_tag, - 'SongShowPlusImport.to_openlp_verse_tag should return "%s" when called with "%s"' % - (openlp_tag, original_tag)) + self.assertEqual(importer.to_openlp_verse_tag(original_tag, ignore_unique=True), openlp_tag, + 'SongShowPlusImport.to_openlp_verse_tag should return "%s" when called with "%s"' % + (openlp_tag, original_tag)) diff --git a/tests/functional/openlp_plugins/songs/test_zionworximport.py b/tests/functional/openlp_plugins/songs/test_zionworximport.py new file mode 100644 index 000000000..2edc071c7 --- /dev/null +++ b/tests/functional/openlp_plugins/songs/test_zionworximport.py @@ -0,0 +1,56 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2014 Raoul Snyman # +# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan # +# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, # +# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. # +# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, # +# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # +# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, # +# Frode Woldsund, Martin Zibricky, Patrick Zimmermann # +# --------------------------------------------------------------------------- # +# 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 ZionWorx song importer. +""" + +from unittest import TestCase + +from tests.functional import MagicMock, patch +from openlp.plugins.songs.lib.zionworximport import ZionWorxImport +from openlp.plugins.songs.lib.songimport import SongImport + + +class TestZionWorxImport(TestCase): + """ + Test the functions in the :mod:`zionworximport` module. + """ + def create_importer_test(self): + """ + Test creating an instance of the ZionWorx file importer + """ + # GIVEN: A mocked out SongImport class, and a mocked out "manager" + with patch('openlp.plugins.songs.lib.songbeamerimport.SongImport'): + mocked_manager = MagicMock() + + # WHEN: An importer object is created + importer = ZionWorxImport(mocked_manager, filenames=[]) + + # THEN: The importer should be an instance of SongImport + self.assertIsInstance(importer, SongImport) diff --git a/tests/functional/test_init.py b/tests/functional/test_init.py new file mode 100644 index 000000000..6fc7d7f70 --- /dev/null +++ b/tests/functional/test_init.py @@ -0,0 +1,70 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2014 Raoul Snyman # +# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan # +# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, # +# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. # +# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, # +# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # +# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, # +# Frode Woldsund, Martin Zibricky, Patrick Zimmermann # +# --------------------------------------------------------------------------- # +# 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 # +############################################################################### +""" +Package to test the openlp.core.__init__ package. +""" +import os + +from unittest import TestCase +from unittest.mock import MagicMock, patch +from PyQt4 import QtCore + +from openlp.core import OpenLP +from tests.helpers.testmixin import TestMixin + + +TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'resources')) + + +class TestInit(TestCase, TestMixin): + def setUp(self): + with patch('openlp.core.common.OpenLPMixin.__init__') as constructor: + constructor.return_value = None + self.openlp = OpenLP(list()) + + def tearDown(self): + del self.openlp + + def event_test(self): + """ + Test the reimplemented event method + """ + # GIVEN: A file path and a QEvent. + file_path = os.path.join(TEST_PATH, 'church.jpg') + mocked_file_method = MagicMock(return_value=file_path) + event = QtCore.QEvent(QtCore.QEvent.FileOpen) + event.file = mocked_file_method + + # WHEN: Call the vent method. + result = self.openlp.event(event) + + # THEN: The path should be inserted. + self.assertTrue(result, "The method should have returned True.") + mocked_file_method.assert_called_once_with() + self.assertEqual(self.openlp.args[0], file_path, "The path should be in args.") diff --git a/tests/helpers/songfileimport.py b/tests/helpers/songfileimport.py index cc67770c1..5364c2c3b 100644 --- a/tests/helpers/songfileimport.py +++ b/tests/helpers/songfileimport.py @@ -110,30 +110,30 @@ class SongImportTestHelper(TestCase): # THEN: do_import should return none, the song data should be as expected, and finish should have been # called. self.assertIsNone(importer.do_import(), 'do_import should return None when it has completed') - self.assertEquals(importer.title, title, 'title for %s should be "%s"' % (source_file_name, title)) + self.assertEqual(importer.title, title, 'title for %s should be "%s"' % (source_file_name, title)) for author in author_calls: self.mocked_parse_author.assert_any_call(author) if song_copyright: self.mocked_add_copyright.assert_called_with(song_copyright) if ccli_number: - self.assertEquals(importer.ccli_number, ccli_number, 'ccli_number for %s should be %s' % - (source_file_name, ccli_number)) + self.assertEqual(importer.ccli_number, ccli_number, + 'ccli_number for %s should be %s' % (source_file_name, ccli_number)) for verse_text, verse_tag in add_verse_calls: self.mocked_add_verse.assert_any_call(verse_text, verse_tag) if topics: - self.assertEquals(importer.topics, topics, 'topics for %s should be %s' % (source_file_name, topics)) + self.assertEqual(importer.topics, topics, 'topics for %s should be %s' % (source_file_name, topics)) if comments: - self.assertEquals(importer.comments, comments, 'comments for %s should be "%s"' % - (source_file_name, comments)) + self.assertEqual(importer.comments, comments, + 'comments for %s should be "%s"' % (source_file_name, comments)) if song_book_name: - self.assertEquals(importer.song_book_name, song_book_name, 'song_book_name for %s should be "%s"' % - (source_file_name, song_book_name)) + self.assertEqual(importer.song_book_name, song_book_name, + 'song_book_name for %s should be "%s"' % (source_file_name, song_book_name)) if song_number: - self.assertEquals(importer.song_number, song_number, 'song_number for %s should be %s' % - (source_file_name, song_number)) + self.assertEqual(importer.song_number, song_number, + 'song_number for %s should be %s' % (source_file_name, song_number)) if verse_order_list: - self.assertEquals(importer.verse_order_list, [], 'verse_order_list for %s should be %s' % - (source_file_name, verse_order_list)) + self.assertEqual(importer.verse_order_list, [], + 'verse_order_list for %s should be %s' % (source_file_name, verse_order_list)) self.mocked_finish.assert_called_with() def _get_data(self, data, key): diff --git a/tests/interfaces/openlp_core_ui/test_servicemanager.py b/tests/interfaces/openlp_core_ui/test_servicemanager.py index f4f5f26fc..78df788ab 100644 --- a/tests/interfaces/openlp_core_ui/test_servicemanager.py +++ b/tests/interfaces/openlp_core_ui/test_servicemanager.py @@ -313,6 +313,7 @@ class TestServiceManager(TestCase, TestMixin): self.service_manager.notes_action.setVisible = MagicMock() self.service_manager.time_action.setVisible = MagicMock() self.service_manager.auto_start_action.setVisible = MagicMock() + self.service_manager.rename_action.setVisible = MagicMock() # WHEN: Show the context menu. self.service_manager.context_menu(q_point) @@ -329,6 +330,8 @@ class TestServiceManager(TestCase, TestMixin): 'The action should be set invisible.' self.service_manager.auto_start_action.setVisible.assert_called_with(True), \ 'The action should be set visible.' + self.service_manager.rename_action.setVisible.assert_called_once_with(False), \ + 'The action should be set invisible.' def click_on_new_service_test(self): """ diff --git a/tests/interfaces/openlp_core_ui/test_splashscreen.py b/tests/interfaces/openlp_core_ui/test_splashscreen.py new file mode 100644 index 000000000..35c15f9ec --- /dev/null +++ b/tests/interfaces/openlp_core_ui/test_splashscreen.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2014 Raoul Snyman # +# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan # +# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, # +# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. # +# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, # +# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # +# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, # +# Frode Woldsund, Martin Zibricky, Patrick Zimmermann # +# --------------------------------------------------------------------------- # +# 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 # +############################################################################### +""" +Test the openlp.core.ui.splashscreen class. +""" +from unittest import TestCase + +from PyQt4 import QtGui + +from openlp.core.ui import SplashScreen +from tests.helpers.testmixin import TestMixin + + +class TestSplashScreen(TestCase, TestMixin): + def setUp(self): + self.get_application() + self.main_window = QtGui.QMainWindow() + + def tearDown(self): + """ + Delete all the C++ objects at the end so that we don't have a segfault + """ + del self.app + del self.main_window + + def setupUi_test(self): + """ + Test if the setupUi method.... + """ + # GIVEN: A splash screen instance. + splash = SplashScreen() + + # THEN: Check if the splash has a setupUi method. + assert hasattr(splash, 'setupUi'), 'The Splash Screen should have a setupUi() method.' diff --git a/tests/interfaces/openlp_plugins/bibles/test_lib_parse_reference.py b/tests/interfaces/openlp_plugins/bibles/test_lib_parse_reference.py index b085bd1df..e20105ea1 100644 --- a/tests/interfaces/openlp_plugins/bibles/test_lib_parse_reference.py +++ b/tests/interfaces/openlp_plugins/bibles/test_lib_parse_reference.py @@ -83,7 +83,7 @@ class TestBibleManager(TestCase, TestMixin): # WHEN asking to parse the bible reference results = parse_reference('1 Timothy 1', self.manager.db_cache['tests'], MagicMock(), 54) # THEN a verse array should be returned - self.assertEquals([(54, 1, 1, -1)], results, "The bible verses should matches the expected results") + self.assertEqual([(54, 1, 1, -1)], results, "The bible verses should matches the expected results") def parse_reference_two_test(self): """ @@ -93,7 +93,7 @@ class TestBibleManager(TestCase, TestMixin): # WHEN asking to parse the bible reference results = parse_reference('1 Timothy 1:1-2', self.manager.db_cache['tests'], MagicMock(), 54) # THEN a verse array should be returned - self.assertEquals([(54, 1, 1, 2)], results, "The bible verses should matches the expected results") + self.assertEqual([(54, 1, 1, 2)], results, "The bible verses should matches the expected results") def parse_reference_three_test(self): """ @@ -103,5 +103,5 @@ class TestBibleManager(TestCase, TestMixin): # WHEN asking to parse the bible reference results = parse_reference('1 Timothy 1:1-2:1', self.manager.db_cache['tests'], MagicMock(), 54) # THEN a verse array should be returned - self.assertEquals([(54, 1, 1, -1), (54, 2, 1, 1)], results, "The bible verses should matches the expected " - "results") + self.assertEqual([(54, 1, 1, -1), (54, 2, 1, 1)], results, + "The bible verses should match the expected results") diff --git a/tests/resources/church2.jpg b/tests/resources/church2.jpg new file mode 100644 index 000000000..826180870 Binary files /dev/null and b/tests/resources/church2.jpg differ diff --git a/tests/resources/church3.jpg b/tests/resources/church3.jpg new file mode 100644 index 000000000..826180870 Binary files /dev/null and b/tests/resources/church3.jpg differ diff --git a/tests/resources/church4.jpg b/tests/resources/church4.jpg new file mode 100644 index 000000000..826180870 Binary files /dev/null and b/tests/resources/church4.jpg differ diff --git a/tests/resources/openlyricssongs/What a friend we have in Jesus.xml b/tests/resources/openlyricssongs/What a friend we have in Jesus.xml new file mode 100644 index 000000000..a111c853c --- /dev/null +++ b/tests/resources/openlyricssongs/What a friend we have in Jesus.xml @@ -0,0 +1,44 @@ + + + + + What A Friend We Have In Jesus + + + Joseph M. Scriven + Charles C. Convers + + Public Domain + 27714 + + Christ: Love/Mercy + Fruit: Peace/Comfort + + + + + + What a friend we have in Jesus, All ours sins and griefs to bear;
+ What a privilege to carry, Everything to God in prayer!
+ O what peace we often forfeit, O what needless pain we bear;
+ All because we do not carry, Everything to God in prayer! +
+
+ + + Have we trials and temptations? Is there trouble anywhere?
+ We should never be discouraged, Take it to the Lord in prayer.
+ Can we find a friend so faithful? Who will all our sorrows share?
+ Jesus knows our every weakness; Take it to the Lord in prayer. +
+
+ + + Are we weak and heavy laden, Cumbered with a load of care?
+ Precious Saviour still our refuge; Take it to the Lord in prayer.
+ Do thy friends despise forsake thee? Take it to the Lord in prayer!
+ In His arms He’ll take and shield thee; Thou wilt find a solace there. +
+
+
+
diff --git a/tests/resources/service/test.osz b/tests/resources/service/test.osz new file mode 100644 index 000000000..a289c0775 Binary files /dev/null and b/tests/resources/service/test.osz differ diff --git a/tests/resources/songbeamersongs/Lobsinget dem Herrn.sng b/tests/resources/songbeamersongs/Lobsinget dem Herrn.sng index fbc9aa9fc..c93a143fa 100644 --- a/tests/resources/songbeamersongs/Lobsinget dem Herrn.sng +++ b/tests/resources/songbeamersongs/Lobsinget dem Herrn.sng @@ -1,5 +1,7 @@ #LangCount=1 #Title=GL 1 - Lobsinget dem Herrn +#Author=Carl Brockhaus +#Melody=Johann Jakob Vetter #Editor=SongBeamer 4.20 #Version=3 #Format=F/K// diff --git a/tests/utils/test_bzr_tags.py b/tests/utils/test_bzr_tags.py new file mode 100644 index 000000000..95017e94f --- /dev/null +++ b/tests/utils/test_bzr_tags.py @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2014 Raoul Snyman # +# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan # +# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, # +# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. # +# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, # +# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # +# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, # +# Frode Woldsund, Martin Zibricky, Patrick Zimmermann # +# --------------------------------------------------------------------------- # +# 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 # +############################################################################### +""" +Package to test for proper bzr tags. +""" +import os + +from unittest import TestCase + +from subprocess import Popen, PIPE + +TAGS = [ + ['1.9.0', '1'], + ['1.9.1', '775'], + ['1.9.2', '890'], + ['1.9.3', '1063'], + ['1.9.4', '1196'], + ['1.9.5', '1421'], + ['1.9.6', '1657'], + ['1.9.7', '1761'], + ['1.9.8', '1856'], + ['1.9.9', '1917'], + ['1.9.10', '2003'], + ['1.9.11', '2039'], + ['1.9.12', '2063'], + ['2.0', '2118'], + ['2.0.1', '?'], + ['2.0.2', '?'], + ['2.0.3', '?'], + ['2.1.0', '2119'] +] + + +class TestBzrTags(TestCase): + + def bzr_tags_test(self): + """ + Test for proper bzr tags + """ + # GIVEN: A bzr branch + path = os.path.dirname(__file__) + + # WHEN getting the branches tags + bzr = Popen(('bzr', 'tags', '--directory=' + path), stdout=PIPE) + stdout = bzr.communicate()[0] + tags = [line.decode('utf-8').split() for line in stdout.splitlines()] + + # THEN the tags should match the accepted tags + self.assertEqual(TAGS, tags, 'List of tags should match')