forked from openlp/openlp
Merge trunk
This commit is contained in:
commit
d5a3651c95
|
@ -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)
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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)
|
|
@ -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,6 +507,10 @@ 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)))
|
||||||
|
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)
|
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))
|
||||||
|
|
|
@ -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')
|
||||||
|
|
|
@ -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': '',
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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')))
|
|
@ -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')))
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
|
@ -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>
|
|
@ -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"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
|
@ -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 & 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>
|
|
@ -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')
|
||||||
|
|
Loading…
Reference in New Issue