diff --git a/openlp/core/common/settings.py b/openlp/core/common/settings.py index 3b7b31ca1..634bc5ced 100644 --- a/openlp/core/common/settings.py +++ b/openlp/core/common/settings.py @@ -286,6 +286,7 @@ class Settings(QtCore.QSettings): 'themes/last directory export': '', 'themes/last directory import': '', 'themes/theme level': ThemeLevel.Song, + 'themes/wrap footer': False, 'user interface/live panel': True, 'user interface/live splitter geometry': QtCore.QByteArray(), 'user interface/lock panel': False, diff --git a/openlp/core/lib/db.py b/openlp/core/lib/db.py index d67c05c42..8e9380241 100644 --- a/openlp/core/lib/db.py +++ b/openlp/core/lib/db.py @@ -96,9 +96,10 @@ def upgrade_db(url, upgrade): mapper(Metadata, metadata_table) version_meta = session.query(Metadata).get('version') if version_meta is None: - version_meta = Metadata.populate(key='version', value='0') + # Tables have just been created - fill the version field with the most recent version + version = upgrade.__version__ + version_meta = Metadata.populate(key='version', value=version) session.add(version_meta) - version = 0 else: version = int(version_meta.value) if version > upgrade.__version__: diff --git a/openlp/core/lib/htmlbuilder.py b/openlp/core/lib/htmlbuilder.py index 473aa9d7d..058e5a2a1 100644 --- a/openlp/core/lib/htmlbuilder.py +++ b/openlp/core/lib/htmlbuilder.py @@ -398,6 +398,7 @@ import logging from PyQt4 import QtWebKit +from openlp.core.common import Settings from openlp.core.lib.theme import BackgroundType, BackgroundGradientType, VerticalType, HorizontalType log = logging.getLogger(__name__) @@ -750,12 +751,13 @@ def build_footer_css(item, height): font-size: %spt; color: %s; text-align: left; - white-space: nowrap; + white-space: %s; """ theme = item.theme_data if not theme or not item.footer: return '' bottom = height - int(item.footer.y()) - int(item.footer.height()) + whitespace = 'normal' if Settings().value('themes/wrap footer') else 'nowrap' lyrics_html = style % (item.footer.x(), bottom, item.footer.width(), - theme.font_footer_name, theme.font_footer_size, theme.font_footer_color) + theme.font_footer_name, theme.font_footer_size, theme.font_footer_color, whitespace) return lyrics_html diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index 52afb5edc..592b01524 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -1103,7 +1103,7 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtGui.QWidget, Ui_ServiceManage Moves the cursor selection up the window. Called by the up arrow. """ item = self.service_manager_list.currentItem() - item_before = self.service_manager_list.item_above(item) + item_before = self.service_manager_list.itemAbove(item) if item_before is None: return self.service_manager_list.setCurrentItem(item_before) diff --git a/openlp/core/ui/thememanager.py b/openlp/core/ui/thememanager.py index 6a67605d4..b07c7fd2b 100644 --- a/openlp/core/ui/thememanager.py +++ b/openlp/core/ui/thememanager.py @@ -384,16 +384,8 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtGui.QWidget, Ui_ThemeManager, R self.application.set_busy_cursor() if path: Settings().setValue(self.settings_section + '/last directory export', path) - theme_path = os.path.join(path, theme + '.otz') - theme_zip = None try: - theme_zip = zipfile.ZipFile(theme_path, 'w') - source = os.path.join(self.path, theme) - for files in os.walk(source): - for name in files[2]: - theme_zip.write( - os.path.join(source, name).encode('utf-8'), os.path.join(theme, name).encode('utf-8') - ) + self._export_theme(path, theme) QtGui.QMessageBox.information(self, translate('OpenLP.ThemeManager', 'Theme Exported'), translate('OpenLP.ThemeManager', @@ -403,11 +395,29 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtGui.QWidget, Ui_ThemeManager, R critical_error_message_box(translate('OpenLP.ThemeManager', 'Theme Export Failed'), translate('OpenLP.ThemeManager', 'Your theme could not be exported due to an error.')) - finally: - if theme_zip: - theme_zip.close() self.application.set_normal_cursor() + def _export_theme(self, path, theme): + """ + Create the zipfile with the theme contents. + :param path: Location where the zip file will be placed + :param theme: The name of the theme to be exported + """ + theme_path = os.path.join(path, theme + '.otz') + try: + theme_zip = zipfile.ZipFile(theme_path, 'w') + source = os.path.join(self.path, theme) + for files in os.walk(source): + for name in files[2]: + theme_zip.write(os.path.join(source, name), os.path.join(theme, name)) + except (IOError, OSError): + if theme_zip: + theme_zip.close() + shutil.rmtree(theme_path, True) + raise + else: + theme_zip.close() + def on_import_theme(self, field=None): """ Opens a file dialog to select the theme file(s) to import before attempting to extract OpenLP themes from diff --git a/openlp/core/ui/themestab.py b/openlp/core/ui/themestab.py index 0478f0ed0..4b3f8b6eb 100644 --- a/openlp/core/ui/themestab.py +++ b/openlp/core/ui/themestab.py @@ -69,6 +69,14 @@ class ThemesTab(SettingsTab): self.default_list_view.setObjectName('default_list_view') self.global_group_box_layout.addWidget(self.default_list_view) self.left_layout.addWidget(self.global_group_box) + self.universal_group_box = QtGui.QGroupBox(self.left_column) + self.universal_group_box.setObjectName('universal_group_box') + self.universal_group_box_layout = QtGui.QVBoxLayout(self.universal_group_box) + self.universal_group_box_layout.setObjectName('universal_group_box_layout') + self.wrap_footer_check_box = QtGui.QCheckBox(self.universal_group_box) + self.wrap_footer_check_box.setObjectName('wrap_footer_check_box') + self.universal_group_box_layout.addWidget(self.wrap_footer_check_box) + self.left_layout.addWidget(self.universal_group_box) self.left_layout.addStretch() self.level_group_box = QtGui.QGroupBox(self.right_column) self.level_group_box.setObjectName('level_group_box') @@ -112,6 +120,8 @@ class ThemesTab(SettingsTab): """ self.tab_title_visible = UiStrings().Themes self.global_group_box.setTitle(translate('OpenLP.ThemesTab', 'Global Theme')) + self.universal_group_box.setTitle(translate('OpenLP.ThemesTab', 'Universal Settings')) + self.wrap_footer_check_box.setText(translate('OpenLP.ThemesTab', '&Wrap footer text')) 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( @@ -136,6 +146,7 @@ class ThemesTab(SettingsTab): settings.beginGroup(self.settings_section) self.theme_level = settings.value('theme level') self.global_theme = settings.value('global theme') + self.wrap_footer_check_box.setChecked(settings.value('wrap footer')) settings.endGroup() if self.theme_level == ThemeLevel.Global: self.global_level_radio_button.setChecked(True) @@ -152,6 +163,7 @@ class ThemesTab(SettingsTab): settings.beginGroup(self.settings_section) settings.setValue('theme level', self.theme_level) settings.setValue('global theme', self.global_theme) + settings.setValue('wrap footer', self.wrap_footer_check_box.isChecked()) settings.endGroup() self.renderer.set_theme_level(self.theme_level) if self.tab_visited: diff --git a/openlp/plugins/songs/forms/editsongdialog.py b/openlp/plugins/songs/forms/editsongdialog.py index a9ca71946..cdbac7fdb 100644 --- a/openlp/plugins/songs/forms/editsongdialog.py +++ b/openlp/plugins/songs/forms/editsongdialog.py @@ -138,6 +138,9 @@ class Ui_EditSongDialog(object): self.author_remove_layout = QtGui.QHBoxLayout() self.author_remove_layout.setObjectName('author_remove_layout') self.author_remove_layout.addStretch() + self.author_edit_button = QtGui.QPushButton(self.authors_group_box) + self.author_edit_button.setObjectName('author_edit_button') + self.author_remove_layout.addWidget(self.author_edit_button) self.author_remove_button = QtGui.QPushButton(self.authors_group_box) self.author_remove_button.setObjectName('author_remove_button') self.author_remove_layout.addWidget(self.author_remove_button) @@ -305,6 +308,7 @@ class Ui_EditSongDialog(object): translate('SongsPlugin.EditSongForm', 'Title && Lyrics')) self.authors_group_box.setTitle(SongStrings.Authors) self.author_add_button.setText(translate('SongsPlugin.EditSongForm', '&Add to Song')) + self.author_edit_button.setText(translate('SongsPlugin.EditSongForm', '&Edit Author Type')) self.author_remove_button.setText(translate('SongsPlugin.EditSongForm', '&Remove')) self.maintenance_button.setText(translate('SongsPlugin.EditSongForm', '&Manage Authors, Topics, Song Books')) self.topics_group_box.setTitle(SongStrings.Topic) diff --git a/openlp/plugins/songs/forms/editsongform.py b/openlp/plugins/songs/forms/editsongform.py index 6b5ccb041..d71cde304 100644 --- a/openlp/plugins/songs/forms/editsongform.py +++ b/openlp/plugins/songs/forms/editsongform.py @@ -70,6 +70,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog, RegistryProperties): self.setupUi(self) # Connecting signals and slots self.author_add_button.clicked.connect(self.on_author_add_button_clicked) + self.author_edit_button.clicked.connect(self.on_author_edit_button_clicked) self.author_remove_button.clicked.connect(self.on_author_remove_button_clicked) self.authors_list_view.itemClicked.connect(self.on_authors_list_view_clicked) self.topic_add_button.clicked.connect(self.on_topic_add_button_clicked) @@ -334,6 +335,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog, RegistryProperties): """ self.verse_edit_button.setEnabled(False) self.verse_delete_button.setEnabled(False) + self.author_edit_button.setEnabled(False) self.author_remove_button.setEnabled(False) self.topic_remove_button.setEnabled(False) @@ -354,12 +356,9 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog, RegistryProperties): # 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) + for author_type in AuthorType.SortedTypes: + self.author_types_combo_box.addItem(AuthorType.Types[author_type], author_type) def load_topics(self): """ @@ -596,9 +595,32 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog, RegistryProperties): """ Run a set of actions when an author in the list is selected (mainly enable the delete button). """ - if self.authors_list_view.count() > 1: + count = self.authors_list_view.count() + if count > 0: + self.author_edit_button.setEnabled(True) + if count > 1: + # There must be at least one author self.author_remove_button.setEnabled(True) + def on_author_edit_button_clicked(self): + """ + Show a dialog to change the type of an author when the edit button is clicked + """ + self.author_edit_button.setEnabled(False) + item = self.authors_list_view.currentItem() + author_id, author_type = item.data(QtCore.Qt.UserRole) + choice, ok = QtGui.QInputDialog.getItem(self, translate('SongsPlugin.EditSongForm', 'Edit Author Type'), + translate('SongsPlugin.EditSongForm', 'Choose type for this author'), + AuthorType.TranslatedTypes, + current=AuthorType.SortedTypes.index(author_type), + editable=False) + if not ok: + return + author = self.manager.get_object(Author, author_id) + author_type = AuthorType.from_translated_text(choice) + item.setData(QtCore.Qt.UserRole, (author_id, author_type)) + item.setText(author.get_display_name(author_type)) + def on_author_remove_button_clicked(self): """ Remove the author from the list when the delete button is clicked. diff --git a/openlp/plugins/songs/lib/__init__.py b/openlp/plugins/songs/lib/__init__.py index d03bdefd6..999f51fad 100644 --- a/openlp/plugins/songs/lib/__init__.py +++ b/openlp/plugins/songs/lib/__init__.py @@ -374,7 +374,7 @@ def clean_song(manager, song): :param manager: The song database manager object. :param song: The song object. """ - from .xml import SongXML + from .openlyricsxml import SongXML if song.title: song.title = clean_title(song.title) diff --git a/openlp/plugins/songs/lib/db.py b/openlp/plugins/songs/lib/db.py index 16f7ea719..a9206a397 100644 --- a/openlp/plugins/songs/lib/db.py +++ b/openlp/plugins/songs/lib/db.py @@ -69,17 +69,42 @@ class AuthorType(object): The 'words+music' type is not an official type, but is provided for convenience. """ + NoType = '' Words = 'words' Music = 'music' WordsAndMusic = 'words+music' Translation = 'translation' Types = { + NoType: '', Words: translate('SongsPlugin.AuthorType', 'Words', 'Author who wrote the lyrics of a song'), Music: translate('SongsPlugin.AuthorType', 'Music', 'Author who wrote the music of a song'), WordsAndMusic: translate('SongsPlugin.AuthorType', 'Words and Music', 'Author who wrote both lyrics and music of a song'), Translation: translate('SongsPlugin.AuthorType', 'Translation', 'Author who translated the song') } + SortedTypes = [ + NoType, + Words, + Music, + WordsAndMusic + ] + TranslatedTypes = [ + Types[NoType], + Types[Words], + Types[Music], + Types[WordsAndMusic] + ] + + @staticmethod + def from_translated_text(translated_type): + """ + Get the AuthorType from a translated string. + :param translated_type: Translated Author type. + """ + for key, value in AuthorType.Types.items(): + if value == translated_type: + return key + return AuthorType.NoType class Book(BaseModel): diff --git a/openlp/plugins/songs/lib/importer.py b/openlp/plugins/songs/lib/importer.py index bb622bcf9..0084a74de 100644 --- a/openlp/plugins/songs/lib/importer.py +++ b/openlp/plugins/songs/lib/importer.py @@ -51,12 +51,12 @@ from .importers.foilpresenter import FoilPresenterImport from .importers.zionworx import ZionWorxImport from .importers.propresenter import ProPresenterImport from .importers.worshipassistant import WorshipAssistantImport -# Imports that might fail - +from .importers.powerpraise import PowerPraiseImport +from .importers.presentationmanager import PresentationManagerImport log = logging.getLogger(__name__) - +# Imports that might fail try: from .importers.songsoffellowship import SongsOfFellowshipImport HAS_SOF = True @@ -160,17 +160,19 @@ class SongFormat(object): FoilPresenter = 8 MediaShout = 9 OpenSong = 10 - PowerSong = 11 - ProPresenter = 12 - SongBeamer = 13 - SongPro = 14 - SongShowPlus = 15 - SongsOfFellowship = 16 - SundayPlus = 17 - WordsOfWorship = 18 - WorshipAssistant = 19 - WorshipCenterPro = 20 - ZionWorx = 21 + PowerPraise = 11 + PowerSong = 12 + PresentationManager = 13 + ProPresenter = 14 + SongBeamer = 15 + SongPro = 16 + SongShowPlus = 17 + SongsOfFellowship = 18 + SundayPlus = 19 + WordsOfWorship = 20 + WorshipAssistant = 21 + WorshipCenterPro = 22 + ZionWorx = 23 # Set optional attribute defaults __defaults__ = { @@ -266,6 +268,12 @@ class SongFormat(object): 'name': WizardStrings.OS, 'prefix': 'openSong' }, + PowerPraise: { + 'class': PowerPraiseImport, + 'name': 'PowerPraise', + 'prefix': 'powerPraise', + 'filter': '%s (*.ppl)' % translate('SongsPlugin.ImportWizardForm', 'PowerPraise Song Files') + }, PowerSong: { 'class': PowerSongImport, 'name': 'PowerSong 1.0', @@ -274,6 +282,12 @@ class SongFormat(object): 'invalidSourceMsg': translate('SongsPlugin.ImportWizardForm', 'You need to specify a valid PowerSong 1.0 ' 'database folder.') }, + PresentationManager: { + 'class': PresentationManagerImport, + 'name': 'PresentationManager', + 'prefix': 'presentationManager', + 'filter': '%s (*.sng)' % translate('SongsPlugin.ImportWizardForm', 'PresentationManager Song Files') + }, ProPresenter: { 'class': ProPresenterImport, 'name': 'ProPresenter', @@ -374,7 +388,9 @@ class SongFormat(object): SongFormat.FoilPresenter, SongFormat.MediaShout, SongFormat.OpenSong, + SongFormat.PowerPraise, SongFormat.PowerSong, + SongFormat.PresentationManager, SongFormat.ProPresenter, SongFormat.SongBeamer, SongFormat.SongPro, diff --git a/openlp/plugins/songs/lib/importers/powerpraise.py b/openlp/plugins/songs/lib/importers/powerpraise.py new file mode 100644 index 000000000..ff30f9763 --- /dev/null +++ b/openlp/plugins/songs/lib/importers/powerpraise.py @@ -0,0 +1,91 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2013 Raoul Snyman # +# Portions copyright (c) 2008-2013 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 # +############################################################################### +""" +The :mod:`powerpraiseimport` module provides the functionality for importing +Powerpraise song files into the current database. +""" + +import os +from lxml import objectify + +from openlp.core.ui.wizard import WizardStrings +from .songimport import SongImport + + +class PowerPraiseImport(SongImport): + """ + The :class:`PowerpraiseImport` class provides OpenLP with the + ability to import Powerpraise song files. + """ + def do_import(self): + self.import_wizard.progress_bar.setMaximum(len(self.import_source)) + for file_path in self.import_source: + if self.stop_import_flag: + return + self.import_wizard.increment_progress_bar(WizardStrings.ImportingType % os.path.basename(file_path)) + root = objectify.parse(open(file_path, 'rb')).getroot() + self.process_song(root) + + def process_song(self, root): + self.set_defaults() + self.title = str(root.general.title) + verse_order_list = [] + verse_count = {} + for item in root.order.item: + verse_order_list.append(str(item)) + for part in root.songtext.part: + original_verse_def = part.get('caption') + # There are some predefined verse defitions in PowerPraise, try to parse these + if original_verse_def.startswith("Strophe") or original_verse_def.startswith("Teil"): + verse_def = 'v' + elif original_verse_def.startswith("Refrain"): + verse_def = 'c' + elif original_verse_def.startswith("Bridge"): + verse_def = 'b' + elif original_verse_def.startswith("Schluss"): + verse_def = 'e' + else: + verse_def = 'o' + verse_count[verse_def] = verse_count.get(verse_def, 0) + 1 + verse_def = '%s%d' % (verse_def, verse_count[verse_def]) + verse_text = [] + for slide in part.slide: + if not hasattr(slide, 'line'): + continue # No content + for line in slide.line: + verse_text.append(str(line)) + self.add_verse('\n'.join(verse_text), verse_def) + # Update verse name in verse order list + for i in range(len(verse_order_list)): + if verse_order_list[i].lower() == original_verse_def.lower(): + verse_order_list[i] = verse_def + + self.verse_order_list = verse_order_list + if not self.finish(): + self.log_error(self.import_source) diff --git a/openlp/plugins/songs/lib/importers/presentationmanager.py b/openlp/plugins/songs/lib/importers/presentationmanager.py new file mode 100644 index 000000000..52a047a30 --- /dev/null +++ b/openlp/plugins/songs/lib/importers/presentationmanager.py @@ -0,0 +1,93 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2013 Raoul Snyman # +# Portions copyright (c) 2008-2013 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 # +############################################################################### +""" +The :mod:`presentationmanager` module provides the functionality for importing +Presentationmanager song files into the current database. +""" + +import os +from lxml import objectify + +from openlp.core.ui.wizard import WizardStrings +from .songimport import SongImport + + +class PresentationManagerImport(SongImport): + """ + The :class:`PresentationManagerImport` class provides OpenLP with the + ability to import Presentationmanager song files. + """ + def do_import(self): + self.import_wizard.progress_bar.setMaximum(len(self.import_source)) + for file_path in self.import_source: + if self.stop_import_flag: + return + self.import_wizard.increment_progress_bar(WizardStrings.ImportingType % os.path.basename(file_path)) + root = objectify.parse(open(file_path, 'rb')).getroot() + self.process_song(root) + + def process_song(self, root): + self.set_defaults() + self.title = str(root.attributes.title) + self.add_author(str(root.attributes.author)) + self.copyright = str(root.attributes.copyright) + self.ccli_number = str(root.attributes.ccli_number) + self.comments = str(root.attributes.comments) + verse_order_list = [] + verse_count = {} + duplicates = [] + for verse in root.verses.verse: + original_verse_def = verse.get('id') + # Presentation Manager stores duplicate verses instead of a verse order. + # We need to create the verse order from that. + is_duplicate = False + if original_verse_def in duplicates: + is_duplicate = True + else: + duplicates.append(original_verse_def) + if original_verse_def.startswith("Verse"): + verse_def = 'v' + elif original_verse_def.startswith("Chorus") or original_verse_def.startswith("Refrain"): + verse_def = 'c' + elif original_verse_def.startswith("Bridge"): + verse_def = 'b' + elif original_verse_def.startswith("End"): + verse_def = 'e' + else: + verse_def = 'o' + if not is_duplicate: # Only increment verse number if no duplicate + verse_count[verse_def] = verse_count.get(verse_def, 0) + 1 + verse_def = '%s%d' % (verse_def, verse_count[verse_def]) + if not is_duplicate: # Only add verse if no duplicate + self.add_verse(str(verse).strip(), verse_def) + verse_order_list.append(verse_def) + + self.verse_order_list = verse_order_list + if not self.finish(): + self.log_error(self.import_source) diff --git a/openlp/plugins/songs/lib/upgrade.py b/openlp/plugins/songs/lib/upgrade.py index 580ae767d..5b7255266 100644 --- a/openlp/plugins/songs/lib/upgrade.py +++ b/openlp/plugins/songs/lib/upgrade.py @@ -33,7 +33,6 @@ backend for the Songs plugin import logging from sqlalchemy import Column, ForeignKey, types -from sqlalchemy.exc import OperationalError from sqlalchemy.sql.expression import func, false, null, text from openlp.core.lib.db import get_upgrade_op @@ -57,16 +56,13 @@ def upgrade_1(session, metadata): :param session: :param metadata: """ - try: - op = get_upgrade_op(session) - op.drop_table('media_files_songs') - op.add_column('media_files', Column('song_id', types.Integer(), server_default=null())) - op.add_column('media_files', Column('weight', types.Integer(), server_default=text('0'))) - if metadata.bind.url.get_dialect().name != 'sqlite': - # SQLite doesn't support ALTER TABLE ADD CONSTRAINT - op.create_foreign_key('fk_media_files_song_id', 'media_files', 'songs', ['song_id', 'id']) - except OperationalError: - log.info('Upgrade 1 has already been run') + op = get_upgrade_op(session) + op.drop_table('media_files_songs') + op.add_column('media_files', Column('song_id', types.Integer(), server_default=null())) + op.add_column('media_files', Column('weight', types.Integer(), server_default=text('0'))) + if metadata.bind.url.get_dialect().name != 'sqlite': + # SQLite doesn't support ALTER TABLE ADD CONSTRAINT + op.create_foreign_key('fk_media_files_song_id', 'media_files', 'songs', ['song_id', 'id']) def upgrade_2(session, metadata): @@ -75,12 +71,9 @@ def upgrade_2(session, metadata): This upgrade adds a create_date and last_modified date to the songs table """ - try: - op = get_upgrade_op(session) - op.add_column('songs', Column('create_date', types.DateTime(), default=func.now())) - op.add_column('songs', Column('last_modified', types.DateTime(), default=func.now())) - except OperationalError: - log.info('Upgrade 2 has already been run') + op = get_upgrade_op(session) + op.add_column('songs', Column('create_date', types.DateTime(), default=func.now())) + op.add_column('songs', Column('last_modified', types.DateTime(), default=func.now())) def upgrade_3(session, metadata): @@ -89,14 +82,11 @@ def upgrade_3(session, metadata): This upgrade adds a temporary song flag to the songs table """ - try: - op = get_upgrade_op(session) - if metadata.bind.url.get_dialect().name == 'sqlite': - op.add_column('songs', Column('temporary', types.Boolean(create_constraint=False), server_default=false())) - else: - op.add_column('songs', Column('temporary', types.Boolean(), server_default=false())) - except OperationalError: - log.info('Upgrade 3 has already been run') + op = get_upgrade_op(session) + if metadata.bind.url.get_dialect().name == 'sqlite': + op.add_column('songs', Column('temporary', types.Boolean(create_constraint=False), server_default=false())) + else: + op.add_column('songs', Column('temporary', types.Boolean(), server_default=false())) def upgrade_4(session, metadata): @@ -105,17 +95,14 @@ def upgrade_4(session, metadata): This upgrade adds a column for author type to the authors_songs table """ - try: - # Since SQLite doesn't support changing the primary key of a table, we need to recreate the table - # and copy the old values - op = get_upgrade_op(session) - op.create_table('authors_songs_tmp', - Column('author_id', types.Integer(), ForeignKey('authors.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('""'))) - op.execute('INSERT INTO authors_songs_tmp SELECT author_id, song_id, "" FROM authors_songs') - op.drop_table('authors_songs') - op.rename_table('authors_songs_tmp', 'authors_songs') - except OperationalError: - log.info('Upgrade 4 has already been run') + # Since SQLite doesn't support changing the primary key of a table, we need to recreate the table + # and copy the old values + op = get_upgrade_op(session) + op.create_table('authors_songs_tmp', + Column('author_id', types.Integer(), ForeignKey('authors.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('""'))) + op.execute('INSERT INTO authors_songs_tmp SELECT author_id, song_id, "" FROM authors_songs') + op.drop_table('authors_songs') + op.rename_table('authors_songs_tmp', 'authors_songs') diff --git a/openlp/plugins/songusage/lib/upgrade.py b/openlp/plugins/songusage/lib/upgrade.py index 24f264824..b0f0f52f0 100644 --- a/openlp/plugins/songusage/lib/upgrade.py +++ b/openlp/plugins/songusage/lib/upgrade.py @@ -32,7 +32,6 @@ backend for the SongsUsage plugin """ import logging -from sqlalchemy.exc import OperationalError from sqlalchemy import Column, types from openlp.core.lib.db import get_upgrade_op @@ -50,9 +49,6 @@ def upgrade_1(session, metadata): :param session: SQLAlchemy Session object :param metadata: SQLAlchemy MetaData object """ - try: - op = get_upgrade_op(session) - op.add_column('songusage_data', Column('plugin_name', types.Unicode(20), server_default='')) - op.add_column('songusage_data', Column('source', types.Unicode(10), server_default='')) - except OperationalError: - log.info('Upgrade 1 has already taken place') + op = get_upgrade_op(session) + op.add_column('songusage_data', Column('plugin_name', types.Unicode(20), server_default='')) + op.add_column('songusage_data', Column('source', types.Unicode(10), server_default='')) diff --git a/scripts/jenkins_script.py b/scripts/jenkins_script.py index 4ee0b3aa2..7abdbccd0 100755 --- a/scripts/jenkins_script.py +++ b/scripts/jenkins_script.py @@ -116,7 +116,9 @@ class JenkinsTrigger(object): print('%s (revision %s)' % (get_repo_name(), revno)) for job in OpenLPJobs.Jobs: - self.__print_build_info(job) + if not self.__print_build_info(job): + print('Stopping after failure') + break def open_browser(self): """ @@ -133,6 +135,7 @@ class JenkinsTrigger(object): :param job_name: The name of the job we want the information from. For example *Branch-01-Pull*. Use the class variables from the :class:`OpenLPJobs` class. """ + is_success = False job = self.jenkins_instance.job(job_name) while job.info['inQueue']: time.sleep(1) @@ -141,11 +144,13 @@ class JenkinsTrigger(object): if build.info['result'] == 'SUCCESS': # Make 'SUCCESS' green. result_string = '%s%s%s' % (Colour.GREEN_START, build.info['result'], Colour.GREEN_END) + is_success = True 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)) + return is_success def get_repo_name(): diff --git a/tests/functional/openlp_core_lib/test_htmlbuilder.py b/tests/functional/openlp_core_lib/test_htmlbuilder.py index ef5ffdf43..7ba63a792 100644 --- a/tests/functional/openlp_core_lib/test_htmlbuilder.py +++ b/tests/functional/openlp_core_lib/test_htmlbuilder.py @@ -6,11 +6,12 @@ from unittest import TestCase from PyQt4 import QtCore +from openlp.core.common import Settings from openlp.core.lib.htmlbuilder import build_html, build_background_css, build_lyrics_css, build_lyrics_outline_css, \ build_lyrics_format_css, build_footer_css from openlp.core.lib.theme import HorizontalType, VerticalType from tests.functional import MagicMock, patch - +from tests.helpers.testmixin import TestMixin HTML = """ @@ -184,7 +185,7 @@ LYRICS_OUTLINE_CSS = ' -webkit-text-stroke: 0.125em #000000; -webkit-text-fill-c LYRICS_FORMAT_CSS = ' word-wrap: break-word; text-align: justify; vertical-align: bottom; ' + \ 'font-family: Arial; font-size: 40pt; color: #FFFFFF; line-height: 108%; margin: 0;padding: 0; ' + \ 'padding-bottom: 0.5em; padding-left: 2px; width: 1580px; height: 810px; font-style:italic; font-weight:bold; ' -FOOTER_CSS = """ +FOOTER_CSS_BASE = """ left: 10px; bottom: 0px; width: 1260px; @@ -192,11 +193,28 @@ FOOTER_CSS = """ font-size: 12pt; color: #FFFFFF; text-align: left; - white-space: nowrap; + white-space: %s; """ +FOOTER_CSS = FOOTER_CSS_BASE % ('nowrap') +FOOTER_CSS_WRAP = FOOTER_CSS_BASE % ('normal') -class Htmbuilder(TestCase): +class Htmbuilder(TestCase, TestMixin): + """ + Test the functions in the Htmlbuilder module + """ + def setUp(self): + """ + Create the UI + """ + self.build_settings() + + def tearDown(self): + """ + Delete all the C++ objects at the end so that we don't have a segfault + """ + self.destroy_settings() + def build_html_test(self): """ Test the build_html() function @@ -225,7 +243,7 @@ class Htmbuilder(TestCase): html = build_html(item, screen, is_live, background, plugins=plugins) # THEN: The returned html should match. - assert html == HTML + self.assertEqual(html, HTML, 'The returned html should match') def build_background_css_radial_test(self): """ @@ -241,7 +259,7 @@ class Htmbuilder(TestCase): css = build_background_css(item, width) # THEN: The returned css should match. - assert BACKGROUND_CSS_RADIAL == css, 'The background css should be equal.' + self.assertEqual(BACKGROUND_CSS_RADIAL, css, 'The background css should be equal.') def build_lyrics_css_test(self): """ @@ -262,7 +280,7 @@ class Htmbuilder(TestCase): css = build_lyrics_css(item) # THEN: The css should be equal. - assert LYRICS_CSS == css, 'The lyrics css should be equal.' + self.assertEqual(LYRICS_CSS, css, 'The lyrics css should be equal.') def build_lyrics_outline_css_test(self): """ @@ -279,7 +297,7 @@ class Htmbuilder(TestCase): css = build_lyrics_outline_css(theme_data) # THEN: The css should be equal. - assert LYRICS_OUTLINE_CSS == css, 'The outline css should be equal.' + self.assertEqual(LYRICS_OUTLINE_CSS, css, 'The outline css should be equal.') def build_lyrics_format_css_test(self): """ @@ -302,7 +320,7 @@ class Htmbuilder(TestCase): css = build_lyrics_format_css(theme_data, width, height) # THEN: They should be equal. - assert LYRICS_FORMAT_CSS == css, 'The lyrics format css should be equal.' + self.assertEqual(LYRICS_FORMAT_CSS, css, 'The lyrics format css should be equal.') def build_footer_css_test(self): """ @@ -316,8 +334,27 @@ class Htmbuilder(TestCase): item.theme_data.font_footer_color = '#FFFFFF' height = 1024 - # WHEN: create the css. + # WHEN: create the css with default settings. css = build_footer_css(item, height) # THEN: THE css should be the same. - assert FOOTER_CSS == css, 'The footer strings should be equal.' + self.assertEqual(FOOTER_CSS, css, 'The footer strings should be equal.') + + def build_footer_css_wrap_test(self): + """ + Test the build_footer_css() function + """ + # GIVEN: Create a theme. + item = MagicMock() + item.footer = QtCore.QRect(10, 921, 1260, 103) + item.theme_data.font_footer_name = 'Arial' + item.theme_data.font_footer_size = 12 + item.theme_data.font_footer_color = '#FFFFFF' + height = 1024 + + # WHEN: Settings say that footer should wrap + Settings().setValue('themes/wrap footer', True) + css = build_footer_css(item, height) + + # THEN: Footer should wrap + self.assertEqual(FOOTER_CSS_WRAP, css, 'The footer strings should be equal.') diff --git a/tests/functional/openlp_core_ui/test_slidecontroller.py b/tests/functional/openlp_core_ui/test_slidecontroller.py index ed237d424..1d241a317 100644 --- a/tests/functional/openlp_core_ui/test_slidecontroller.py +++ b/tests/functional/openlp_core_ui/test_slidecontroller.py @@ -504,21 +504,20 @@ class TestSlideController(TestCase): mocked_item = MagicMock() mocked_item.is_command.return_value = True mocked_item.name = 'Mocked Item' - mocked_execute = MagicMock() mocked_update_preview = MagicMock() mocked_preview_widget = MagicMock() mocked_slide_selected = MagicMock() - Registry.execute = mocked_execute - Registry.create() - slide_controller = SlideController(None) - slide_controller.service_item = mocked_item - slide_controller.update_preview = mocked_update_preview - slide_controller.preview_widget = mocked_preview_widget - slide_controller.slide_selected = mocked_slide_selected - slide_controller.is_live = True + with patch.object(Registry, 'execute') as mocked_execute: + Registry.create() + slide_controller = SlideController(None) + slide_controller.service_item = mocked_item + slide_controller.update_preview = mocked_update_preview + slide_controller.preview_widget = mocked_preview_widget + slide_controller.slide_selected = mocked_slide_selected + slide_controller.is_live = True - # WHEN: The method is called - slide_controller.on_slide_selected_index([9]) + # WHEN: The method is called + slide_controller.on_slide_selected_index([9]) # THEN: It should have sent a notification mocked_item.is_command.assert_called_once_with() @@ -535,20 +534,19 @@ class TestSlideController(TestCase): mocked_item = MagicMock() mocked_item.is_command.return_value = False mocked_item.name = 'Mocked Item' - mocked_execute = MagicMock() mocked_update_preview = MagicMock() mocked_preview_widget = MagicMock() mocked_slide_selected = MagicMock() - Registry.execute = mocked_execute - Registry.create() - slide_controller = SlideController(None) - slide_controller.service_item = mocked_item - slide_controller.update_preview = mocked_update_preview - slide_controller.preview_widget = mocked_preview_widget - slide_controller.slide_selected = mocked_slide_selected + with patch.object(Registry, 'execute') as mocked_execute: + Registry.create() + slide_controller = SlideController(None) + slide_controller.service_item = mocked_item + slide_controller.update_preview = mocked_update_preview + slide_controller.preview_widget = mocked_preview_widget + slide_controller.slide_selected = mocked_slide_selected - # WHEN: The method is called - slide_controller.on_slide_selected_index([7]) + # WHEN: The method is called + slide_controller.on_slide_selected_index([7]) # THEN: It should have sent a notification mocked_item.is_command.assert_called_once_with() diff --git a/tests/functional/openlp_core_ui/test_thememanager.py b/tests/functional/openlp_core_ui/test_thememanager.py index 496e78aa4..3fd15baac 100644 --- a/tests/functional/openlp_core_ui/test_thememanager.py +++ b/tests/functional/openlp_core_ui/test_thememanager.py @@ -27,14 +27,16 @@ # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### """ -Package to test the openlp.core.ui.slidecontroller package. +Package to test the openlp.core.ui.thememanager package. """ +import zipfile import os from unittest import TestCase +from tests.interfaces import MagicMock -from openlp.core.common import Registry from openlp.core.ui import ThemeManager +from openlp.core.common import Registry from tests.utils.constants import TEST_RESOURCES_PATH from tests.interfaces import MagicMock, patch @@ -48,6 +50,25 @@ class TestThemeManager(TestCase): """ Registry.create() + def export_theme_test(self): + """ + Test exporting a theme . + """ + # GIVEN: A new ThemeManager instance. + theme_manager = ThemeManager() + theme_manager.path = os.path.join(TEST_RESOURCES_PATH, 'themes') + zipfile.ZipFile.__init__ = MagicMock() + zipfile.ZipFile.__init__.return_value = None + zipfile.ZipFile.write = MagicMock() + + # WHEN: The theme is exported + theme_manager._export_theme('/some/path', 'Default') + + # THEN: The zipfile should be created at the given path + zipfile.ZipFile.__init__.assert_called_with('/some/path/Default.otz', 'w') + zipfile.ZipFile.write.assert_called_with(os.path.join(TEST_RESOURCES_PATH, 'themes', 'Default', 'Default.xml'), + 'Default/Default.xml') + def initial_theme_manager_test(self): """ Test the instantiation of theme manager. diff --git a/tests/functional/openlp_core_ui/test_thememanager.py.moved b/tests/functional/openlp_core_ui/test_thememanager.py.moved new file mode 100644 index 000000000..496e78aa4 --- /dev/null +++ b/tests/functional/openlp_core_ui/test_thememanager.py.moved @@ -0,0 +1,115 @@ +# -*- 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.slidecontroller package. +""" +import os + +from unittest import TestCase + +from openlp.core.common import Registry +from openlp.core.ui import ThemeManager + +from tests.utils.constants import TEST_RESOURCES_PATH +from tests.interfaces import MagicMock, patch + + +class TestThemeManager(TestCase): + + def setUp(self): + """ + Set up the tests + """ + Registry.create() + + def initial_theme_manager_test(self): + """ + Test the instantiation of theme manager. + """ + # GIVEN: A new service manager instance. + ThemeManager(None) + + # WHEN: the default theme manager is built. + # THEN: The the controller should be registered in the registry. + self.assertIsNotNone(Registry().get('theme_manager'), 'The base theme manager should be registered') + + def write_theme_same_image_test(self): + """ + Test that we don't try to overwrite a theme background image with itself + """ + # GIVEN: A new theme manager instance, with mocked builtins.open, shutil.copyfile, + # theme, check_directory_exists and thememanager-attributes. + with patch('builtins.open') as mocked_open, \ + patch('openlp.core.ui.thememanager.shutil.copyfile') as mocked_copyfile, \ + patch('openlp.core.ui.thememanager.check_directory_exists') as mocked_check_directory_exists: + mocked_open.return_value = MagicMock() + theme_manager = ThemeManager(None) + theme_manager.old_background_image = None + theme_manager.generate_and_save_image = MagicMock() + theme_manager.path = '' + mocked_theme = MagicMock() + mocked_theme.theme_name = 'themename' + mocked_theme.extract_formatted_xml = MagicMock() + mocked_theme.extract_formatted_xml.return_value = 'fake_theme_xml'.encode() + + # WHEN: Calling _write_theme with path to the same image, but the path written slightly different + file_name1 = os.path.join(TEST_RESOURCES_PATH, 'church.jpg') + # Do replacement from end of string to avoid problems with path start + file_name2 = file_name1[::-1].replace(os.sep, os.sep + os.sep, 2)[::-1] + theme_manager._write_theme(mocked_theme, file_name1, file_name2) + + # THEN: The mocked_copyfile should not have been called + self.assertFalse(mocked_copyfile.called, 'shutil.copyfile should not be called') + + def write_theme_diff_images_test(self): + """ + Test that we do overwrite a theme background image when a new is submitted + """ + # GIVEN: A new theme manager instance, with mocked builtins.open, shutil.copyfile, + # theme, check_directory_exists and thememanager-attributes. + with patch('builtins.open') as mocked_open, \ + patch('openlp.core.ui.thememanager.shutil.copyfile') as mocked_copyfile, \ + patch('openlp.core.ui.thememanager.check_directory_exists') as mocked_check_directory_exists: + mocked_open.return_value = MagicMock() + theme_manager = ThemeManager(None) + theme_manager.old_background_image = None + theme_manager.generate_and_save_image = MagicMock() + theme_manager.path = '' + mocked_theme = MagicMock() + mocked_theme.theme_name = 'themename' + mocked_theme.extract_formatted_xml = MagicMock() + mocked_theme.extract_formatted_xml.return_value = 'fake_theme_xml'.encode() + + # WHEN: Calling _write_theme with path to different images + file_name1 = os.path.join(TEST_RESOURCES_PATH, 'church.jpg') + file_name2 = os.path.join(TEST_RESOURCES_PATH, 'church2.jpg') + theme_manager._write_theme(mocked_theme, file_name1, file_name2) + + # THEN: The mocked_copyfile should not have been called + self.assertTrue(mocked_copyfile.called, 'shutil.copyfile should be called') diff --git a/tests/functional/openlp_plugins/songs/test_db.py b/tests/functional/openlp_plugins/songs/test_db.py index 3080db77e..e696ea94b 100644 --- a/tests/functional/openlp_plugins/songs/test_db.py +++ b/tests/functional/openlp_plugins/songs/test_db.py @@ -112,3 +112,44 @@ class TestDB(TestCase): # THEN: It should have been removed and the other author should still be there self.assertEqual(1, len(song.authors_songs)) self.assertEqual(None, song.authors_songs[0].author_type) + + def test_get_author_type_from_translated_text(self): + """ + Test getting an author type from translated text + """ + # GIVEN: A string with an author type + author_type_name = AuthorType.Types[AuthorType.Words] + + # WHEN: We call the method + author_type = AuthorType.from_translated_text(author_type_name) + + # THEN: The type should be correct + self.assertEqual(author_type, AuthorType.Words) + + def test_author_get_display_name(self): + """ + Test that the display name of an author is correct + """ + # GIVEN: An author + author = Author() + author.display_name = "John Doe" + + # WHEN: We call the get_display_name() function + display_name = author.get_display_name() + + # THEN: It should return only the name + self.assertEqual("John Doe", display_name) + + def test_author_get_display_name_with_type(self): + """ + Test that the display name of an author with a type is correct + """ + # GIVEN: An author + author = Author() + author.display_name = "John Doe" + + # WHEN: We call the get_display_name() function + display_name = author.get_display_name(AuthorType.Words) + + # THEN: It should return the name with the type in brackets + self.assertEqual("John Doe (Words)", display_name) diff --git a/tests/functional/openlp_plugins/songs/test_powerpraiseimport.py b/tests/functional/openlp_plugins/songs/test_powerpraiseimport.py new file mode 100644 index 000000000..dbe834e1c --- /dev/null +++ b/tests/functional/openlp_plugins/songs/test_powerpraiseimport.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-2013 Raoul Snyman # +# Portions copyright (c) 2008-2013 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 # +############################################################################### +""" +The :mod:`powerpraiseimport` module provides the functionality for importing +ProPresenter song files into the current installation database. +""" + +import os + +from tests.helpers.songfileimport import SongImportTestHelper + +TEST_PATH = os.path.abspath( + os.path.join(os.path.dirname(__file__), '..', '..', '..', 'resources', 'powerpraisesongs')) + + +class TestPowerPraiseFileImport(SongImportTestHelper): + + def __init__(self, *args, **kwargs): + self.importer_class_name = 'PowerPraiseImport' + self.importer_module_name = 'powerpraise' + super(TestPowerPraiseFileImport, self).__init__(*args, **kwargs) + + def test_song_import(self): + """ + Test that loading a PowerPraise file works correctly + """ + self.file_import([os.path.join(TEST_PATH, 'Näher, mein Gott zu Dir.ppl')], + self.load_external_result_data(os.path.join(TEST_PATH, 'Näher, mein Gott zu Dir.json'))) + self.file_import([os.path.join(TEST_PATH, 'You are so faithful.ppl')], + self.load_external_result_data(os.path.join(TEST_PATH, 'You are so faithful.json'))) diff --git a/tests/functional/openlp_plugins/songs/test_presentationmanagerimport.py b/tests/functional/openlp_plugins/songs/test_presentationmanagerimport.py new file mode 100644 index 000000000..9d0f7dca4 --- /dev/null +++ b/tests/functional/openlp_plugins/songs/test_presentationmanagerimport.py @@ -0,0 +1,53 @@ +# -*- 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 PresentationManager song importer. +""" + +import os + +from tests.helpers.songfileimport import SongImportTestHelper + +TEST_PATH = os.path.abspath( + os.path.join(os.path.dirname(__file__), '..', '..', '..', 'resources', 'presentationmanagersongs')) + + +class TestSongShowPlusFileImport(SongImportTestHelper): + + def __init__(self, *args, **kwargs): + self.importer_class_name = 'PresentationManagerImport' + self.importer_module_name = 'presentationmanager' + super(TestSongShowPlusFileImport, self).__init__(*args, **kwargs) + + def test_song_import(self): + """ + Test that loading a PresentationManager file works correctly + """ + self.file_import([os.path.join(TEST_PATH, 'Great Is Thy Faithfulness.sng')], + self.load_external_result_data(os.path.join(TEST_PATH, 'Great Is Thy Faithfulness.json'))) diff --git a/tests/functional/openlp_plugins/songs/test_propresenterimport.py b/tests/functional/openlp_plugins/songs/test_propresenterimport.py index bc313e250..3b79961f9 100644 --- a/tests/functional/openlp_plugins/songs/test_propresenterimport.py +++ b/tests/functional/openlp_plugins/songs/test_propresenterimport.py @@ -48,7 +48,7 @@ class TestProPresenterFileImport(SongImportTestHelper): def test_song_import(self): """ - Test that loading an ProPresenter file works correctly + Test that loading a ProPresenter file works correctly """ self.file_import([os.path.join(TEST_PATH, 'Amazing Grace.pro4')], self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json'))) diff --git a/tests/helpers/songfileimport.py b/tests/helpers/songfileimport.py index 18e7914b9..01bfafdd8 100644 --- a/tests/helpers/songfileimport.py +++ b/tests/helpers/songfileimport.py @@ -31,10 +31,13 @@ The :mod:`songfileimporthelper` modules provides a helper class and methods to e song files from third party applications. """ import json +import logging from unittest import TestCase from tests.functional import patch, MagicMock, call +log = logging.getLogger(__name__) + class SongImportTestHelper(TestCase): """ @@ -108,9 +111,21 @@ class SongImportTestHelper(TestCase): topics = self._get_data(result_data, 'topics') verse_order_list = self._get_data(result_data, 'verse_order_list') - # THEN: do_import should return none, the song data should be as expected, and finish should have been - # called. + # 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') + + # Debug information - will be displayed when the test fails + log.debug("Title imported: %s" % importer.title) + log.debug("Verses imported: %s" % self.mocked_add_verse.mock_calls) + log.debug("Verse order imported: %s" % importer.verse_order_list) + log.debug("Authors imported: %s" % self.mocked_add_author.mock_calls) + log.debug("CCLI No. imported: %s" % importer.ccli_number) + log.debug("Comments imported: %s" % importer.comments) + log.debug("Songbook imported: %s" % importer.song_book_name) + log.debug("Song number imported: %s" % importer.song_number) + log.debug("Song copyright imported: %s" % importer.song_number) + log.debug("Topics imported: %s" % importer.topics) + self.assertEqual(importer.title, title, 'title for %s should be "%s"' % (source_file_name, title)) for author in author_calls: self.mocked_add_author.assert_any_call(author) diff --git a/tests/resources/powerpraisesongs/Näher, mein Gott zu Dir.json b/tests/resources/powerpraisesongs/Näher, mein Gott zu Dir.json new file mode 100644 index 000000000..630b71949 --- /dev/null +++ b/tests/resources/powerpraisesongs/Näher, mein Gott zu Dir.json @@ -0,0 +1,18 @@ +{ + "title": "Näher, mein Gott, zu Dir", + "verse_order_list": ["v1", "v2", "v3"], + "verses": [ + [ + "Näher, mein Gott, zu Dir,\nsei meine Bitt'!\nNäher, o Herr, zu Dir\nmit jedem Schritt.\nNur an dem Herzen Dein\nkann ich geborgen sein;\ndeshalb die Bitte mein:\nNäher zu Dir!", + "v1" + ], + [ + "Näher, mein Gott, zu Dir!\nEin jeder Tag\nsoll es neu zeigen mir,\nwas er vermag:\nWie seiner Gnade Macht,\nErlösung hat gebracht,\nin uns're Sündennacht.\nNäher zu Dir!", + "v2" + ], + [ + "Näher, mein Gott, zu Dir!\nDich bet' ich an.\nWie vieles hast an mir,\nDu doch getan!\nVon Banden frei und los,\nruh' ich in Deinem Schoss.\nJa, Deine Gnad' ist gross!\nNäher zu Dir!", + "v3" + ] + ] +} diff --git a/tests/resources/powerpraisesongs/Näher, mein Gott zu Dir.ppl b/tests/resources/powerpraisesongs/Näher, mein Gott zu Dir.ppl new file mode 100644 index 000000000..c0c7f8c19 --- /dev/null +++ b/tests/resources/powerpraisesongs/Näher, mein Gott zu Dir.ppl @@ -0,0 +1,2 @@ + +Näher, mein Gott, zu DirAnbetungDeutschNäher, mein Gott, zu Dir,sei meine Bitt'!Näher, o Herr, zu Dirmit jedem Schritt.Nur an dem Herzen Deinkann ich geborgen sein;deshalb die Bitte mein:Näher zu Dir!Näher, mein Gott, zu Dir!Ein jeder Tagsoll es neu zeigen mir,was er vermag:Wie seiner Gnade Macht,Erlösung hat gebracht,in uns're Sündennacht.Näher zu Dir!Näher, mein Gott, zu Dir!Dich bet' ich an.Wie vieles hast an mir,Du doch getan!Von Banden frei und los,ruh' ich in Deinem Schoss.Ja, Deine Gnad' ist gross!Näher zu Dir!Teil 1Teil 2Teil 3lastslideText und Musik: Lowell Mason, 1792-1872firstslidegrünes Buch 339Times New Roman44truetrue167772153015Times New Roman20falsefalse167772153020Times New Roman14falsefalse167772153020Times New Roman30falsefalse167772153020false0true0125Blumen\Blume 3.jpg
30
20
leftcenterinline50406070302040
diff --git a/tests/resources/powerpraisesongs/You are so faithful.json b/tests/resources/powerpraisesongs/You are so faithful.json new file mode 100644 index 000000000..855b52f67 --- /dev/null +++ b/tests/resources/powerpraisesongs/You are so faithful.json @@ -0,0 +1,26 @@ +{ + "title": "You are so faithful", + "verse_order_list": ["v1", "c1", "v2", "c1", "v3", "c1", "v4"], + "verses": [ + [ + "You are so faithful\nso faithful, so faithful.\nYou are so faithful\nso faithful, so faithful.", + "v1" + ], + [ + "That's why I praise you\nin the morning\nThat's why I praise you\nin the noontime.\nThat's why I praise you\nin the evening\nThat's why I praise you\nall the time.", + "c1" + ], + [ + "You are so loving\nso loving, so loving.\nYou are so loving\nso loving, so loving.", + "v2" + ], + [ + "You are so caring\nso caring, so caring.\nYou are so caring\nso caring, so caring.", + "v3" + ], + [ + "You are so mighty\nso mighty, so mighty.\nYou are so mighty\nso mighty, so mighty.", + "v4" + ] + ] +} diff --git a/tests/resources/powerpraisesongs/You are so faithful.ppl b/tests/resources/powerpraisesongs/You are so faithful.ppl new file mode 100644 index 000000000..420ae8fb6 --- /dev/null +++ b/tests/resources/powerpraisesongs/You are so faithful.ppl @@ -0,0 +1,2 @@ + +You are so faithfulLobpreisEnglischYou are so faithfulso faithful, so faithful.Du bist so treuso treu, so treu.You are so faithfulso faithful, so faithful.Du bist so treuso treu, so treu.That's why I praise youin the morningThat's why I praise youin the noontime.Deshalb preise ich Dicham MorgenDeshalb preise ich Dicham Mittag.That's why I praise youin the eveningThat's why I praise youall the time.Deshalb preise ich Dicham AbendDeshalb preise ich Dichallezeit.You are so lovingso loving, so loving.Du bist so liebevollso liebevoll, so liebevoll.You are so lovingso loving, so loving.Du bist so liebevollso liebevoll, so liebevoll.You are so caringso caring, so caring.Du sorgst so gutDu kümmerst dich um uns.You are so caringso caring, so caring.Du sorgst so gutDu kümmerst dich um uns.You are so mightyso mighty, so mighty.Du bist so mächtigso mächtig, so mächtig.You are so mightyso mighty, so mighty.Du bist so mächtigso mächtig, so mächtig.Strophe 1RefrainStrophe 2RefrainStrophe 3RefrainStrophe 4lastslideMusik & Copyright unbekanntfirstslideTahoma30truefalse167772153020Tahoma20falsefalse167772153020Tahoma14falsefalse167772153020Tahoma30falsefalse167772153020true0true0125Blumen\Blume 6.jpg
30
20
centercenterinline50406070302040
diff --git a/tests/resources/presentationmanagersongs/Great Is Thy Faithfulness.json b/tests/resources/presentationmanagersongs/Great Is Thy Faithfulness.json new file mode 100644 index 000000000..1e484b11b --- /dev/null +++ b/tests/resources/presentationmanagersongs/Great Is Thy Faithfulness.json @@ -0,0 +1,25 @@ +{ + "title": "Great Is Thy Faithfulness", + "authors": [ + "Thomas O. Chisholm (1866-1960)" + ], + "verse_order_list": ["v1", "c1", "v2", "c1", "v3", "c1"], + "verses": [ + [ + "\"Great is Thy faithfulness\", O God my Father.\nThere is no shadow of turning with Thee;\nThou changest not, Thy compassions they fail not,\nAs Thou hast been Thou forever shall be.", + "v1" + ], + [ + "Great is Thy faithfulness!\nGreat is Thy faithfulness!\nMorning by morning new mercies I see!\nAll I have needed Thy hand hath provided -\n\"Great is Thy faithfulness\", Lord, unto me!", + "c1" + ], + [ + "Summer and winter, and springtime and harvest,\nSun, moon, and stars in their courses above,\nJoin with all nature in manifold witness,\nTo Thy great faithfulness, mercy and love.", + "v2" + ], + [ + "Pardon for sin and a peace that endureth,\nThine own dear presence to cheer and to guide,\nStrength for today and bright hope for tomorrow,\nBlessings all mine, with ten thousand beside!", + "v3" + ] + ] +} diff --git a/tests/resources/presentationmanagersongs/Great Is Thy Faithfulness.sng b/tests/resources/presentationmanagersongs/Great Is Thy Faithfulness.sng new file mode 100644 index 000000000..49b29c4c7 --- /dev/null +++ b/tests/resources/presentationmanagersongs/Great Is Thy Faithfulness.sng @@ -0,0 +1,51 @@ + + + +Great Is Thy Faithfulness +Thomas O. Chisholm (1866-1960) + + + + + + +"Great is Thy faithfulness", O God my Father. +There is no shadow of turning with Thee; +Thou changest not, Thy compassions they fail not, +As Thou hast been Thou forever shall be. + + +Great is Thy faithfulness! +Great is Thy faithfulness! +Morning by morning new mercies I see! +All I have needed Thy hand hath provided - +"Great is Thy faithfulness", Lord, unto me! + + +Summer and winter, and springtime and harvest, +Sun, moon, and stars in their courses above, +Join with all nature in manifold witness, +To Thy great faithfulness, mercy and love. + + +Great is Thy faithfulness! +Great is Thy faithfulness! +Morning by morning new mercies I see! +All I have needed Thy hand hath provided - +"Great is Thy faithfulness", Lord, unto me! + + +Pardon for sin and a peace that endureth, +Thine own dear presence to cheer and to guide, +Strength for today and bright hope for tomorrow, +Blessings all mine, with ten thousand beside! + + +Great is Thy faithfulness! +Great is Thy faithfulness! +Morning by morning new mercies I see! +All I have needed Thy hand hath provided - +"Great is Thy faithfulness", Lord, unto me! + + + diff --git a/tests/resources/themes/Default/Default.xml b/tests/resources/themes/Default/Default.xml new file mode 100644 index 000000000..d77731005 --- /dev/null +++ b/tests/resources/themes/Default/Default.xml @@ -0,0 +1,34 @@ + + + Default + + #000000 + + + Arial + #FFFFFF + 40 + False + False + 0 + + True + False + + + Arial + #FFFFFF + 12 + False + False + 0 + + True + False + + + 0 + 0 + False + +