Merge trunk

This commit is contained in:
Samuel Mehrbrodt 2014-07-14 18:48:48 +02:00
commit d5a3651c95
21 changed files with 873 additions and 31 deletions

View File

@ -1103,7 +1103,7 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtGui.QWidget, Ui_ServiceManage
Moves the cursor selection up the window. Called by the up arrow. Moves the cursor selection up the window. Called by the up arrow.
""" """
item = self.service_manager_list.currentItem() 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: if item_before is None:
return return
self.service_manager_list.setCurrentItem(item_before) self.service_manager_list.setCurrentItem(item_before)

View File

@ -96,6 +96,8 @@ class DisplayController(QtGui.QWidget):
""" """
This is the generic function to send signal for control widgets, created from within other plugins This is the generic function to send signal for control widgets, created from within other plugins
This function is needed to catch the current controller This function is needed to catch the current controller
:param args: Arguments to send to the plugins
""" """
sender = self.sender().objectName() if self.sender().objectName() else self.sender().text() sender = self.sender().objectName() if self.sender().objectName() else self.sender().text()
controller = self controller = self
@ -143,11 +145,19 @@ class SlideController(DisplayController, RegistryProperties):
self.panel_layout = QtGui.QVBoxLayout(self.panel) self.panel_layout = QtGui.QVBoxLayout(self.panel)
self.panel_layout.setSpacing(0) self.panel_layout.setSpacing(0)
self.panel_layout.setMargin(0) self.panel_layout.setMargin(0)
# Type label for the top of the slide controller # Type label at the top of the slide controller
self.type_label = QtGui.QLabel(self.panel) self.type_label = QtGui.QLabel(self.panel)
self.type_label.setStyleSheet('font-weight: bold; font-size: 12pt;') self.type_label.setStyleSheet('font-weight: bold; font-size: 12pt;')
self.type_label.setAlignment(QtCore.Qt.AlignCenter) self.type_label.setAlignment(QtCore.Qt.AlignCenter)
if self.is_live:
self.type_label.setText(UiStrings().Live)
else:
self.type_label.setText(UiStrings().Preview)
self.panel_layout.addWidget(self.type_label) self.panel_layout.addWidget(self.type_label)
# Info label for the title of the current item, at the top of the slide controller
self.info_label = QtGui.QLabel(self.panel)
self.info_label.setAlignment(QtCore.Qt.AlignCenter)
self.panel_layout.addWidget(self.info_label)
# Splitter # Splitter
self.splitter = QtGui.QSplitter(self.panel) self.splitter = QtGui.QSplitter(self.panel)
self.splitter.setOrientation(QtCore.Qt.Vertical) self.splitter.setOrientation(QtCore.Qt.Vertical)
@ -402,12 +412,17 @@ class SlideController(DisplayController, RegistryProperties):
""" """
try: try:
from openlp.plugins.songs.lib import VerseType from openlp.plugins.songs.lib import VerseType
SONGS_PLUGIN_AVAILABLE = True is_songs_plugin_available = True
except ImportError: except ImportError:
SONGS_PLUGIN_AVAILABLE = False class VerseType(object):
"""
This empty class is mostly just to satisfy Python, PEP8 and PyCharm
"""
pass
is_songs_plugin_available = False
sender_name = self.sender().objectName() sender_name = self.sender().objectName()
verse_type = sender_name[15:] if sender_name[:15] == 'shortcutAction_' else '' verse_type = sender_name[15:] if sender_name[:15] == 'shortcutAction_' else ''
if SONGS_PLUGIN_AVAILABLE: if is_songs_plugin_available:
if verse_type == 'V': if verse_type == 'V':
self.current_shortcut = VerseType.translated_tags[VerseType.Verse] self.current_shortcut = VerseType.translated_tags[VerseType.Verse]
elif verse_type == 'C': elif verse_type == 'C':
@ -777,6 +792,7 @@ class SlideController(DisplayController, RegistryProperties):
if service_item.is_command(): if service_item.is_command():
Registry().execute( Registry().execute(
'%s_start' % service_item.name.lower(), [self.service_item, self.is_live, self.hide_mode(), slide_no]) '%s_start' % service_item.name.lower(), [self.service_item, self.is_live, self.hide_mode(), slide_no])
self.info_label.setText(self.service_item.title)
self.slide_list = {} self.slide_list = {}
if self.is_live: if self.is_live:
self.song_menu.menu().clear() self.song_menu.menu().clear()

View File

@ -138,6 +138,9 @@ class Ui_EditSongDialog(object):
self.author_remove_layout = QtGui.QHBoxLayout() self.author_remove_layout = QtGui.QHBoxLayout()
self.author_remove_layout.setObjectName('author_remove_layout') self.author_remove_layout.setObjectName('author_remove_layout')
self.author_remove_layout.addStretch() 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 = QtGui.QPushButton(self.authors_group_box)
self.author_remove_button.setObjectName('author_remove_button') self.author_remove_button.setObjectName('author_remove_button')
self.author_remove_layout.addWidget(self.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')) translate('SongsPlugin.EditSongForm', 'Title && Lyrics'))
self.authors_group_box.setTitle(SongStrings.Authors) self.authors_group_box.setTitle(SongStrings.Authors)
self.author_add_button.setText(translate('SongsPlugin.EditSongForm', '&Add to Song')) 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.author_remove_button.setText(translate('SongsPlugin.EditSongForm', '&Remove'))
self.maintenance_button.setText(translate('SongsPlugin.EditSongForm', '&Manage Authors, Topics, Song Books')) self.maintenance_button.setText(translate('SongsPlugin.EditSongForm', '&Manage Authors, Topics, Song Books'))
self.topics_group_box.setTitle(SongStrings.Topic) self.topics_group_box.setTitle(SongStrings.Topic)

View File

@ -70,6 +70,7 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog, RegistryProperties):
self.setupUi(self) self.setupUi(self)
# Connecting signals and slots # Connecting signals and slots
self.author_add_button.clicked.connect(self.on_author_add_button_clicked) 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.author_remove_button.clicked.connect(self.on_author_remove_button_clicked)
self.authors_list_view.itemClicked.connect(self.on_authors_list_view_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) 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_edit_button.setEnabled(False)
self.verse_delete_button.setEnabled(False) self.verse_delete_button.setEnabled(False)
self.author_edit_button.setEnabled(False)
self.author_remove_button.setEnabled(False) self.author_remove_button.setEnabled(False)
self.topic_remove_button.setEnabled(False) self.topic_remove_button.setEnabled(False)
@ -354,12 +356,9 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog, RegistryProperties):
# Types # Types
self.author_types_combo_box.clear() self.author_types_combo_box.clear()
self.author_types_combo_box.addItem('')
# Don't iterate over the dictionary to give them this specific order # Don't iterate over the dictionary to give them this specific order
self.author_types_combo_box.addItem(AuthorType.Types[AuthorType.Words], AuthorType.Words) for author_type in AuthorType.SortedTypes:
self.author_types_combo_box.addItem(AuthorType.Types[AuthorType.Music], AuthorType.Music) self.author_types_combo_box.addItem(AuthorType.Types[author_type], author_type)
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): 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). 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) 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): def on_author_remove_button_clicked(self):
""" """
Remove the author from the list when the delete button is clicked. Remove the author from the list when the delete button is clicked.

View File

@ -69,17 +69,42 @@ class AuthorType(object):
The 'words+music' type is not an official type, but is provided for convenience. The 'words+music' type is not an official type, but is provided for convenience.
""" """
NoType = ''
Words = 'words' Words = 'words'
Music = 'music' Music = 'music'
WordsAndMusic = 'words+music' WordsAndMusic = 'words+music'
Translation = 'translation' Translation = 'translation'
Types = { Types = {
NoType: '',
Words: translate('SongsPlugin.AuthorType', 'Words', 'Author who wrote the lyrics of a song'), 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'), Music: translate('SongsPlugin.AuthorType', 'Music', 'Author who wrote the music of a song'),
WordsAndMusic: translate('SongsPlugin.AuthorType', 'Words and Music', WordsAndMusic: translate('SongsPlugin.AuthorType', 'Words and Music',
'Author who wrote both lyrics and music of a song'), 'Author who wrote both lyrics and music of a song'),
Translation: translate('SongsPlugin.AuthorType', 'Translation', 'Author who translated the 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): class Book(BaseModel):

View File

@ -51,6 +51,7 @@ from .importers.foilpresenter import FoilPresenterImport
from .importers.zionworx import ZionWorxImport from .importers.zionworx import ZionWorxImport
from .importers.propresenter import ProPresenterImport from .importers.propresenter import ProPresenterImport
from .importers.worshipassistant import WorshipAssistantImport from .importers.worshipassistant import WorshipAssistantImport
from .importers.powerpraise import PowerPraiseImport
from .importers.presentationmanager import PresentationManagerImport from .importers.presentationmanager import PresentationManagerImport
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -159,8 +160,13 @@ class SongFormat(object):
FoilPresenter = 8 FoilPresenter = 8
MediaShout = 9 MediaShout = 9
OpenSong = 10 OpenSong = 10
<<<<<<< TREE
PowerSong = 11 PowerSong = 11
PresentationManager = 12 PresentationManager = 12
=======
PowerPraise = 11
PowerSong = 12
>>>>>>> MERGE-SOURCE
ProPresenter = 13 ProPresenter = 13
SongBeamer = 14 SongBeamer = 14
SongPro = 15 SongPro = 15
@ -266,6 +272,12 @@ class SongFormat(object):
'name': WizardStrings.OS, 'name': WizardStrings.OS,
'prefix': 'openSong' 'prefix': 'openSong'
}, },
PowerPraise: {
'class': PowerPraiseImport,
'name': 'PowerPraise',
'prefix': 'powerPraise',
'filter': '%s (*.ppl)' % translate('SongsPlugin.ImportWizardForm', 'PowerPraise Song Files')
},
PowerSong: { PowerSong: {
'class': PowerSongImport, 'class': PowerSongImport,
'name': 'PowerSong 1.0', 'name': 'PowerSong 1.0',
@ -380,6 +392,7 @@ class SongFormat(object):
SongFormat.FoilPresenter, SongFormat.FoilPresenter,
SongFormat.MediaShout, SongFormat.MediaShout,
SongFormat.OpenSong, SongFormat.OpenSong,
SongFormat.PowerPraise,
SongFormat.PowerSong, SongFormat.PowerSong,
SongFormat.PresentationManager, SongFormat.PresentationManager,
SongFormat.ProPresenter, SongFormat.ProPresenter,

View File

@ -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)

View File

@ -36,7 +36,7 @@ from PyQt4 import QtCore, QtGui
from sqlalchemy.sql import or_ from sqlalchemy.sql import or_
from openlp.core.common import Registry, AppLocation, Settings, check_directory_exists, UiStrings, translate from openlp.core.common import Registry, AppLocation, Settings, check_directory_exists, UiStrings, translate
from openlp.core.lib import MediaManagerItem, ItemCapabilities, PluginStatus, ServiceItem, ServiceItemContext, \ from openlp.core.lib import MediaManagerItem, ItemCapabilities, PluginStatus, ServiceItemContext, \
check_item_selected, create_separated_list check_item_selected, create_separated_list
from openlp.core.lib.ui import create_widget_action from openlp.core.lib.ui import create_widget_action
from openlp.plugins.songs.forms.editsongform import EditSongForm from openlp.plugins.songs.forms.editsongform import EditSongForm
@ -126,6 +126,7 @@ class SongMediaItem(MediaManagerItem):
self.update_service_on_edit = Settings().value(self.settings_section + '/update service on edit') self.update_service_on_edit = Settings().value(self.settings_section + '/update service on edit')
self.add_song_from_service = Settings().value(self.settings_section + '/add song from service') self.add_song_from_service = Settings().value(self.settings_section + '/add song from service')
self.display_songbook = Settings().value(self.settings_section + '/display songbook') self.display_songbook = Settings().value(self.settings_section + '/display songbook')
self.display_copyright_symbol = Settings().value(self.settings_section + '/display copyright symbol')
def retranslateUi(self): def retranslateUi(self):
self.search_text_label.setText('%s:' % UiStrings().Search) self.search_text_label.setText('%s:' % UiStrings().Search)
@ -506,7 +507,11 @@ class SongMediaItem(MediaManagerItem):
if authors_translation: if authors_translation:
item.raw_footer.append("%s: %s" % (AuthorType.Types[AuthorType.Translation], item.raw_footer.append("%s: %s" % (AuthorType.Types[AuthorType.Translation],
create_separated_list(authors_translation))) create_separated_list(authors_translation)))
item.raw_footer.append(song.copyright) if song.copyright:
if self.display_copyright_symbol:
item.raw_footer.append("%s %s" % (SongStrings.CopyrightSymbol, song.copyright))
else:
item.raw_footer.append(song.copyright)
if self.display_songbook and song.book: if self.display_songbook and song.book:
item.raw_footer.append("%s #%s" % (song.book.name, song.song_number)) item.raw_footer.append("%s #%s" % (song.book.name, song.song_number))
if Settings().value('core/ccli number'): if Settings().value('core/ccli number'):

View File

@ -31,6 +31,7 @@ from PyQt4 import QtCore, QtGui
from openlp.core.common import Settings, translate from openlp.core.common import Settings, translate
from openlp.core.lib import SettingsTab from openlp.core.lib import SettingsTab
from openlp.plugins.songs.lib.ui import SongStrings
class SongsTab(SettingsTab): class SongsTab(SettingsTab):
@ -62,6 +63,9 @@ class SongsTab(SettingsTab):
self.display_songbook_check_box = QtGui.QCheckBox(self.mode_group_box) self.display_songbook_check_box = QtGui.QCheckBox(self.mode_group_box)
self.display_songbook_check_box.setObjectName('songbook_check_box') self.display_songbook_check_box.setObjectName('songbook_check_box')
self.mode_layout.addWidget(self.display_songbook_check_box) self.mode_layout.addWidget(self.display_songbook_check_box)
self.display_copyright_check_box = QtGui.QCheckBox(self.mode_group_box)
self.display_copyright_check_box.setObjectName('copyright_check_box')
self.mode_layout.addWidget(self.display_copyright_check_box)
self.left_layout.addWidget(self.mode_group_box) self.left_layout.addWidget(self.mode_group_box)
self.left_layout.addStretch() self.left_layout.addStretch()
self.right_layout.addStretch() self.right_layout.addStretch()
@ -70,6 +74,7 @@ class SongsTab(SettingsTab):
self.update_on_edit_check_box.stateChanged.connect(self.on_update_on_edit_check_box_changed) self.update_on_edit_check_box.stateChanged.connect(self.on_update_on_edit_check_box_changed)
self.add_from_service_check_box.stateChanged.connect(self.on_add_from_service_check_box_changed) self.add_from_service_check_box.stateChanged.connect(self.on_add_from_service_check_box_changed)
self.display_songbook_check_box.stateChanged.connect(self.on_songbook_check_box_changed) self.display_songbook_check_box.stateChanged.connect(self.on_songbook_check_box_changed)
self.display_copyright_check_box.stateChanged.connect(self.on_copyright_check_box_changed)
def retranslateUi(self): def retranslateUi(self):
self.mode_group_box.setTitle(translate('SongsPlugin.SongsTab', 'Songs Mode')) self.mode_group_box.setTitle(translate('SongsPlugin.SongsTab', 'Songs Mode'))
@ -80,6 +85,9 @@ class SongsTab(SettingsTab):
self.add_from_service_check_box.setText(translate('SongsPlugin.SongsTab', self.add_from_service_check_box.setText(translate('SongsPlugin.SongsTab',
'Import missing songs from service files')) 'Import missing songs from service files'))
self.display_songbook_check_box.setText(translate('SongsPlugin.SongsTab', 'Display songbook in footer')) self.display_songbook_check_box.setText(translate('SongsPlugin.SongsTab', 'Display songbook in footer'))
self.display_copyright_check_box.setText(translate('SongsPlugin.SongsTab',
'Display "%s" symbol before copyright info' %
SongStrings.CopyrightSymbol))
def on_search_as_type_check_box_changed(self, check_state): def on_search_as_type_check_box_changed(self, check_state):
self.song_search = (check_state == QtCore.Qt.Checked) self.song_search = (check_state == QtCore.Qt.Checked)
@ -96,6 +104,9 @@ class SongsTab(SettingsTab):
def on_songbook_check_box_changed(self, check_state): def on_songbook_check_box_changed(self, check_state):
self.display_songbook = (check_state == QtCore.Qt.Checked) self.display_songbook = (check_state == QtCore.Qt.Checked)
def on_copyright_check_box_changed(self, check_state):
self.display_copyright_symbol = (check_state == QtCore.Qt.Checked)
def load(self): def load(self):
settings = Settings() settings = Settings()
settings.beginGroup(self.settings_section) settings.beginGroup(self.settings_section)
@ -104,11 +115,13 @@ class SongsTab(SettingsTab):
self.update_edit = settings.value('update service on edit') self.update_edit = settings.value('update service on edit')
self.update_load = settings.value('add song from service') self.update_load = settings.value('add song from service')
self.display_songbook = settings.value('display songbook') self.display_songbook = settings.value('display songbook')
self.display_copyright_symbol = settings.value('display copyright symbol')
self.search_as_type_check_box.setChecked(self.song_search) self.search_as_type_check_box.setChecked(self.song_search)
self.tool_bar_active_check_box.setChecked(self.tool_bar) self.tool_bar_active_check_box.setChecked(self.tool_bar)
self.update_on_edit_check_box.setChecked(self.update_edit) self.update_on_edit_check_box.setChecked(self.update_edit)
self.add_from_service_check_box.setChecked(self.update_load) self.add_from_service_check_box.setChecked(self.update_load)
self.display_songbook_check_box.setChecked(self.display_songbook) self.display_songbook_check_box.setChecked(self.display_songbook)
self.display_copyright_check_box.setChecked(self.display_copyright_symbol)
settings.endGroup() settings.endGroup()
def save(self): def save(self):
@ -119,6 +132,7 @@ class SongsTab(SettingsTab):
settings.setValue('update service on edit', self.update_edit) settings.setValue('update service on edit', self.update_edit)
settings.setValue('add song from service', self.update_load) settings.setValue('add song from service', self.update_load)
settings.setValue('display songbook', self.display_songbook) settings.setValue('display songbook', self.display_songbook)
settings.setValue('display copyright symbol', self.display_copyright_symbol)
settings.endGroup() settings.endGroup()
if self.tab_visited: if self.tab_visited:
self.settings_form.register_post_process('songs_config_updated') self.settings_form.register_post_process('songs_config_updated')

View File

@ -64,6 +64,7 @@ __default_settings__ = {
'songs/add song from service': True, 'songs/add song from service': True,
'songs/display songbar': True, 'songs/display songbar': True,
'songs/display songbook': False, 'songs/display songbook': False,
'songs/display copyright symbol': False,
'songs/last directory import': '', 'songs/last directory import': '',
'songs/last directory export': '', 'songs/last directory export': '',
'songs/songselect username': '', 'songs/songselect username': '',

View File

@ -30,10 +30,13 @@
Package to test the openlp.core.ui.slidecontroller package. Package to test the openlp.core.ui.slidecontroller package.
""" """
from unittest import TestCase from unittest import TestCase
from openlp.core import Registry
from openlp.core.lib import ServiceItemAction
from openlp.core.ui import SlideController from openlp.core.ui import SlideController
from openlp.core.ui.slidecontroller import WIDE_MENU, NON_TEXT_MENU
from tests.interfaces import MagicMock from tests.interfaces import MagicMock, patch
class TestSlideController(TestCase): class TestSlideController(TestCase):
@ -42,37 +45,514 @@ class TestSlideController(TestCase):
""" """
Test the initial slide controller state . Test the initial slide controller state .
""" """
# GIVEN: A new slideController instance. # GIVEN: A new SlideController instance.
slide_controller = SlideController(None) slide_controller = SlideController(None)
# WHEN: the default controller is built. # WHEN: the default controller is built.
# THEN: The controller should not be a live controller. # THEN: The controller should not be a live controller.
self.assertEqual(slide_controller.is_live, False, 'The base slide controller should not be a live controller') self.assertEqual(slide_controller.is_live, False, 'The base slide controller should not be a live controller')
def toggle_blank_test(self): def text_service_item_blank_test(self):
""" """
Test the setting of the display blank icons by display type. Test that loading a text-based service item into the slide controller sets the correct blank menu
""" """
# GIVEN: A new slideController instance. # GIVEN: A new SlideController instance.
slide_controller = SlideController(None) slide_controller = SlideController(None)
service_item = MagicMock() service_item = MagicMock()
toolbar = MagicMock() toolbar = MagicMock()
toolbar.set_widget_visible = self.dummy_widget_visible toolbar.set_widget_visible = MagicMock()
slide_controller.toolbar = toolbar slide_controller.toolbar = toolbar
slide_controller.service_item = service_item slide_controller.service_item = service_item
# WHEN a text based service item is used # WHEN: a text based service item is used
slide_controller.service_item.is_text = MagicMock(return_value=True) slide_controller.service_item.is_text = MagicMock(return_value=True)
slide_controller.set_blank_menu() slide_controller.set_blank_menu()
# THEN: then call set up the toolbar to blank the display screen. # THEN: the call to set the visible items on the toolbar should be correct
self.assertEqual(len(self.test_widget), 3, 'There should be three icons to display on the screen') toolbar.set_widget_visible.assert_called_with(WIDE_MENU, True)
def non_text_service_item_blank_test(self):
"""
Test that loading a non-text service item into the slide controller sets the correct blank menu
"""
# GIVEN: A new SlideController instance.
slide_controller = SlideController(None)
service_item = MagicMock()
toolbar = MagicMock()
toolbar.set_widget_visible = MagicMock()
slide_controller.toolbar = toolbar
slide_controller.service_item = service_item
# WHEN a non text based service item is used # WHEN a non text based service item is used
slide_controller.service_item.is_text = MagicMock(return_value=False) slide_controller.service_item.is_text = MagicMock(return_value=False)
slide_controller.set_blank_menu() slide_controller.set_blank_menu()
# THEN: then call set up the toolbar to blank the display screen. # THEN: then call set up the toolbar to blank the display screen.
self.assertEqual(len(self.test_widget), 2, 'There should be only two icons to display on the screen') toolbar.set_widget_visible.assert_called_with(NON_TEXT_MENU, True)
def dummy_widget_visible(self, widget, visible=True): def receive_spin_delay_test(self):
self.test_widget = widget """
Test that the spin box is updated accordingly after a call to receive_spin_delay()
"""
with patch('openlp.core.ui.slidecontroller.Settings') as MockedSettings:
# GIVEN: A new SlideController instance.
mocked_value = MagicMock(return_value=1)
MockedSettings.return_value = MagicMock(value=mocked_value)
mocked_delay_spin_box = MagicMock()
slide_controller = SlideController(None)
slide_controller.delay_spin_box = mocked_delay_spin_box
# WHEN: The receive_spin_delay() method is called
slide_controller.receive_spin_delay()
# THEN: The Settings()value() and delay_spin_box.setValue() methods should have been called correctly
mocked_value.assert_called_with('core/loop delay')
mocked_delay_spin_box.setValue.assert_called_with(1)
def toggle_display_blank_test(self):
"""
Check that the toggle_display('blank') method calls the on_blank_display() method
"""
# GIVEN: A new SlideController instance.
mocked_on_blank_display = MagicMock()
mocked_on_theme_display = MagicMock()
mocked_on_hide_display = MagicMock()
slide_controller = SlideController(None)
slide_controller.on_blank_display = mocked_on_blank_display
slide_controller.on_theme_display = mocked_on_theme_display
slide_controller.on_hide_display = mocked_on_hide_display
# WHEN: toggle_display() is called with an argument of "blank"
slide_controller.toggle_display('blank')
# THEN: Only on_blank_display() should have been called with an argument of True
mocked_on_blank_display.assert_called_once_with(True)
self.assertEqual(0, mocked_on_theme_display.call_count, 'on_theme_display should not have been called')
self.assertEqual(0, mocked_on_hide_display.call_count, 'on_hide_display should not have been called')
def toggle_display_hide_test(self):
"""
Check that the toggle_display('hide') method calls the on_blank_display() method
"""
# GIVEN: A new SlideController instance.
mocked_on_blank_display = MagicMock()
mocked_on_theme_display = MagicMock()
mocked_on_hide_display = MagicMock()
slide_controller = SlideController(None)
slide_controller.on_blank_display = mocked_on_blank_display
slide_controller.on_theme_display = mocked_on_theme_display
slide_controller.on_hide_display = mocked_on_hide_display
# WHEN: toggle_display() is called with an argument of "hide"
slide_controller.toggle_display('hide')
# THEN: Only on_blank_display() should have been called with an argument of True
mocked_on_blank_display.assert_called_once_with(True)
self.assertEqual(0, mocked_on_theme_display.call_count, 'on_theme_display should not have been called')
self.assertEqual(0, mocked_on_hide_display.call_count, 'on_hide_display should not have been called')
def toggle_display_theme_test(self):
"""
Check that the toggle_display('theme') method calls the on_theme_display() method
"""
# GIVEN: A new SlideController instance.
mocked_on_blank_display = MagicMock()
mocked_on_theme_display = MagicMock()
mocked_on_hide_display = MagicMock()
slide_controller = SlideController(None)
slide_controller.on_blank_display = mocked_on_blank_display
slide_controller.on_theme_display = mocked_on_theme_display
slide_controller.on_hide_display = mocked_on_hide_display
# WHEN: toggle_display() is called with an argument of "theme"
slide_controller.toggle_display('theme')
# THEN: Only on_theme_display() should have been called with an argument of True
mocked_on_theme_display.assert_called_once_with(True)
self.assertEqual(0, mocked_on_blank_display.call_count, 'on_blank_display should not have been called')
self.assertEqual(0, mocked_on_hide_display.call_count, 'on_hide_display should not have been called')
def toggle_display_desktop_test(self):
"""
Check that the toggle_display('desktop') method calls the on_hide_display() method
"""
# GIVEN: A new SlideController instance.
mocked_on_blank_display = MagicMock()
mocked_on_theme_display = MagicMock()
mocked_on_hide_display = MagicMock()
slide_controller = SlideController(None)
slide_controller.on_blank_display = mocked_on_blank_display
slide_controller.on_theme_display = mocked_on_theme_display
slide_controller.on_hide_display = mocked_on_hide_display
# WHEN: toggle_display() is called with an argument of "desktop"
slide_controller.toggle_display('desktop')
# THEN: Only on_hide_display() should have been called with an argument of True
mocked_on_hide_display.assert_called_once_with(True)
self.assertEqual(0, mocked_on_blank_display.call_count, 'on_blank_display should not have been called')
self.assertEqual(0, mocked_on_theme_display.call_count, 'on_theme_display should not have been called')
def toggle_display_show_test(self):
"""
Check that the toggle_display('show') method calls all the on_X_display() methods
"""
# GIVEN: A new SlideController instance.
mocked_on_blank_display = MagicMock()
mocked_on_theme_display = MagicMock()
mocked_on_hide_display = MagicMock()
slide_controller = SlideController(None)
slide_controller.on_blank_display = mocked_on_blank_display
slide_controller.on_theme_display = mocked_on_theme_display
slide_controller.on_hide_display = mocked_on_hide_display
# WHEN: toggle_display() is called with an argument of "show"
slide_controller.toggle_display('show')
# THEN: All the on_X_display() methods should have been called with an argument of False
mocked_on_blank_display.assert_called_once_with(False)
mocked_on_theme_display.assert_called_once_with(False)
mocked_on_hide_display.assert_called_once_with(False)
def live_escape_test(self):
"""
Test that when the live_escape() method is called, the display is set to invisible and any media is stopped
"""
# GIVEN: A new SlideController instance and mocked out display and media_controller
mocked_display = MagicMock()
mocked_media_controller = MagicMock()
Registry.create()
Registry().register('media_controller', mocked_media_controller)
slide_controller = SlideController(None)
slide_controller.display = mocked_display
# WHEN: live_escape() is called
slide_controller.live_escape()
# THEN: the display should be set to invisible and the media controller stopped
mocked_display.setVisible.assert_called_once_with(False)
mocked_media_controller.media_stop.assert_called_once_with(slide_controller)
def service_previous_test(self):
"""
Check that calling the service_previous() method adds the previous key to the queue and processes the queue
"""
# GIVEN: A new SlideController instance.
mocked_keypress_queue = MagicMock()
mocked_process_queue = MagicMock()
slide_controller = SlideController(None)
slide_controller.keypress_queue = mocked_keypress_queue
slide_controller._process_queue = mocked_process_queue
# WHEN: The service_previous() method is called
slide_controller.service_previous()
# THEN: The keypress is added to the queue and the queue is processed
mocked_keypress_queue.append.assert_called_once_with(ServiceItemAction.Previous)
mocked_process_queue.assert_called_once_with()
def service_next_test(self):
"""
Check that calling the service_next() method adds the next key to the queue and processes the queue
"""
# GIVEN: A new SlideController instance and mocked out methods
mocked_keypress_queue = MagicMock()
mocked_process_queue = MagicMock()
slide_controller = SlideController(None)
slide_controller.keypress_queue = mocked_keypress_queue
slide_controller._process_queue = mocked_process_queue
# WHEN: The service_next() method is called
slide_controller.service_next()
# THEN: The keypress is added to the queue and the queue is processed
mocked_keypress_queue.append.assert_called_once_with(ServiceItemAction.Next)
mocked_process_queue.assert_called_once_with()
def update_slide_limits_test(self):
"""
Test that calling the update_slide_limits() method updates the slide limits
"""
# GIVEN: A mocked out Settings object, a new SlideController and a mocked out main_window
with patch('openlp.core.ui.slidecontroller.Settings') as MockedSettings:
mocked_value = MagicMock(return_value=10)
MockedSettings.return_value = MagicMock(value=mocked_value)
mocked_main_window = MagicMock(advanced_settings_section='advanced')
Registry.create()
Registry().register('main_window', mocked_main_window)
slide_controller = SlideController(None)
# WHEN: update_slide_limits() is called
slide_controller.update_slide_limits()
# THEN: The value of slide_limits should be 10
mocked_value.assert_called_once_with('advanced/slide limits')
self.assertEqual(10, slide_controller.slide_limits, 'Slide limits should have been updated to 10')
def enable_tool_bar_live_test(self):
"""
Check that when enable_tool_bar on a live slide controller is called, enable_live_tool_bar is called
"""
# GIVEN: Mocked out enable methods and a real slide controller which is set to live
mocked_enable_live_tool_bar = MagicMock()
mocked_enable_preview_tool_bar = MagicMock()
slide_controller = SlideController(None)
slide_controller.is_live = True
slide_controller.enable_live_tool_bar = mocked_enable_live_tool_bar
slide_controller.enable_preview_tool_bar = mocked_enable_preview_tool_bar
mocked_service_item = MagicMock()
# WHEN: enable_tool_bar() is called
slide_controller.enable_tool_bar(mocked_service_item)
# THEN: The enable_live_tool_bar() method is called, not enable_preview_tool_bar()
mocked_enable_live_tool_bar.assert_called_once_with(mocked_service_item)
self.assertEqual(0, mocked_enable_preview_tool_bar.call_count, 'The preview method should not have been called')
def enable_tool_bar_preview_test(self):
"""
Check that when enable_tool_bar on a preview slide controller is called, enable_preview_tool_bar is called
"""
# GIVEN: Mocked out enable methods and a real slide controller which is set to live
mocked_enable_live_tool_bar = MagicMock()
mocked_enable_preview_tool_bar = MagicMock()
slide_controller = SlideController(None)
slide_controller.is_live = False
slide_controller.enable_live_tool_bar = mocked_enable_live_tool_bar
slide_controller.enable_preview_tool_bar = mocked_enable_preview_tool_bar
mocked_service_item = MagicMock()
# WHEN: enable_tool_bar() is called
slide_controller.enable_tool_bar(mocked_service_item)
# THEN: The enable_preview_tool_bar() method is called, not enable_live_tool_bar()
mocked_enable_preview_tool_bar.assert_called_once_with(mocked_service_item)
self.assertEqual(0, mocked_enable_live_tool_bar.call_count, 'The live method should not have been called')
def refresh_service_item_text_test(self):
"""
Test that the refresh_service_item() method refreshes a text service item
"""
# GIVEN: A mock service item and a fresh slide controller
mocked_service_item = MagicMock()
mocked_service_item.is_text.return_value = True
mocked_service_item.is_image.return_value = False
mocked_process_item = MagicMock()
slide_controller = SlideController(None)
slide_controller.service_item = mocked_service_item
slide_controller._process_item = mocked_process_item
slide_controller.selected_row = 5
# WHEN: The refresh_service_item method() is called
slide_controller.refresh_service_item()
# THEN: The item should be re-processed
mocked_service_item.is_text.assert_called_once_with()
self.assertEqual(0, mocked_service_item.is_image.call_count, 'is_image should not have been called')
mocked_service_item.render.assert_called_once_with()
mocked_process_item.assert_called_once_with(mocked_service_item, 5)
def refresh_service_item_image_test(self):
"""
Test that the refresh_service_item() method refreshes a image service item
"""
# GIVEN: A mock service item and a fresh slide controller
mocked_service_item = MagicMock()
mocked_service_item.is_text.return_value = False
mocked_service_item.is_image.return_value = True
mocked_process_item = MagicMock()
slide_controller = SlideController(None)
slide_controller.service_item = mocked_service_item
slide_controller._process_item = mocked_process_item
slide_controller.selected_row = 5
# WHEN: The refresh_service_item method() is called
slide_controller.refresh_service_item()
# THEN: The item should be re-processed
mocked_service_item.is_text.assert_called_once_with()
mocked_service_item.is_image.assert_called_once_with()
mocked_service_item.render.assert_called_once_with()
mocked_process_item.assert_called_once_with(mocked_service_item, 5)
def refresh_service_item_not_image_or_text_test(self):
"""
Test that the refresh_service_item() method does not refresh a service item if it's neither text or an image
"""
# GIVEN: A mock service item and a fresh slide controller
mocked_service_item = MagicMock()
mocked_service_item.is_text.return_value = False
mocked_service_item.is_image.return_value = False
mocked_process_item = MagicMock()
slide_controller = SlideController(None)
slide_controller.service_item = mocked_service_item
slide_controller._process_item = mocked_process_item
slide_controller.selected_row = 5
# WHEN: The refresh_service_item method() is called
slide_controller.refresh_service_item()
# THEN: The item should be re-processed
mocked_service_item.is_text.assert_called_once_with()
mocked_service_item.is_image.assert_called_once_with()
self.assertEqual(0, mocked_service_item.render.call_count, 'The render() method should not have been called')
self.assertEqual(0, mocked_process_item.call_count,
'The mocked_process_item() method should not have been called')
def add_service_item_with_song_edit_test(self):
"""
Test the add_service_item() method when song_edit is True
"""
# GIVEN: A slide controller and a new item to add
mocked_item = MagicMock()
mocked_process_item = MagicMock()
slide_controller = SlideController(None)
slide_controller._process_item = mocked_process_item
slide_controller.song_edit = True
slide_controller.selected_row = 2
# WHEN: The item is added to the service
slide_controller.add_service_item(mocked_item)
# THEN: The item is processed, the slide number is correct, and the song is not editable (or something)
mocked_item.render.assert_called_once_with()
self.assertFalse(slide_controller.song_edit, 'song_edit should be False')
mocked_process_item.assert_called_once_with(mocked_item, 2)
def add_service_item_without_song_edit_test(self):
"""
Test the add_service_item() method when song_edit is False
"""
# GIVEN: A slide controller and a new item to add
mocked_item = MagicMock()
mocked_process_item = MagicMock()
slide_controller = SlideController(None)
slide_controller._process_item = mocked_process_item
slide_controller.song_edit = False
slide_controller.selected_row = 2
# WHEN: The item is added to the service
slide_controller.add_service_item(mocked_item)
# THEN: The item is processed, the slide number is correct, and the song is not editable (or something)
mocked_item.render.assert_called_once_with()
self.assertFalse(slide_controller.song_edit, 'song_edit should be False')
mocked_process_item.assert_called_once_with(mocked_item, 0)
def replace_service_manager_item_different_items_test(self):
"""
Test that when the service items are not the same, nothing happens
"""
# GIVEN: A slide controller and a new item to add
mocked_item = MagicMock()
mocked_preview_widget = MagicMock()
mocked_process_item = MagicMock()
slide_controller = SlideController(None)
slide_controller.preview_widget = mocked_preview_widget
slide_controller._process_item = mocked_process_item
slide_controller.service_item = None
# WHEN: The service item is replaced
slide_controller.replace_service_manager_item(mocked_item)
# THEN: The service item should not be processed
self.assertEqual(0, mocked_process_item.call_count, 'The _process_item() method should not have been called')
self.assertEqual(0, mocked_preview_widget.current_slide_number.call_count,
'The preview_widgetcurrent_slide_number.() method should not have been called')
def replace_service_manager_item_same_item_test(self):
"""
Test that when the service item is the same, the service item is reprocessed
"""
# GIVEN: A slide controller and a new item to add
mocked_item = MagicMock()
mocked_preview_widget = MagicMock()
mocked_preview_widget.current_slide_number.return_value = 7
mocked_process_item = MagicMock()
slide_controller = SlideController(None)
slide_controller.preview_widget = mocked_preview_widget
slide_controller._process_item = mocked_process_item
slide_controller.service_item = mocked_item
# WHEN: The service item is replaced
slide_controller.replace_service_manager_item(mocked_item)
# THEN: The service item should not be processed
mocked_preview_widget.current_slide_number.assert_called_with()
mocked_process_item.assert_called_once_with(mocked_item, 7)
def on_slide_selected_index_no_service_item_test(self):
"""
Test that when there is no service item, the on_slide_selected_index() method returns immediately
"""
# GIVEN: A mocked service item and a slide controller without a service item
mocked_item = MagicMock()
slide_controller = SlideController(None)
slide_controller.service_item = None
# WHEN: The method is called
slide_controller.on_slide_selected_index([10])
# THEN: It should have exited early
self.assertEqual(0, mocked_item.is_command.call_count, 'The service item should have not been called')
def on_slide_selected_index_service_item_command_test(self):
"""
Test that when there is a command service item, the command is executed
"""
# GIVEN: A mocked service item and a slide controller with a service item
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
# 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()
mocked_execute.assert_called_once_with('mocked item_slide', [mocked_item, True, 9])
mocked_update_preview.assert_called_once_with()
self.assertEqual(0, mocked_preview_widget.change_slide.call_count, 'Change slide should not have been called')
self.assertEqual(0, mocked_slide_selected.call_count, 'slide_selected should not have been called')
def on_slide_selected_index_service_item_not_command_test(self):
"""
Test that when there is a service item but it's not a command, the preview widget is updated
"""
# GIVEN: A mocked service item and a slide controller with a service item
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
# 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()
self.assertEqual(0, mocked_execute.call_count, 'Execute should not have been called')
self.assertEqual(0, mocked_update_preview.call_count, 'Update preview should not have been called')
mocked_preview_widget.change_slide.assert_called_once_with(7)
mocked_slide_selected.assert_called_once_with()

View File

@ -112,3 +112,16 @@ class TestDB(TestCase):
# THEN: It should have been removed and the other author should still be there # THEN: It should have been removed and the other author should still be there
self.assertEqual(1, len(song.authors_songs)) self.assertEqual(1, len(song.authors_songs))
self.assertEqual(None, song.authors_songs[0].author_type) 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)

View File

@ -28,6 +28,7 @@ class TestMediaItem(TestCase, TestMixin):
patch('openlp.plugins.songs.forms.editsongform.EditSongForm.__init__'): patch('openlp.plugins.songs.forms.editsongform.EditSongForm.__init__'):
self.media_item = SongMediaItem(None, MagicMock()) self.media_item = SongMediaItem(None, MagicMock())
self.media_item.display_songbook = False self.media_item.display_songbook = False
self.media_item.display_copyright_symbol = False
self.get_application() self.get_application()
self.build_settings() self.build_settings()
QtCore.QLocale.setDefault(QtCore.QLocale('en_GB')) QtCore.QLocale.setDefault(QtCore.QLocale('en_GB'))
@ -154,6 +155,39 @@ class TestMediaItem(TestCase, TestMixin):
# THEN: The songbook should be in the footer # THEN: The songbook should be in the footer
self.assertEqual(service_item.raw_footer, ['My Song', 'My copyright', 'My songbook #12']) self.assertEqual(service_item.raw_footer, ['My Song', 'My copyright', 'My songbook #12'])
def build_song_footer_copyright_enabled_test(self):
"""
Test building song footer with displaying the copyright symbol
"""
# GIVEN: A Song and a Service Item; displaying the copyright symbol is enabled
self.media_item.display_copyright_symbol = True
mock_song = MagicMock()
mock_song.title = 'My Song'
mock_song.copyright = 'My copyright'
service_item = ServiceItem(None)
# WHEN: I generate the Footer with default settings
self.media_item.generate_footer(service_item, mock_song)
# THEN: The copyright symbol should be in the footer
self.assertEqual(service_item.raw_footer, ['My Song', '© My copyright'])
def build_song_footer_copyright_disabled_test(self):
"""
Test building song footer without displaying the copyright symbol
"""
# GIVEN: A Song and a Service Item; displaying the copyright symbol should be disabled by default
mock_song = MagicMock()
mock_song.title = 'My Song'
mock_song.copyright = 'My copyright'
service_item = ServiceItem(None)
# WHEN: I generate the Footer with default settings
self.media_item.generate_footer(service_item, mock_song)
# THEN: The copyright symbol should not be in the footer
self.assertEqual(service_item.raw_footer, ['My Song', 'My copyright'])
def authors_match_test(self): def authors_match_test(self):
""" """
Test the author matching when importing a song from a service Test the author matching when importing a song from a service

View File

@ -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')))

View File

@ -48,7 +48,7 @@ class TestProPresenterFileImport(SongImportTestHelper):
def test_song_import(self): 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.file_import([os.path.join(TEST_PATH, 'Amazing Grace.pro4')],
self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json'))) self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json')))

View File

@ -31,10 +31,13 @@ The :mod:`songfileimporthelper` modules provides a helper class and methods to e
song files from third party applications. song files from third party applications.
""" """
import json import json
import logging
from unittest import TestCase from unittest import TestCase
from tests.functional import patch, MagicMock, call from tests.functional import patch, MagicMock, call
log = logging.getLogger(__name__)
class SongImportTestHelper(TestCase): class SongImportTestHelper(TestCase):
""" """
@ -108,9 +111,21 @@ class SongImportTestHelper(TestCase):
topics = self._get_data(result_data, 'topics') topics = self._get_data(result_data, 'topics')
verse_order_list = self._get_data(result_data, 'verse_order_list') 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 # THEN: do_import should return none, the song data should be as expected, and finish should have been called.
# called.
self.assertIsNone(importer.do_import(), 'do_import should return None when it has completed') 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)) self.assertEqual(importer.title, title, 'title for %s should be "%s"' % (source_file_name, title))
for author in author_calls: for author in author_calls:
self.mocked_add_author.assert_any_call(author) self.mocked_add_author.assert_any_call(author)

View File

@ -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"
]
]
}

View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<ppl version="3.0"><general><title>Näher, mein Gott, zu Dir</title><category>Anbetung</category><language>Deutsch</language></general><songtext><part caption="Teil 1"><slide mainsize="42" backgroundnr="0"><line>Näher, mein Gott, zu Dir,</line><line>sei meine Bitt'!</line><line>Näher, o Herr, zu Dir</line><line>mit jedem Schritt.</line></slide><slide mainsize="44" backgroundnr="0"><line>Nur an dem Herzen Dein</line><line>kann ich geborgen sein;</line><line>deshalb die Bitte mein:</line><line>Näher zu Dir!</line></slide></part><part caption="Teil 2"><slide mainsize="42" backgroundnr="0"><line>Näher, mein Gott, zu Dir!</line><line>Ein jeder Tag</line><line>soll es neu zeigen mir,</line><line>was er vermag:</line></slide><slide mainsize="42" backgroundnr="0"><line>Wie seiner Gnade Macht,</line><line>Erlösung hat gebracht,</line><line>in uns're Sündennacht.</line><line>Näher zu Dir!</line></slide></part><part caption="Teil 3"><slide mainsize="42" backgroundnr="0"><line>Näher, mein Gott, zu Dir!</line><line>Dich bet' ich an.</line><line>Wie vieles hast an mir,</line><line>Du doch getan!</line></slide><slide mainsize="42" backgroundnr="0"><line>Von Banden frei und los,</line><line>ruh' ich in Deinem Schoss.</line><line>Ja, Deine Gnad' ist gross!</line><line>Näher zu Dir!</line></slide></part></songtext><order><item>Teil 1</item><item>Teil 2</item><item>Teil 3</item></order><information><copyright><position>lastslide</position><text><line>Text und Musik: Lowell Mason, 1792-1872</line></text></copyright><source><position>firstslide</position><text><line>grünes Buch 339</line></text></source></information><formatting><font><maintext><name>Times New Roman</name><size>44</size><bold>true</bold><italic>true</italic><color>16777215</color><outline>30</outline><shadow>15</shadow></maintext><translationtext><name>Times New Roman</name><size>20</size><bold>false</bold><italic>false</italic><color>16777215</color><outline>30</outline><shadow>20</shadow></translationtext><copyrighttext><name>Times New Roman</name><size>14</size><bold>false</bold><italic>false</italic><color>16777215</color><outline>30</outline><shadow>20</shadow></copyrighttext><sourcetext><name>Times New Roman</name><size>30</size><bold>false</bold><italic>false</italic><color>16777215</color><outline>30</outline><shadow>20</shadow></sourcetext><outline><enabled>false</enabled><color>0</color></outline><shadow><enabled>true</enabled><color>0</color><direction>125</direction></shadow></font><background><file>Blumen\Blume 3.jpg</file></background><linespacing><main>30</main><translation>20</translation></linespacing><textorientation><horizontal>left</horizontal><vertical>center</vertical><transpos>inline</transpos></textorientation><borders><mainleft>50</mainleft><maintop>40</maintop><mainright>60</mainright><mainbottom>70</mainbottom><copyrightbottom>30</copyrightbottom><sourcetop>20</sourcetop><sourceright>40</sourceright></borders></formatting></ppl>

View File

@ -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"
]
]
}

View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<ppl version="3.0"><general><title>You are so faithful</title><category>Lobpreis</category><language>Englisch</language></general><songtext><part caption="Strophe 1"><slide mainsize="30" backgroundnr="0"><line>You are so faithful</line><line>so faithful, so faithful.</line><translation>Du bist so treu</translation><translation>so treu, so treu.</translation></slide><slide mainsize="30" backgroundnr="0"><line>You are so faithful</line><line>so faithful, so faithful.</line><translation>Du bist so treu</translation><translation>so treu, so treu.</translation></slide></part><part caption="Refrain"><slide mainsize="30" backgroundnr="0"><line>That's why I praise you</line><line>in the morning</line><line>That's why I praise you</line><line>in the noontime.</line><translation>Deshalb preise ich Dich</translation><translation>am Morgen</translation><translation>Deshalb preise ich Dich</translation><translation>am Mittag.</translation></slide><slide mainsize="30" backgroundnr="0"><line>That's why I praise you</line><line>in the evening</line><line>That's why I praise you</line><line>all the time.</line><translation>Deshalb preise ich Dich</translation><translation>am Abend</translation><translation>Deshalb preise ich Dich</translation><translation>allezeit.</translation></slide></part><part caption="Strophe 2"><slide mainsize="30" backgroundnr="0"><line>You are so loving</line><line>so loving, so loving.</line><translation>Du bist so liebevoll</translation><translation>so liebevoll, so liebevoll.</translation></slide><slide mainsize="30" backgroundnr="0"><line>You are so loving</line><line>so loving, so loving.</line><translation>Du bist so liebevoll</translation><translation>so liebevoll, so liebevoll.</translation></slide></part><part caption="Strophe 3"><slide mainsize="30" backgroundnr="0"><line>You are so caring</line><line>so caring, so caring.</line><translation>Du sorgst so gut</translation><translation>Du kümmerst dich um uns.</translation></slide><slide mainsize="30" backgroundnr="0"><line>You are so caring</line><line>so caring, so caring.</line><translation>Du sorgst so gut</translation><translation>Du kümmerst dich um uns.</translation></slide></part><part caption="Strophe 4"><slide mainsize="30" backgroundnr="0"><line>You are so mighty</line><line>so mighty, so mighty.</line><translation>Du bist so mächtig</translation><translation>so mächtig, so mächtig.</translation></slide><slide mainsize="30" backgroundnr="0"><line>You are so mighty</line><line>so mighty, so mighty.</line><translation>Du bist so mächtig</translation><translation>so mächtig, so mächtig.</translation></slide></part></songtext><order><item>Strophe 1</item><item>Refrain</item><item>Strophe 2</item><item>Refrain</item><item>Strophe 3</item><item>Refrain</item><item>Strophe 4</item></order><information><copyright><position>lastslide</position><text><line>Musik &amp; Copyright unbekannt</line></text></copyright><source><position>firstslide</position><text/></source></information><formatting><font><maintext><name>Tahoma</name><size>30</size><bold>true</bold><italic>false</italic><color>16777215</color><outline>30</outline><shadow>20</shadow></maintext><translationtext><name>Tahoma</name><size>20</size><bold>false</bold><italic>false</italic><color>16777215</color><outline>30</outline><shadow>20</shadow></translationtext><copyrighttext><name>Tahoma</name><size>14</size><bold>false</bold><italic>false</italic><color>16777215</color><outline>30</outline><shadow>20</shadow></copyrighttext><sourcetext><name>Tahoma</name><size>30</size><bold>false</bold><italic>false</italic><color>16777215</color><outline>30</outline><shadow>20</shadow></sourcetext><outline><enabled>true</enabled><color>0</color></outline><shadow><enabled>true</enabled><color>0</color><direction>125</direction></shadow></font><background><file>Blumen\Blume 6.jpg</file></background><linespacing><main>30</main><translation>20</translation></linespacing><textorientation><horizontal>center</horizontal><vertical>center</vertical><transpos>inline</transpos></textorientation><borders><mainleft>50</mainleft><maintop>40</maintop><mainright>60</mainright><mainbottom>70</mainbottom><copyrightbottom>30</copyrightbottom><sourcetop>20</sourcetop><sourceright>40</sourceright></borders></formatting></ppl>

View File

@ -30,7 +30,7 @@
Package to test for proper bzr tags. Package to test for proper bzr tags.
""" """
import os import os
import re
from unittest import TestCase from unittest import TestCase
from subprocess import Popen, PIPE from subprocess import Popen, PIPE
@ -52,6 +52,10 @@ TAGS = [
['2.0', '2118'], ['2.0', '2118'],
['2.1.0', '2119'] ['2.1.0', '2119']
] ]
# Depending on the repository, we sometimes have the 2.0.x tags in the repo too. They come up with a revision number of
# "?", which I suspect is due to the fact that we're using shared repositories. This regular expression matches all
# 2.0.x tags.
TAG_SEARCH = re.compile('2\.0\.\d')
class TestBzrTags(TestCase): class TestBzrTags(TestCase):
@ -65,8 +69,9 @@ class TestBzrTags(TestCase):
# WHEN getting the branches tags # WHEN getting the branches tags
bzr = Popen(('bzr', 'tags', '--directory=' + path), stdout=PIPE) bzr = Popen(('bzr', 'tags', '--directory=' + path), stdout=PIPE)
stdout = bzr.communicate()[0] std_out = bzr.communicate()[0]
tags = [line.decode('utf-8').split() for line in stdout.splitlines()] tags = [line.decode('utf-8').split() for line in std_out.splitlines()]
tags = [t_r for t_r in tags if t_r[1] != '?' or not (t_r[1] == '?' and TAG_SEARCH.search(t_r[0]))]
# THEN the tags should match the accepted tags # THEN the tags should match the accepted tags
self.assertEqual(TAGS, tags, 'List of tags should match') self.assertEqual(TAGS, tags, 'List of tags should match')