made use of pystache for footer generation being configurable in song settings

- removed now obsolete and via template better configurable options to display "songbook", "written by" and "copyright" information in footer
- added explanation box for so far used settings as pystache placeholders
- added songs configuration setting for template including reset button
- added default template replacing currently existing configuration as best as possible (should be backwards compatible or at least be adaptable to correspond to former settings)
- adjusted tests to new and removed functionality

Fixes: https://launchpad.net/bugs/1695620
This commit is contained in:
Samuel Mehrbrodt 2017-06-04 01:31:47 +02:00
parent 81492013ed
commit 66c9f8eb82
8 changed files with 251 additions and 135 deletions

View File

@ -163,7 +163,8 @@ class ServiceItem(RegistryProperties):
self.items = [] self.items = []
self.iconic_representation = None self.iconic_representation = None
self.raw_footer = [] self.raw_footer = []
self.foot_text = '' # Plugins can set footer_html themselves. If they don't, it will be generated from raw_footer.
self.footer_html = ''
self.theme = None self.theme = None
self.service_item_type = None self.service_item_type = None
self._raw_frames = [] self._raw_frames = []
@ -276,12 +277,9 @@ class ServiceItem(RegistryProperties):
else: else:
log.error('Invalid value renderer: {item}'.format(item=self.service_item_type)) log.error('Invalid value renderer: {item}'.format(item=self.service_item_type))
self.title = clean_tags(self.title) self.title = clean_tags(self.title)
# The footer should never be None, but to be compatible with a few
# nightly builds between 1.9.4 and 1.9.5, we have to correct this to if not self.footer_html:
# avoid tracebacks. self.footer_html = '<br>'.join([_f for _f in self.raw_footer if _f])
if self.raw_footer is None:
self.raw_footer = []
self.foot_text = '<br>'.join([_f for _f in self.raw_footer if _f])
def add_from_image(self, path, title, background=None, thumbnail=None): def add_from_image(self, path, title, background=None, thumbnail=None):
""" """

View File

@ -471,8 +471,8 @@ class MainDisplay(OpenLPMixin, Display, RegistryProperties):
created_html = build_html(self.service_item, self.screen, self.is_live, background, image_bytes, created_html = build_html(self.service_item, self.screen, self.is_live, background, image_bytes,
plugins=self.plugin_manager.plugins) plugins=self.plugin_manager.plugins)
self.web_view.setHtml(created_html) self.web_view.setHtml(created_html)
if service_item.foot_text: if service_item.footer_html:
self.footer(service_item.foot_text) self.footer(service_item.footer_html)
# if was hidden keep it hidden # if was hidden keep it hidden
if self.hide_mode and self.is_live and not service_item.is_media(): if self.hide_mode and self.is_live and not service_item.is_media():
if Settings().value('core/auto unblank'): if Settings().value('core/auto unblank'):

View File

@ -231,11 +231,11 @@ class PrintServiceForm(QtWidgets.QDialog, Ui_PrintServiceDialog, RegistryPropert
for slide in range(len(item.get_frames())): for slide in range(len(item.get_frames())):
self._add_element('li', item.get_frame_title(slide), ol) self._add_element('li', item.get_frame_title(slide), ol)
# add footer # add footer
foot_text = item.foot_text footer_html = item.footer_html
foot_text = foot_text.partition('<br>')[2] footer_html = footer_html.partition('<br>')[2]
if foot_text: if footer_html:
foot_text = html.escape(foot_text.replace('<br>', '\n')) footer_html = html.escape(footer_html.replace('<br>', '\n'))
self._add_element('div', foot_text.replace('\n', '<br>'), parent=div, classId='itemFooter') self._add_element('div', footer_html.replace('\n', '<br>'), parent=div, classId='itemFooter')
# Add service items' notes. # Add service items' notes.
if self.notes_check_box.isChecked(): if self.notes_check_box.isChecked():
if item.notes: if item.notes:

View File

@ -664,3 +664,17 @@ def transpose_chord(chord, transpose_value, notation):
else: else:
transposed_chord += note + rest transposed_chord += note + rest
return transposed_chord return transposed_chord
def make_list(array):
if len(array) > 0:
result = []
for i in range(len(array)):
result.append({'entry': array[i]})
if i == 0:
result[i]['first'] = True
if i == len(array) - 1:
result[i]['last'] = True
return result
else:
return False

View File

@ -23,6 +23,7 @@
import logging import logging
import os import os
import shutil import shutil
import pystache
from PyQt5 import QtCore, QtWidgets from PyQt5 import QtCore, QtWidgets
from sqlalchemy.sql import and_, or_ from sqlalchemy.sql import and_, or_
@ -36,7 +37,7 @@ from openlp.plugins.songs.forms.editsongform import EditSongForm
from openlp.plugins.songs.forms.songmaintenanceform import SongMaintenanceForm from openlp.plugins.songs.forms.songmaintenanceform import SongMaintenanceForm
from openlp.plugins.songs.forms.songimportform import SongImportForm from openlp.plugins.songs.forms.songimportform import SongImportForm
from openlp.plugins.songs.forms.songexportform import SongExportForm from openlp.plugins.songs.forms.songexportform import SongExportForm
from openlp.plugins.songs.lib import VerseType, clean_string, delete_song from openlp.plugins.songs.lib import VerseType, clean_string, delete_song, make_list
from openlp.plugins.songs.lib.db import Author, AuthorType, Song, Book, MediaFile, SongBookEntry, Topic from openlp.plugins.songs.lib.db import Author, AuthorType, Song, Book, MediaFile, SongBookEntry, Topic
from openlp.plugins.songs.lib.ui import SongStrings from openlp.plugins.songs.lib.ui import SongStrings
from openlp.plugins.songs.lib.openlyricsxml import OpenLyrics, SongXML from openlp.plugins.songs.lib.openlyricsxml import OpenLyrics, SongXML
@ -125,9 +126,6 @@ class SongMediaItem(MediaManagerItem):
self.is_search_as_you_type_enabled = Settings().value('advanced/search as type') self.is_search_as_you_type_enabled = Settings().value('advanced/search as type')
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_written_by_text = Settings().value(self.settings_section + '/display written by')
self.display_copyright_symbol = Settings().value(self.settings_section + '/display copyright symbol')
def retranslateUi(self): def retranslateUi(self):
self.search_text_label.setText('{text}:'.format(text=UiStrings().Search)) self.search_text_label.setText('{text}:'.format(text=UiStrings().Search))
@ -641,12 +639,8 @@ class SongMediaItem(MediaManagerItem):
item.raw_footer = [] item.raw_footer = []
item.raw_footer.append(song.title) item.raw_footer.append(song.title)
if authors_none: if authors_none:
# If the setting for showing "Written by:" is enabled, show it before unspecified authors. item.raw_footer.append("{text}: {authors}".format(text=translate('OpenLP.Ui', 'Written by'),
if Settings().value('songs/display written by'): authors=create_separated_list(authors_none)))
item.raw_footer.append("{text}: {authors}".format(text=translate('OpenLP.Ui', 'Written by'),
authors=create_separated_list(authors_none)))
else:
item.raw_footer.append("{authors}".format(authors=create_separated_list(authors_none)))
if authors_words_music: if authors_words_music:
item.raw_footer.append("{text}: {authors}".format(text=AuthorType.Types[AuthorType.WordsAndMusic], item.raw_footer.append("{text}: {authors}".format(text=AuthorType.Types[AuthorType.WordsAndMusic],
authors=create_separated_list(authors_words_music))) authors=create_separated_list(authors_words_music)))
@ -660,17 +654,35 @@ class SongMediaItem(MediaManagerItem):
item.raw_footer.append("{text}: {authors}".format(text=AuthorType.Types[AuthorType.Translation], item.raw_footer.append("{text}: {authors}".format(text=AuthorType.Types[AuthorType.Translation],
authors=create_separated_list(authors_translation))) authors=create_separated_list(authors_translation)))
if song.copyright: if song.copyright:
if self.display_copyright_symbol: item.raw_footer.append("{symbol} {song}".format(symbol=SongStrings.CopyrightSymbol,
item.raw_footer.append("{symbol} {song}".format(symbol=SongStrings.CopyrightSymbol, song=song.copyright))
song=song.copyright)) songbooks = [str(songbook_entry) for songbook_entry in song.songbook_entries]
else: if song.songbook_entries:
item.raw_footer.append(song.copyright)
if self.display_songbook and song.songbook_entries:
songbooks = [str(songbook_entry) for songbook_entry in song.songbook_entries]
item.raw_footer.append(", ".join(songbooks)) item.raw_footer.append(", ".join(songbooks))
if Settings().value('core/ccli number'): if Settings().value('core/ccli number'):
item.raw_footer.append(translate('SongsPlugin.MediaItem', item.raw_footer.append(translate('SongsPlugin.MediaItem', 'CCLI License: ') +
'CCLI License: ') + Settings().value('core/ccli number')) Settings().value('core/ccli number'))
footer_template = Settings().value('songs/footer template').replace('\n', '').replace(' ', '')
# Keep this in sync with the list in songstab.py
item.footer_html = pystache.render(footer_template, {
'title': song.title,
'authors_none_label': translate('OpenLP.Ui', 'Written by'),
'authors_none': make_list(authors_none),
'authors_words_label': AuthorType.Types[AuthorType.Words],
'authors_words': make_list(authors_words),
'authors_music_label': AuthorType.Types[AuthorType.Music],
'authors_music': make_list(authors_music),
'authors_words_music_label': AuthorType.Types[AuthorType.WordsAndMusic],
'authors_words_music': make_list(authors_words_music),
'authors_translation_label': AuthorType.Types[AuthorType.Translation],
'authors_translation': make_list(authors_translation),
'copyright': song.copyright,
'songbook_entries': make_list(songbooks),
'ccli_license': Settings().value('core/ccli number'),
'ccli_license_label': translate('SongsPlugin.MediaItem', 'CCLI License')
})
return authors_all return authors_all
def service_load(self, item): def service_load(self, item):

View File

@ -24,7 +24,7 @@ from PyQt5 import QtCore, QtWidgets
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 from openlp.plugins.songs.lib.db import AuthorType
class SongsTab(SettingsTab): class SongsTab(SettingsTab):
@ -50,15 +50,6 @@ class SongsTab(SettingsTab):
self.add_from_service_check_box = QtWidgets.QCheckBox(self.mode_group_box) self.add_from_service_check_box = QtWidgets.QCheckBox(self.mode_group_box)
self.add_from_service_check_box.setObjectName('add_from_service_check_box') self.add_from_service_check_box.setObjectName('add_from_service_check_box')
self.mode_layout.addWidget(self.add_from_service_check_box) self.mode_layout.addWidget(self.add_from_service_check_box)
self.display_songbook_check_box = QtWidgets.QCheckBox(self.mode_group_box)
self.display_songbook_check_box.setObjectName('songbook_check_box')
self.mode_layout.addWidget(self.display_songbook_check_box)
self.display_written_by_check_box = QtWidgets.QCheckBox(self.mode_group_box)
self.display_written_by_check_box.setObjectName('written_by_check_box')
self.mode_layout.addWidget(self.display_written_by_check_box)
self.display_copyright_check_box = QtWidgets.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)
# Chords group box # Chords group box
self.chords_group_box = QtWidgets.QGroupBox(self.left_column) self.chords_group_box = QtWidgets.QGroupBox(self.left_column)
@ -89,19 +80,36 @@ class SongsTab(SettingsTab):
self.neolatin_notation_radio_button.setObjectName('neolatin_notation_radio_button') self.neolatin_notation_radio_button.setObjectName('neolatin_notation_radio_button')
self.chords_layout.addWidget(self.neolatin_notation_radio_button) self.chords_layout.addWidget(self.neolatin_notation_radio_button)
self.left_layout.addWidget(self.chords_group_box) self.left_layout.addWidget(self.chords_group_box)
# Footer group box
self.footer_group_box = QtWidgets.QGroupBox(self.left_column)
self.footer_group_box.setObjectName('footer_group_box')
self.footer_layout = QtWidgets.QVBoxLayout(self.footer_group_box)
self.footer_layout.setObjectName('chords_layout')
self.footer_info_label = QtWidgets.QLabel(self.footer_group_box)
self.footer_layout.addWidget(self.footer_info_label)
self.footer_placeholder_info = QtWidgets.QTextEdit(self.footer_group_box)
self.footer_layout.addWidget(self.footer_placeholder_info)
self.footer_desc_label = QtWidgets.QLabel(self.footer_group_box)
self.footer_layout.addWidget(self.footer_desc_label)
self.footer_edit_box = QtWidgets.QTextEdit(self.footer_group_box)
self.footer_layout.addWidget(self.footer_edit_box)
self.footer_reset_button = QtWidgets.QPushButton(self.footer_group_box)
self.footer_layout.addWidget(self.footer_reset_button, alignment=QtCore.Qt.AlignRight)
self.right_layout.addWidget(self.footer_group_box)
self.left_layout.addStretch() self.left_layout.addStretch()
self.right_layout.addStretch() self.right_layout.addStretch()
self.tool_bar_active_check_box.stateChanged.connect(self.on_tool_bar_active_check_box_changed) self.tool_bar_active_check_box.stateChanged.connect(self.on_tool_bar_active_check_box_changed)
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_written_by_check_box.stateChanged.connect(self.on_written_by_check_box_changed)
self.display_copyright_check_box.stateChanged.connect(self.on_copyright_check_box_changed)
self.mainview_chords_check_box.stateChanged.connect(self.on_mainview_chords_check_box_changed) self.mainview_chords_check_box.stateChanged.connect(self.on_mainview_chords_check_box_changed)
self.disable_chords_import_check_box.stateChanged.connect(self.on_disable_chords_import_check_box_changed) self.disable_chords_import_check_box.stateChanged.connect(self.on_disable_chords_import_check_box_changed)
self.english_notation_radio_button.clicked.connect(self.on_english_notation_button_clicked) self.english_notation_radio_button.clicked.connect(self.on_english_notation_button_clicked)
self.german_notation_radio_button.clicked.connect(self.on_german_notation_button_clicked) self.german_notation_radio_button.clicked.connect(self.on_german_notation_button_clicked)
self.neolatin_notation_radio_button.clicked.connect(self.on_neolatin_notation_button_clicked) self.neolatin_notation_radio_button.clicked.connect(self.on_neolatin_notation_button_clicked)
self.footer_reset_button.clicked.connect(self.on_footer_reset_button_clicked)
def retranslateUi(self): def retranslateUi(self):
self.mode_group_box.setTitle(translate('SongsPlugin.SongsTab', 'Song related settings')) self.mode_group_box.setTitle(translate('SongsPlugin.SongsTab', 'Song related settings'))
@ -110,12 +118,6 @@ class SongsTab(SettingsTab):
self.update_on_edit_check_box.setText(translate('SongsPlugin.SongsTab', 'Update service from song edit')) self.update_on_edit_check_box.setText(translate('SongsPlugin.SongsTab', 'Update service from song edit'))
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_written_by_check_box.setText(translate(
'SongsPlugin.SongsTab', 'Show "Written by:" in footer for unspecified authors'))
self.display_copyright_check_box.setText(translate('SongsPlugin.SongsTab',
'Display "{symbol}" symbol before copyright '
'info').format(symbol=SongStrings.CopyrightSymbol))
self.chords_info_label.setText(translate('SongsPlugin.SongsTab', 'If enabled all text between "[" and "]" will ' self.chords_info_label.setText(translate('SongsPlugin.SongsTab', 'If enabled all text between "[" and "]" will '
'be regarded as chords.')) 'be regarded as chords.'))
self.chords_group_box.setTitle(translate('SongsPlugin.SongsTab', 'Chords')) self.chords_group_box.setTitle(translate('SongsPlugin.SongsTab', 'Chords'))
@ -127,6 +129,49 @@ class SongsTab(SettingsTab):
self.german_notation_radio_button.setText(translate('SongsPlugin.SongsTab', 'German') + ' (C-D-E-F-G-A-H)') self.german_notation_radio_button.setText(translate('SongsPlugin.SongsTab', 'German') + ' (C-D-E-F-G-A-H)')
self.neolatin_notation_radio_button.setText( self.neolatin_notation_radio_button.setText(
translate('SongsPlugin.SongsTab', 'Neo-Latin') + ' (Do-Re-Mi-Fa-Sol-La-Si)') translate('SongsPlugin.SongsTab', 'Neo-Latin') + ' (Do-Re-Mi-Fa-Sol-La-Si)')
self.footer_group_box.setTitle(translate('SongsPlugin.SongsTab', 'Footer'))
# Keep this in sync with the list in mediaitem.py
const = '<code>"{}"</code>'
placeholders = [
['title', translate('SongsPlugin.SongsTab', 'Song Title'), False, False],
['written_by', const.format(translate('SongsPlugin.SongsTab', 'Written By')), True, False],
['authors_none', translate('SongsPlugin.SongsTab', 'Authors when type is not set'), True, True],
['authors_words_label', const.format(AuthorType.Types[AuthorType.Words]), False, False],
['authors_words', translate('SongsPlugin.SongsTab', 'Authors (Type Words)'), True, True],
['authors_music_label', const.format(AuthorType.Types[AuthorType.Music]), False, False],
['authors_music', translate('SongsPlugin.SongsTab', 'Authors (Type Music)'), True, True],
['authors_words_music_label', const.format(AuthorType.Types[AuthorType.WordsAndMusic]), False, False],
['authors_words_music', translate('SongsPlugin.SongsTab', 'Authors (Type Words and Music)'), True, True],
['authors_translation_label', const.format(AuthorType.Types[AuthorType.Translation]), False, False],
['authors_translation', translate('SongsPlugin.SongsTab', 'Authors (Type Translation)'), True, True],
['copyright', translate('SongsPlugin.SongsTab', 'Copyright information'), True, False],
['songbook_entries', translate('SongsPlugin.SongsTab', 'Songbook Entries'), True, True],
['ccli_license', translate('SongsPlugin.SongsTab', 'CCLI License'), True, False],
['ccli_license_label', const.format(translate('SongsPlugin.SongsTab', 'CCLI License')), False, False],
]
placeholder_info = '<table style="background: #eee">\n<tr><th><b>{ph}</b></th><th><b>{desc}</b></th></tr>\n'\
.format(ph=translate('SongsPlugin.SongsTab', 'Placeholder'),
desc=translate('SongsPlugin.SongsTab', 'Description'))
for placeholder in placeholders:
placeholder_info += '<tr><td>{{{pl}}}</td><td>{des}{opt}</td></tr>\n'\
.format(pl=placeholder[0], des=placeholder[1],
opt=('&nbsp;¹' if placeholder[2] else '') + ('&nbsp;²' if placeholder[3] else ''))
placeholder_info += '</table>'
placeholder_info += '\n<br/>¹ {}'.format(translate('SongsPlugin.SongsTab', 'can be empty'))
placeholder_info += '\n<br/>² {}:<pre>{{#first}} <i>True</i> {}<br/>{{entry}} {}<br/>' \
'{{#last}} <i>True</i> {}</pre>'\
.format(translate('SongsPlugin.SongsTab', 'list of entries'),
translate('SongsPlugin.SongsTab', 'for first element of list'),
translate('SongsPlugin.SongsTab', 'iterates over all entries'),
translate('SongsPlugin.SongsTab', 'for last element'))
self.footer_placeholder_info.setHtml(placeholder_info)
self.footer_placeholder_info.setReadOnly(True)
self.footer_info_label.setText(translate('SongsPlugin.SongsTab', 'How to use Footers:'))
self.footer_desc_label.setText(translate('SongsPlugin.SongsTab', 'Footer Template (Mustache Syntax):'))
self.footer_reset_button.setText(translate('SongsPlugin.SongsTab', 'Reset Template'))
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)
@ -140,15 +185,6 @@ class SongsTab(SettingsTab):
def on_add_from_service_check_box_changed(self, check_state): def on_add_from_service_check_box_changed(self, check_state):
self.update_load = (check_state == QtCore.Qt.Checked) self.update_load = (check_state == QtCore.Qt.Checked)
def on_songbook_check_box_changed(self, check_state):
self.display_songbook = (check_state == QtCore.Qt.Checked)
def on_written_by_check_box_changed(self, check_state):
self.display_written_by = (check_state == QtCore.Qt.Checked)
def on_copyright_check_box_changed(self, check_state):
self.display_copyright_symbol = (check_state == QtCore.Qt.Checked)
def on_mainview_chords_check_box_changed(self, check_state): def on_mainview_chords_check_box_changed(self, check_state):
self.mainview_chords = (check_state == QtCore.Qt.Checked) self.mainview_chords = (check_state == QtCore.Qt.Checked)
@ -164,15 +200,15 @@ class SongsTab(SettingsTab):
def on_neolatin_notation_button_clicked(self): def on_neolatin_notation_button_clicked(self):
self.chord_notation = 'neo-latin' self.chord_notation = 'neo-latin'
def on_footer_reset_button_clicked(self):
self.footer_edit_box.setPlainText(Settings().get_default_value('songs/footer template'));
def load(self): def load(self):
settings = Settings() settings = Settings()
settings.beginGroup(self.settings_section) settings.beginGroup(self.settings_section)
self.tool_bar = settings.value('display songbar') self.tool_bar = settings.value('display songbar')
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_written_by = settings.value('display written by')
self.display_copyright_symbol = settings.value('display copyright symbol')
self.enable_chords = settings.value('enable chords') self.enable_chords = settings.value('enable chords')
self.chord_notation = settings.value('chord notation') self.chord_notation = settings.value('chord notation')
self.mainview_chords = settings.value('mainview chords') self.mainview_chords = settings.value('mainview chords')
@ -180,9 +216,6 @@ class SongsTab(SettingsTab):
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_written_by_check_box.setChecked(self.display_written_by)
self.display_copyright_check_box.setChecked(self.display_copyright_symbol)
self.chords_group_box.setChecked(self.enable_chords) self.chords_group_box.setChecked(self.enable_chords)
self.mainview_chords_check_box.setChecked(self.mainview_chords) self.mainview_chords_check_box.setChecked(self.mainview_chords)
self.disable_chords_import_check_box.setChecked(self.disable_chords_import) self.disable_chords_import_check_box.setChecked(self.disable_chords_import)
@ -192,6 +225,7 @@ class SongsTab(SettingsTab):
self.neolatin_notation_radio_button.setChecked(True) self.neolatin_notation_radio_button.setChecked(True)
else: else:
self.english_notation_radio_button.setChecked(True) self.english_notation_radio_button.setChecked(True)
self.footer_edit_box.setPlainText(settings.value('footer template'))
settings.endGroup() settings.endGroup()
def save(self): def save(self):
@ -200,13 +234,11 @@ class SongsTab(SettingsTab):
settings.setValue('display songbar', self.tool_bar) settings.setValue('display songbar', self.tool_bar)
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 written by', self.display_written_by)
settings.setValue('display copyright symbol', self.display_copyright_symbol)
settings.setValue('enable chords', self.chords_group_box.isChecked()) settings.setValue('enable chords', self.chords_group_box.isChecked())
settings.setValue('mainview chords', self.mainview_chords) settings.setValue('mainview chords', self.mainview_chords)
settings.setValue('disable chords import', self.disable_chords_import) settings.setValue('disable chords import', self.disable_chords_import)
settings.setValue('chord notation', self.chord_notation) settings.setValue('chord notation', self.chord_notation)
settings.setValue('footer template', self.footer_edit_box.toPlainText())
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

@ -59,9 +59,6 @@ __default_settings__ = {
'songs/update service on edit': False, 'songs/update service on edit': False,
'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 written by': True,
'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': '',
@ -71,6 +68,46 @@ __default_settings__ = {
'songs/chord notation': 'english', # Can be english, german or neo-latin 'songs/chord notation': 'english', # Can be english, german or neo-latin
'songs/mainview chords': False, 'songs/mainview chords': False,
'songs/disable chords import': False, 'songs/disable chords import': False,
'songs/footer template': """{{title}}<br/>
{{#authors_none}}
{{#first}}{{authors_none_label}}:&nbsp;{{/first}}
{{entry}}{{^last}},&nbsp;{{/last}}
{{#last}}<br/>{{/last}}
{{/authors_none}}
{{#authors_words_music}}
{{#first}}{{authors_words_music_label}}:&nbsp;{{/first}}
{{entry}}{{^last}},&nbsp;{{/last}}
{{#last}}<br/>{{/last}}
{{/authors_words_music}}
{{#authors_words}}
{{#first}}{{authors_words_label}}:&nbsp;{{/first}}
{{entry}}{{^last}},&nbsp;{{/last}}
{{#last}}<br/>{{/last}}
{{/authors_words}}
{{#authors_music}}
{{#first}}{{authors_music_label}}:&nbsp;{{/first}}
{{entry}}{{^last}},&nbsp;{{/last}}
{{#last}}<br/>{{/last}}
{{/authors_music}}
{{#authors_translation}}
{{#first}}{{authors_translation_label}}:&nbsp;{{/first}}
{{entry}}{{^last}},&nbsp;{{/last}}
{{#last}}<br/>{{/last}}
{{/authors_translation}}
{{#copyright}}
&copy;&nbsp;{{copyright}}<br/>
{{/copyright}}
{{#songbook_entries}}
{{entry}}{{^last}},&nbsp;{{/last}}
{{#last}}<br/>{{/last}}
{{/songbook_entries}}
{{#ccli_license}}
{{ccli_license_label}}:&nbsp;{{ccli_license}}<br/>
{{/ccli_license}}""",
} }

View File

@ -34,6 +34,49 @@ from openlp.plugins.songs.lib.mediaitem import SongMediaItem
from tests.helpers.testmixin import TestMixin from tests.helpers.testmixin import TestMixin
__default_settings__ = {
'songs/footer template': """{{title}}<br/>
{{#authors_none}}
{{#first}}{{authors_none_label}}:&nbsp;{{/first}}
{{entry}}{{^last}},&nbsp;{{/last}}
{{#last}}<br/>{{/last}}
{{/authors_none}}
{{#authors_words_music}}
{{#first}}{{authors_words_music_label}}:&nbsp;{{/first}}
{{entry}}{{^last}},&nbsp;{{/last}}
{{#last}}<br/>{{/last}}
{{/authors_words_music}}
{{#authors_words}}
{{#first}}{{authors_words_label}}:&nbsp;{{/first}}
{{entry}}{{^last}},&nbsp;{{/last}}
{{#last}}<br/>{{/last}}
{{/authors_words}}
{{#authors_music}}
{{#first}}{{authors_music_label}}:&nbsp;{{/first}}
{{entry}}{{^last}},&nbsp;{{/last}}
{{#last}}<br/>{{/last}}
{{/authors_music}}
{{#authors_translation}}
{{#first}}{{authors_translation_label}}:&nbsp;{{/first}}
{{entry}}{{^last}},&nbsp;{{/last}}
{{#last}}<br/>{{/last}}
{{/authors_translation}}
{{#copyright}}
&copy;&nbsp;{{copyright}}<br/>
{{/copyright}}
{{#songbook_entries}}
{{entry}}{{^last}},&nbsp;{{/last}}
{{#last}}<br/>{{/last}}
{{/songbook_entries}}
{{#ccli_license}}
{{ccli_license_label}}:&nbsp;{{ccli_license}}<br/>
{{/ccli_license}}"""
}
class TestMediaItem(TestCase, TestMixin): class TestMediaItem(TestCase, TestMixin):
""" """
@ -60,6 +103,7 @@ class TestMediaItem(TestCase, TestMixin):
self.media_item.display_copyright_symbol = False self.media_item.display_copyright_symbol = False
self.setup_application() self.setup_application()
self.build_settings() self.build_settings()
Settings().extend_default_settings(__default_settings__)
QtCore.QLocale.setDefault(QtCore.QLocale('en_GB')) QtCore.QLocale.setDefault(QtCore.QLocale('en_GB'))
def tearDown(self): def tearDown(self):
@ -304,61 +348,43 @@ class TestMediaItem(TestCase, TestMixin):
# and False for 'core/ccli number' (ccli will cause traceback if true) # and False for 'core/ccli number' (ccli will cause traceback if true)
mocked_settings = MagicMock() mocked_settings = MagicMock()
mocked_settings.value.side_effect = [True, False] mocked_settings.value.side_effect = [False, "", "0"]
MockedSettings.return_value = mocked_settings MockedSettings.return_value = mocked_settings
mock_song = MagicMock() with patch('pystache.Renderer.render') as MockedRenderer:
mock_song.title = 'My Song' mock_song = MagicMock()
mock_song.authors_songs = [] mock_song.title = 'My Song'
mock_author = MagicMock() mock_song.authors_songs = []
mock_author.display_name = 'my author' mock_author = MagicMock()
mock_author_song = MagicMock() mock_author.display_name = 'my author'
mock_author_song.author = mock_author mock_author_song = MagicMock()
mock_song.authors_songs.append(mock_author_song) mock_author_song.author = mock_author
mock_song.copyright = 'My copyright' mock_song.authors_songs.append(mock_author_song)
service_item = ServiceItem(None) mock_song.copyright = 'My copyright'
mock_song.songbook_entries = []
service_item = ServiceItem(None)
MockedRenderer.return_value = ""
# WHEN: I generate the Footer with default settings # WHEN: I generate the Footer with default settings
author_list = self.media_item.generate_footer(service_item, mock_song) author_list = self.media_item.generate_footer(service_item, mock_song)
# THEN: I get the following Array returned # THEN: I get nothing real returned
self.assertEqual(service_item.raw_footer, ['My Song', 'Written by: my author', 'My copyright'], self.assertEqual(service_item.footer_html, "", 'pystache isnt in scope')
'The array should be returned correctly with a song, one author and copyright')
self.assertEqual(author_list, ['my author'],
'The author list should be returned correctly with one author')
@patch(u'openlp.plugins.songs.lib.mediaitem.Settings') # AND: The psytache function was called with the following arguments
def test_build_song_footer_one_author_hide_written_by(self, MockedSettings): MockedRenderer.assert_called_once_with('', {'authors_music_label': 'Music',
""" 'authors_none': [{'first': True, 'entry': 'my author',
Test build songs footer with basic song and one author 'last': True}], 'authors_words_music': False,
""" 'authors_words': False, 'authors_music': False,
# GIVEN: A Song and a Service Item, mocked settings: False for 'songs/display written by' 'authors_translation_label': 'Translation',
# and False for 'core/ccli number' (ccli will cause traceback if true) 'authors_words_music_label': 'Words and Music',
'title': 'My Song', 'ccli_license': "0",
mocked_settings = MagicMock() 'copyright': 'My copyright', 'authors_none_label': 'Written by',
mocked_settings.value.side_effect = [False, False] 'ccli_license_label': 'CCLI License',
MockedSettings.return_value = mocked_settings 'authors_translation': False, 'authors_words_label': 'Words',
'songbook_entries': False})
mock_song = MagicMock() self.assertEqual(author_list, ['my author'],
mock_song.title = 'My Song' 'The author list should be returned correctly with one author')
mock_song.authors_songs = []
mock_author = MagicMock()
mock_author.display_name = 'my author'
mock_author_song = MagicMock()
mock_author_song.author = mock_author
mock_song.authors_songs.append(mock_author_song)
mock_song.copyright = 'My copyright'
service_item = ServiceItem(None)
# WHEN: I generate the Footer with default settings
author_list = self.media_item.generate_footer(service_item, mock_song)
# THEN: I get the following Array returned
self.assertEqual(service_item.raw_footer, ['My Song', 'my author', 'My copyright'],
'The array should be returned correctly with a song, one author and copyright,'
'text Written by should not be part of the text.')
self.assertEqual(author_list, ['my author'],
'The author list should be returned correctly with one author')
def test_build_song_footer_two_authors(self): def test_build_song_footer_two_authors(self):
""" """
@ -387,6 +413,7 @@ class TestMediaItem(TestCase, TestMixin):
mock_author_song.author_type = AuthorType.Translation mock_author_song.author_type = AuthorType.Translation
mock_song.authors_songs.append(mock_author_song) mock_song.authors_songs.append(mock_author_song)
mock_song.copyright = 'My copyright' mock_song.copyright = 'My copyright'
mock_song.songbook_entries = []
service_item = ServiceItem(None) service_item = ServiceItem(None)
# WHEN: I generate the Footer with default settings # WHEN: I generate the Footer with default settings
@ -394,7 +421,7 @@ class TestMediaItem(TestCase, TestMixin):
# THEN: I get the following Array returned # THEN: I get the following Array returned
self.assertEqual(service_item.raw_footer, ['My Song', 'Words: another author', 'Music: my author', self.assertEqual(service_item.raw_footer, ['My Song', 'Words: another author', 'Music: my author',
'Translation: translator', 'My copyright'], 'Translation: translator', '© My copyright'],
'The array should be returned correctly with a song, two authors and copyright') 'The array should be returned correctly with a song, two authors and copyright')
self.assertEqual(author_list, ['another author', 'my author', 'translator'], self.assertEqual(author_list, ['another author', 'my author', 'translator'],
'The author list should be returned correctly with two authors') 'The author list should be returned correctly with two authors')
@ -407,6 +434,7 @@ class TestMediaItem(TestCase, TestMixin):
mock_song = MagicMock() mock_song = MagicMock()
mock_song.title = 'My Song' mock_song.title = 'My Song'
mock_song.copyright = 'My copyright' mock_song.copyright = 'My copyright'
mock_song.songbook_entries = []
service_item = ServiceItem(None) service_item = ServiceItem(None)
Settings().setValue('core/ccli number', '1234') Settings().setValue('core/ccli number', '1234')
@ -414,7 +442,7 @@ class TestMediaItem(TestCase, TestMixin):
self.media_item.generate_footer(service_item, mock_song) self.media_item.generate_footer(service_item, mock_song)
# THEN: I get the following Array returned # THEN: I get the following Array returned
self.assertEqual(service_item.raw_footer, ['My Song', 'My copyright', 'CCLI License: 1234'], self.assertEqual(service_item.raw_footer, ['My Song', '© My copyright', 'CCLI License: 1234'],
'The array should be returned correctly with a song, an author, copyright and ccli') 'The array should be returned correctly with a song, an author, copyright and ccli')
# WHEN: I amend the CCLI value # WHEN: I amend the CCLI value
@ -422,7 +450,7 @@ class TestMediaItem(TestCase, TestMixin):
self.media_item.generate_footer(service_item, mock_song) self.media_item.generate_footer(service_item, mock_song)
# THEN: I would get an amended footer string # THEN: I would get an amended footer string
self.assertEqual(service_item.raw_footer, ['My Song', 'My copyright', 'CCLI License: 4321'], self.assertEqual(service_item.raw_footer, ['My Song', '© My copyright', 'CCLI License: 4321'],
'The array should be returned correctly with a song, an author, copyright and amended ccli') 'The array should be returned correctly with a song, an author, copyright and amended ccli')
def test_build_song_footer_base_songbook(self): def test_build_song_footer_base_songbook(self):
@ -448,15 +476,8 @@ class TestMediaItem(TestCase, TestMixin):
# WHEN: I generate the Footer with default settings # WHEN: I generate the Footer with default settings
self.media_item.generate_footer(service_item, song) self.media_item.generate_footer(service_item, song)
# THEN: The songbook should not be in the footer
self.assertEqual(service_item.raw_footer, ['My Song', 'My copyright'])
# WHEN: I activate the "display songbook" option
self.media_item.display_songbook = True
self.media_item.generate_footer(service_item, song)
# 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, Thy songbook #502A']) self.assertEqual(service_item.raw_footer, ['My Song', '© My copyright', 'My songbook #12, Thy songbook #502A'])
def test_build_song_footer_copyright_enabled(self): def test_build_song_footer_copyright_enabled(self):
""" """
@ -467,6 +488,7 @@ class TestMediaItem(TestCase, TestMixin):
mock_song = MagicMock() mock_song = MagicMock()
mock_song.title = 'My Song' mock_song.title = 'My Song'
mock_song.copyright = 'My copyright' mock_song.copyright = 'My copyright'
mock_song.songbook_entries = []
service_item = ServiceItem(None) service_item = ServiceItem(None)
# WHEN: I generate the Footer with default settings # WHEN: I generate the Footer with default settings
@ -483,13 +505,14 @@ class TestMediaItem(TestCase, TestMixin):
mock_song = MagicMock() mock_song = MagicMock()
mock_song.title = 'My Song' mock_song.title = 'My Song'
mock_song.copyright = 'My copyright' mock_song.copyright = 'My copyright'
mock_song.songbook_entries = []
service_item = ServiceItem(None) service_item = ServiceItem(None)
# WHEN: I generate the Footer with default settings # WHEN: I generate the Footer with default settings
self.media_item.generate_footer(service_item, mock_song) self.media_item.generate_footer(service_item, mock_song)
# THEN: The copyright symbol should not be in the footer # THEN: The copyright symbol should not be in the footer
self.assertEqual(service_item.raw_footer, ['My Song', 'My copyright']) self.assertEqual(service_item.raw_footer, ['My Song', '© My copyright'])
def test_authors_match(self): def test_authors_match(self):
""" """