Make footer configurable

bzr-revno: 2854
This commit is contained in:
Samuel Mehrbrodt 2019-04-08 23:07:24 -07:00 committed by Raoul Snyman
commit b478b4e58f
6 changed files with 278 additions and 149 deletions

View File

@ -81,7 +81,8 @@ class ServiceItem(RegistryProperties):
self.items = [] self.items = []
self.icon = UiIcons().default self.icon = UiIcons().default
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.unique_identifier = 0 self.unique_identifier = 0
@ -165,7 +166,8 @@ class ServiceItem(RegistryProperties):
# the dict instead of rendering them again. # the dict instead of rendering them again.
previous_pages = {} previous_pages = {}
index = 0 index = 0
self.foot_text = '<br>'.join([_f for _f in self.raw_footer if _f]) if not self.footer_html:
self.footer_html = '<br>'.join([_f for _f in self.raw_footer if _f])
for raw_slide in self.slides: for raw_slide in self.slides:
verse_tag = raw_slide['verse'] verse_tag = raw_slide['verse']
if verse_tag in previous_pages and previous_pages[verse_tag][0] == raw_slide: if verse_tag in previous_pages and previous_pages[verse_tag][0] == raw_slide:
@ -178,7 +180,7 @@ class ServiceItem(RegistryProperties):
'title': raw_slide['title'], 'title': raw_slide['title'],
'text': render_tags(page), 'text': render_tags(page),
'verse': index, 'verse': index,
'footer': self.foot_text, 'footer': self.footer_html,
} }
self._rendered_slides.append(rendered_slide) self._rendered_slides.append(rendered_slide)
display_slide = { display_slide = {

View File

@ -235,11 +235,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, class_id='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

@ -21,6 +21,7 @@
############################################################################### ###############################################################################
import logging import logging
import os import os
import mako
from PyQt5 import QtCore, QtWidgets from PyQt5 import QtCore, QtWidgets
from sqlalchemy.sql import and_, or_ from sqlalchemy.sql import and_, or_
@ -35,7 +36,7 @@ from openlp.core.lib import ServiceItemContext, check_item_selected, create_sepa
from openlp.core.lib.mediamanageritem import MediaManagerItem from openlp.core.lib.mediamanageritem import MediaManagerItem
from openlp.core.lib.plugin import PluginStatus from openlp.core.lib.plugin import PluginStatus
from openlp.core.lib.serviceitem import ItemCapabilities from openlp.core.lib.serviceitem import ItemCapabilities
from openlp.core.lib.ui import create_widget_action from openlp.core.lib.ui import create_widget_action, critical_error_message_box
from openlp.core.ui.icons import UiIcons from openlp.core.ui.icons import UiIcons
from openlp.plugins.songs.forms.editsongform import EditSongForm from openlp.plugins.songs.forms.editsongform import EditSongForm
from openlp.plugins.songs.forms.songexportform import SongExportForm from openlp.plugins.songs.forms.songexportform import SongExportForm
@ -131,9 +132,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 retranslate_ui(self): def retranslate_ui(self):
self.search_text_label.setText('{text}:'.format(text=UiStrings().Search)) self.search_text_label.setText('{text}:'.format(text=UiStrings().Search))
@ -677,12 +675,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.
if Settings().value('songs/display written by'):
item.raw_footer.append("{text}: {authors}".format(text=translate('OpenLP.Ui', 'Written by'), item.raw_footer.append("{text}: {authors}".format(text=translate('OpenLP.Ui', 'Written by'),
authors=create_separated_list(authors_none))) 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)))
@ -696,34 +690,44 @@ 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))
else:
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] songbooks = [str(songbook_entry) for songbook_entry in song.songbook_entries]
if 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'))
item.metadata.append('<em>{label}:</em> {title}'.format(label=translate('SongsPlugin.MediaItem', 'Title'), footer_template = Settings().value('songs/footer template')
title=song.title)) # Keep this in sync with the list in songstab.py
if song.alternate_title: vars = {
item.metadata.append('<em>{label}:</em> {title}'. 'title': song.title,
format(label=translate('SongsPlugin.MediaItem', 'Alt Title'), 'alternate_title': song.alternate_title,
title=song.alternate_title)) 'authors_none_label': translate('OpenLP.Ui', 'Written by'),
if song.songbook_entries: 'authors_none': authors_none,
for songbook_entry in song.songbook_entries: 'authors_words_label': AuthorType.Types[AuthorType.Words],
item.metadata.append('<em>{label}:</em> {book}/{num}/{pub}'. 'authors_words': authors_words,
format(label=translate('SongsPlugin.MediaItem', 'Songbook'), 'authors_music_label': AuthorType.Types[AuthorType.Music],
book=songbook_entry.songbook.name, 'authors_music': authors_music,
num=songbook_entry.entry, 'authors_words_music_label': AuthorType.Types[AuthorType.WordsAndMusic],
pub=songbook_entry.songbook.publisher)) 'authors_words_music': authors_words_music,
if song.topics: 'authors_translation_label': AuthorType.Types[AuthorType.Translation],
for topics in song.topics: 'authors_translation': authors_translation,
item.metadata.append('<em>{label}:</em> {topic}'. 'authors_words_all': authors_words + authors_words_music,
format(label=translate('SongsPlugin.MediaItem', 'Topic'), topic=topics.name)) 'authors_music_all': authors_music + authors_words_music,
'copyright': song.copyright,
'songbook_entries': songbooks,
'ccli_license': Settings().value('core/ccli number'),
'ccli_license_label': translate('SongsPlugin.MediaItem', 'CCLI License'),
'ccli_number': song.ccli_number,
'topics': [topic.name for topic in song.topics]
}
try:
item.footer_html = mako.template.Template(footer_template).render_unicode(**vars).replace('\n', '')
except mako.exceptions.SyntaxException:
log.error('Failed to render Song footer html:\n' + mako.exceptions.text_error_template().render())
critical_error_message_box(message=translate('SongsPlugin.MediaItem',
'Failed to render Song footer html.\nSee log for details'))
return authors_all return authors_all
def service_load(self, item): def service_load(self, item):

View File

@ -25,7 +25,7 @@ from PyQt5 import QtCore, QtWidgets
from openlp.core.common.i18n import translate from openlp.core.common.i18n import translate
from openlp.core.common.settings import Settings from openlp.core.common.settings import Settings
from openlp.core.lib.settingstab import SettingsTab from openlp.core.lib.settingstab import SettingsTab
from openlp.plugins.songs.lib.ui import SongStrings from openlp.plugins.songs.lib.db import AuthorType
class SongsTab(SettingsTab): class SongsTab(SettingsTab):
@ -54,15 +54,6 @@ class SongsTab(SettingsTab):
self.songbook_slide_check_box = QtWidgets.QCheckBox(self.mode_group_box) self.songbook_slide_check_box = QtWidgets.QCheckBox(self.mode_group_box)
self.songbook_slide_check_box.setObjectName('songbook_slide_check_box') self.songbook_slide_check_box.setObjectName('songbook_slide_check_box')
self.mode_layout.addWidget(self.songbook_slide_check_box) self.mode_layout.addWidget(self.songbook_slide_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)
@ -93,20 +84,34 @@ 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.songbook_slide_check_box.stateChanged.connect(self.on_songbook_slide_check_box_changed) self.songbook_slide_check_box.stateChanged.connect(self.on_songbook_slide_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 retranslate_ui(self): def retranslate_ui(self):
self.mode_group_box.setTitle(translate('SongsPlugin.SongsTab', 'Song related settings')) self.mode_group_box.setTitle(translate('SongsPlugin.SongsTab', 'Song related settings'))
@ -117,12 +122,6 @@ class SongsTab(SettingsTab):
'Import missing songs from Service files')) 'Import missing songs from Service files'))
self.songbook_slide_check_box.setText(translate('SongsPlugin.SongsTab', self.songbook_slide_check_box.setText(translate('SongsPlugin.SongsTab',
'Add Songbooks as first slide')) 'Add Songbooks as first slide'))
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'))
@ -134,6 +133,53 @@ 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 = [
# placeholder, description, can be empty, is a list
['title', translate('SongsPlugin.SongsTab', 'Song Title'), False, False],
['alternate_title', translate('SongsPlugin.SongsTab', 'Alternate Title'), True, False],
['written_by', const.format(translate('SongsPlugin.SongsTab', 'Written By')), True, False],
['authors_none', translate('SongsPlugin.SongsTab', 'Authors when type is not set'), False, True],
['authors_words_label', const.format(AuthorType.Types[AuthorType.Words]), False, False],
['authors_words', translate('SongsPlugin.SongsTab', 'Authors (Type "Words")'), False, True],
['authors_music_label', const.format(AuthorType.Types[AuthorType.Music]), False, False],
['authors_music', translate('SongsPlugin.SongsTab', 'Authors (Type "Music")'), False, True],
['authors_words_music_label', const.format(AuthorType.Types[AuthorType.WordsAndMusic]), False, False],
['authors_words_music', translate('SongsPlugin.SongsTab', 'Authors (Type "Words and Music")'), False, True],
['authors_translation_label', const.format(AuthorType.Types[AuthorType.Translation]), False, False],
['authors_translation', translate('SongsPlugin.SongsTab', 'Authors (Type "Translation")'), False, True],
['authors_words_all', translate('SongsPlugin.SongsTab', 'Authors (Type "Words" & "Words and Music")'),
False, True],
['authors_music_all', translate('SongsPlugin.SongsTab', 'Authors (Type "Music" & "Words and Music")'),
False, True],
['copyright', translate('SongsPlugin.SongsTab', 'Copyright information'), True, False],
['songbook_entries', translate('SongsPlugin.SongsTab', 'Songbook Entries'), False, True],
['ccli_license', translate('SongsPlugin.SongsTab', 'CCLI License'), True, False],
['ccli_license_label', const.format(translate('SongsPlugin.SongsTab', 'CCLI License')), False, False],
['ccli_number', translate('SongsPlugin.SongsTab', 'Song CCLI Number'), True, False],
['topics', translate('SongsPlugin.SongsTab', 'Topics'), False, True],
]
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/>² {}'.format(translate('SongsPlugin.SongsTab', 'list of entries, can be empty'))
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('{} (<a href="http://docs.makotemplates.org">{}</a>):'
.format(translate('SongsPlugin.SongsTab', 'Footer Template'),
translate('SongsPlugin.SongsTab', 'Mako 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)
@ -150,15 +196,6 @@ class SongsTab(SettingsTab):
def on_songbook_slide_check_box_changed(self, check_state): def on_songbook_slide_check_box_changed(self, check_state):
self.songbook_slide = (check_state == QtCore.Qt.Checked) self.songbook_slide = (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)
@ -174,6 +211,9 @@ 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)
@ -181,9 +221,6 @@ 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.songbook_slide = settings.value('add songbook slide') self.songbook_slide = settings.value('add songbook slide')
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')
@ -191,9 +228,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)
@ -203,6 +237,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):
@ -211,13 +246,13 @@ 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)
# Only save footer template if it has been changed. This allows future updates
if self.footer_edit_box.toPlainText() != Settings().get_default_value('songs/footer template'):
settings.setValue('footer template', self.footer_edit_box.toPlainText())
settings.setValue('add songbook slide', self.songbook_slide) settings.setValue('add songbook slide', self.songbook_slide)
settings.endGroup() settings.endGroup()
if self.tab_visited: if self.tab_visited:

View File

@ -66,11 +66,8 @@ __default_settings__ = {
'songs/add song from service': True, 'songs/add song from service': True,
'songs/add songbook slide': False, 'songs/add songbook slide': False,
'songs/display songbar': True, 'songs/display songbar': True,
'songs/display songbook': False, 'songs/last directory import': '',
'songs/display written by': True, 'songs/last directory export': '',
'songs/display copyright symbol': False,
'songs/last directory import': None,
'songs/last directory export': None,
'songs/songselect username': '', 'songs/songselect username': '',
'songs/songselect password': '', 'songs/songselect password': '',
'songs/songselect searches': '', 'songs/songselect searches': '',
@ -78,6 +75,59 @@ __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/>
%if authors_none:
<%
authors = ", ".join(authors_none)
%>
${authors_none_label}:&nbsp;${authors}<br/>
%endif
%if authors_words_music:
<%
authors = ", ".join(authors_words_music)
%>
${authors_words_music_label}:&nbsp;${authors}<br/>
%endif
%if authors_words:
<%
authors = ", ".join(authors_words)
%>
${authors_words_label}:&nbsp;${authors}<br/>
%endif
%if authors_music:
<%
authors = ", ".join(authors_music)
%>
${authors_music_label}:&nbsp;${authors}<br/>
%endif
%if authors_translation:
<%
authors = ", ".join(authors_translation)
%>
${authors_translation_label}:&nbsp;${authors}<br/>
%endif
%if copyright:
&copy;&nbsp;${copyright}<br/>
%endif
%if songbook_entries:
<%
entries = ", ".join(songbook_entries)
%>
${entries}<br/>
%endif
%if ccli_license:
${ccli_license_label}&nbsp;${ccli_license}<br/>
%endif
""",
} }

View File

@ -34,6 +34,62 @@ from openlp.plugins.songs.lib.db import AuthorType, Song
from openlp.plugins.songs.lib.mediaitem import SongMediaItem 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/>
%if authors_none:
<%
authors = ", ".join(authors_none)
%>
${authors_none_label}:&nbsp;${authors}<br/>
%endif
%if authors_words_music:
<%
authors = ", ".join(authors_words_music)
%>
${authors_words_music_label}:&nbsp;${authors}<br/>
%endif
%if authors_words:
<%
authors = ", ".join(authors_words)
%>
${authors_words_label}:&nbsp;${authors}<br/>
%endif
%if authors_music:
<%
authors = ", ".join(authors_music)
%>
${authors_music_label}:&nbsp;${authors}<br/>
%endif
%if authors_translation:
<%
authors = ", ".join(authors_translation)
%>
${authors_translation_label}:&nbsp;${authors}<br/>
%endif
%if copyright:
&copy;&nbsp;${copyright}<br/>
%endif
%if songbook_entries:
<%
entries = ", ".join(songbook_entries)
%>
${entries}<br/>
%endif
%if ccli_license:
${ccli_license_label}&nbsp;${ccli_license}<br/>
%endif
"""
}
class TestMediaItem(TestCase, TestMixin): class TestMediaItem(TestCase, TestMixin):
""" """
@ -61,6 +117,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):
@ -297,15 +354,17 @@ class TestMediaItem(TestCase, TestMixin):
""" """
Test build songs footer with basic song and one author Test build songs footer with basic song and one author
""" """
# GIVEN: A Song and a Service Item, mocked settings: True for 'songs/display written by' # GIVEN: A Song and a Service Item, mocked settings
# 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
with patch('mako.template.Template.render_unicode') as MockedRenderer:
mock_song = MagicMock() mock_song = MagicMock()
mock_song.title = 'My Song' mock_song.title = 'My Song'
mock_song.alternate_title = ''
mock_song.ccli_number = ''
mock_song.authors_songs = [] mock_song.authors_songs = []
mock_author = MagicMock() mock_author = MagicMock()
mock_author.display_name = 'my author' mock_author.display_name = 'my author'
@ -313,47 +372,27 @@ class TestMediaItem(TestCase, TestMixin):
mock_author_song.author = mock_author mock_author_song.author = mock_author
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
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: The mako function was called with the following arguments
assert service_item.raw_footer == ['My Song', 'Written by: my author', 'My copyright'], \ args = {'authors_translation': [], 'authors_music_label': 'Music',
'The array should be returned correctly with a song, one author and copyright' 'copyright': 'My copyright', 'songbook_entries': [],
assert author_list == ['my author'], 'The author list should be returned correctly with one author' 'alternate_title': '', 'topics': [], 'authors_music_all': [],
'authors_words_label': 'Words', 'authors_music': [],
@patch(u'openlp.plugins.songs.lib.mediaitem.Settings') 'authors_words_music': [], 'ccli_number': '',
def test_build_song_footer_one_author_hide_written_by(self, MockedSettings): 'authors_none_label': 'Written by', 'title': 'My Song',
""" 'authors_words_music_label': 'Words and Music',
Test build songs footer with basic song and one author 'authors_none': ['my author'],
""" 'ccli_license_label': 'CCLI License', 'authors_words': [],
# GIVEN: A Song and a Service Item, mocked settings: False for 'songs/display written by' 'ccli_license': '0', 'authors_translation_label': 'Translation',
# and False for 'core/ccli number' (ccli will cause traceback if true) 'authors_words_all': []}
MockedRenderer.assert_called_once_with(**args)
mocked_settings = MagicMock() self.assertEqual(author_list, ['my author'],
mocked_settings.value.side_effect = [False, False] 'The author list should be returned correctly with one author')
MockedSettings.return_value = mocked_settings
mock_song = MagicMock()
mock_song.title = 'My Song'
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
assert 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.'
assert 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):
""" """
@ -382,6 +421,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
@ -389,7 +429,7 @@ class TestMediaItem(TestCase, TestMixin):
# THEN: I get the following Array returned # THEN: I get the following Array returned
assert service_item.raw_footer == ['My Song', 'Words: another author', 'Music: my author', assert 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'
assert author_list == ['another author', 'my author', 'translator'], \ assert 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'
@ -402,6 +442,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')
@ -409,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 get the following Array returned # THEN: I get the following Array returned
assert service_item.raw_footer == ['My Song', 'My copyright', 'CCLI License: 1234'], \ assert 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
@ -417,7 +458,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
assert service_item.raw_footer == ['My Song', 'My copyright', 'CCLI License: 4321'], \ assert 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):
@ -431,6 +472,8 @@ class TestMediaItem(TestCase, TestMixin):
song.copyright = 'My copyright' song.copyright = 'My copyright'
song.authors_songs = [] song.authors_songs = []
song.songbook_entries = [] song.songbook_entries = []
song.alternate_title = ''
song.topics = []
song.ccli_number = '' song.ccli_number = ''
book1 = MagicMock() book1 = MagicMock()
book1.name = 'My songbook' book1.name = 'My songbook'
@ -444,15 +487,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
assert 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
assert service_item.raw_footer == ['My Song', 'My copyright', 'My songbook #12, Thy songbook #502A'] assert 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):
""" """
@ -463,6 +499,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
@ -479,13 +516,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
assert service_item.raw_footer == ['My Song', 'My copyright'] assert service_item.raw_footer == ['My Song', '© My copyright']
def test_authors_match(self): def test_authors_match(self):
""" """