Merge lp:~sam92/openlp/bug-1695620 into lp:openlp
- bug-1695620
- Merge into trunk
Status: | Merged | ||||||||
---|---|---|---|---|---|---|---|---|---|
Merged at revision: | 2854 | ||||||||
Proposed branch: | lp:~sam92/openlp/bug-1695620 | ||||||||
Merge into: | lp:openlp | ||||||||
Diff against target: |
724 lines (+284/-155) 6 files modified
openlp/core/lib/serviceitem.py (+5/-3) openlp/core/ui/printserviceform.py (+5/-5) openlp/plugins/songs/lib/mediaitem.py (+40/-36) openlp/plugins/songs/lib/songstab.py (+72/-37) openlp/plugins/songs/songsplugin.py (+55/-5) tests/functional/openlp_plugins/songs/test_mediaitem.py (+107/-69) |
||||||||
To merge this branch: | bzr merge lp:~sam92/openlp/bug-1695620 | ||||||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Tim Bentley | Pending | ||
Raoul Snyman | Pending | ||
Tomas Groth | Pending | ||
Review via email: mp+365702@code.launchpad.net |
This proposal supersedes a proposal from 2019-02-21.
Commit message
Make footer configurable
Description of the change
Make footer configurable
- Make use of Mako for footer generation, 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 Mako 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)
- write and adapt tests for new and removed functionality
- Added some more available fields in the footer: Alternate Title, CCLI Number, Topics, Authors (all music, all words)
Tim Bentley (trb143) wrote : Posted in a previous version of this proposal | # |
Raoul Snyman (raoul-snyman) wrote : Posted in a previous version of this proposal | # |
We're moving away from Python generated HTML, and using a JS system based on Reveal.js. Come chat with tgc and myself in IRC.
Samuel Mehrbrodt (sam92) wrote : Posted in a previous version of this proposal | # |
> Cannot run this but there seem to be lots of line breaks in the footer display. As the footer area is small this will not be displayed and lead to forum comments.
The footer has the same height as before, there should be no difference by default.
So I replaced Pystache with Mako. Should be safe to merge now I think.
As the new renderer is in a very early state atm, I can't see how I can adopt this for the new renderer. Maybe we can merge this now and adopt when the new renderer is ready?
Tim Bentley (trb143) wrote : Posted in a previous version of this proposal | # |
Blank lines!
Will review tomorrow as I have bible study!
Samuel Mehrbrodt (sam92) wrote : Posted in a previous version of this proposal | # |
So is there a chance this can be reviewed now?
> Blank lines!
What do you mean with that?
Raoul Snyman (raoul-snyman) wrote : Posted in a previous version of this proposal | # |
Linux tests passed!
Raoul Snyman (raoul-snyman) wrote : Posted in a previous version of this proposal | # |
Linting passed!
Raoul Snyman (raoul-snyman) wrote : Posted in a previous version of this proposal | # |
macOS tests passed!
Raoul Snyman (raoul-snyman) wrote : Posted in a previous version of this proposal | # |
Linux tests passed!
Raoul Snyman (raoul-snyman) wrote : Posted in a previous version of this proposal | # |
Linting passed!
Raoul Snyman (raoul-snyman) wrote : Posted in a previous version of this proposal | # |
macOS tests passed!
Tomas Groth (tomasgroth) wrote : Posted in a previous version of this proposal | # |
Looks good to me!
Tim Bentley (trb143) wrote : Posted in a previous version of this proposal | # |
Template should be in its own file so can be tested and not duplicated.
Not happy with the if conditions being removed these have been added as we sometimes get broken databases in the wild and this needs to be guarded against.
Samuel Mehrbrodt (sam92) wrote : Posted in a previous version of this proposal | # |
@Tim: As discussed on IRC, could you please review again?
I had to remove the conditions so that the variables are always available to the template, even if they are empty. They were there to make sure that no empty lines were added to the footer. But now this checking is done in the template.
Also wrt the template in its own file, after discussion we came to agreement that it's not worth the hassle (packaging etc). So we keep it just as a config string.
Tim Bentley (trb143) : Posted in a previous version of this proposal | # |
Raoul Snyman (raoul-snyman) wrote : Posted in a previous version of this proposal | # |
So, I like the principle, but I really don't think that using Mako is necessary. Do you think it's plausible to just use string formatting or string templating?
Samuel Mehrbrodt (sam92) wrote : Posted in a previous version of this proposal | # |
> Do you think it's plausible to just use string formatting or string templating?
Hm while that would allow adding a bit of markup around the strings, you can't add any control structures.
With mako you can do much more, e.g. concatenate multiple authors into one line (as we do using join), display them each on it own line.
The use case I'm especially interested in: Pick just one songbook to be displayed. Using Mako I can just write Python code to only show the songbook we are actually using in the service.
I'm sure there are more valid use cases for this.
Raoul Snyman (raoul-snyman) wrote : Posted in a previous version of this proposal | # |
You can do the same things in Python. The thing is, we no longer use Mako anywhere else. It's such a small usage, I'd really like it if we could remove the dependency.
I know we're trying to make a customisable footer, but is there any way we can do it without Mako and without introducing another dependency?
Look, if there's no other way around it, then I'll approve this, I'm just trying to reduce/minimize dependencies where possible.
Raoul Snyman (raoul-snyman) wrote : Posted in a previous version of this proposal | # |
I'm getting conflicts when merging with trunk, can you just re-merge trunk and fix the conflicts please?
Samuel Mehrbrodt (sam92) wrote : | # |
Merged trunk.
I couldn't find a better way to do this without Mako or another template engine.
Raoul Snyman (raoul-snyman) wrote : | # |
Linux tests passed!
Raoul Snyman (raoul-snyman) wrote : | # |
Linting passed!
Raoul Snyman (raoul-snyman) wrote : | # |
macOS tests passed!
Preview Diff
1 | === modified file 'openlp/core/lib/serviceitem.py' | |||
2 | --- openlp/core/lib/serviceitem.py 2019-03-17 10:36:12 +0000 | |||
3 | +++ openlp/core/lib/serviceitem.py 2019-04-08 21:08:13 +0000 | |||
4 | @@ -81,7 +81,8 @@ | |||
5 | 81 | self.items = [] | 81 | self.items = [] |
6 | 82 | self.icon = UiIcons().default | 82 | self.icon = UiIcons().default |
7 | 83 | self.raw_footer = [] | 83 | self.raw_footer = [] |
9 | 84 | self.foot_text = '' | 84 | # Plugins can set footer_html themselves. If they don't, it will be generated from raw_footer. |
10 | 85 | self.footer_html = '' | ||
11 | 85 | self.theme = None | 86 | self.theme = None |
12 | 86 | self.service_item_type = None | 87 | self.service_item_type = None |
13 | 87 | self.unique_identifier = 0 | 88 | self.unique_identifier = 0 |
14 | @@ -165,7 +166,8 @@ | |||
15 | 165 | # the dict instead of rendering them again. | 166 | # the dict instead of rendering them again. |
16 | 166 | previous_pages = {} | 167 | previous_pages = {} |
17 | 167 | index = 0 | 168 | index = 0 |
19 | 168 | self.foot_text = '<br>'.join([_f for _f in self.raw_footer if _f]) | 169 | if not self.footer_html: |
20 | 170 | self.footer_html = '<br>'.join([_f for _f in self.raw_footer if _f]) | ||
21 | 169 | for raw_slide in self.slides: | 171 | for raw_slide in self.slides: |
22 | 170 | verse_tag = raw_slide['verse'] | 172 | verse_tag = raw_slide['verse'] |
23 | 171 | if verse_tag in previous_pages and previous_pages[verse_tag][0] == raw_slide: | 173 | if verse_tag in previous_pages and previous_pages[verse_tag][0] == raw_slide: |
24 | @@ -178,7 +180,7 @@ | |||
25 | 178 | 'title': raw_slide['title'], | 180 | 'title': raw_slide['title'], |
26 | 179 | 'text': render_tags(page), | 181 | 'text': render_tags(page), |
27 | 180 | 'verse': index, | 182 | 'verse': index, |
29 | 181 | 'footer': self.foot_text, | 183 | 'footer': self.footer_html, |
30 | 182 | } | 184 | } |
31 | 183 | self._rendered_slides.append(rendered_slide) | 185 | self._rendered_slides.append(rendered_slide) |
32 | 184 | display_slide = { | 186 | display_slide = { |
33 | 185 | 187 | ||
34 | === modified file 'openlp/core/ui/printserviceform.py' | |||
35 | --- openlp/core/ui/printserviceform.py 2019-02-14 15:09:09 +0000 | |||
36 | +++ openlp/core/ui/printserviceform.py 2019-04-08 21:08:13 +0000 | |||
37 | @@ -235,11 +235,11 @@ | |||
38 | 235 | for slide in range(len(item.get_frames())): | 235 | for slide in range(len(item.get_frames())): |
39 | 236 | self._add_element('li', item.get_frame_title(slide), ol) | 236 | self._add_element('li', item.get_frame_title(slide), ol) |
40 | 237 | # add footer | 237 | # add footer |
46 | 238 | foot_text = item.foot_text | 238 | footer_html = item.footer_html |
47 | 239 | foot_text = foot_text.partition('<br>')[2] | 239 | footer_html = footer_html.partition('<br>')[2] |
48 | 240 | if foot_text: | 240 | if footer_html: |
49 | 241 | foot_text = html.escape(foot_text.replace('<br>', '\n')) | 241 | footer_html = html.escape(footer_html.replace('<br>', '\n')) |
50 | 242 | self._add_element('div', foot_text.replace('\n', '<br>'), parent=div, class_id='itemFooter') | 242 | self._add_element('div', footer_html.replace('\n', '<br>'), parent=div, classId='itemFooter') |
51 | 243 | # Add service items' notes. | 243 | # Add service items' notes. |
52 | 244 | if self.notes_check_box.isChecked(): | 244 | if self.notes_check_box.isChecked(): |
53 | 245 | if item.notes: | 245 | if item.notes: |
54 | 246 | 246 | ||
55 | === modified file 'openlp/plugins/songs/lib/mediaitem.py' | |||
56 | --- openlp/plugins/songs/lib/mediaitem.py 2019-03-28 20:19:15 +0000 | |||
57 | +++ openlp/plugins/songs/lib/mediaitem.py 2019-04-08 21:08:13 +0000 | |||
58 | @@ -21,6 +21,7 @@ | |||
59 | 21 | ############################################################################### | 21 | ############################################################################### |
60 | 22 | import logging | 22 | import logging |
61 | 23 | import os | 23 | import os |
62 | 24 | import mako | ||
63 | 24 | 25 | ||
64 | 25 | from PyQt5 import QtCore, QtWidgets | 26 | from PyQt5 import QtCore, QtWidgets |
65 | 26 | from sqlalchemy.sql import and_, or_ | 27 | from sqlalchemy.sql import and_, or_ |
66 | @@ -35,7 +36,7 @@ | |||
67 | 35 | from openlp.core.lib.mediamanageritem import MediaManagerItem | 36 | from openlp.core.lib.mediamanageritem import MediaManagerItem |
68 | 36 | from openlp.core.lib.plugin import PluginStatus | 37 | from openlp.core.lib.plugin import PluginStatus |
69 | 37 | from openlp.core.lib.serviceitem import ItemCapabilities | 38 | from openlp.core.lib.serviceitem import ItemCapabilities |
71 | 38 | from openlp.core.lib.ui import create_widget_action | 39 | from openlp.core.lib.ui import create_widget_action, critical_error_message_box |
72 | 39 | from openlp.core.ui.icons import UiIcons | 40 | from openlp.core.ui.icons import UiIcons |
73 | 40 | from openlp.plugins.songs.forms.editsongform import EditSongForm | 41 | from openlp.plugins.songs.forms.editsongform import EditSongForm |
74 | 41 | from openlp.plugins.songs.forms.songexportform import SongExportForm | 42 | from openlp.plugins.songs.forms.songexportform import SongExportForm |
75 | @@ -131,9 +132,6 @@ | |||
76 | 131 | self.is_search_as_you_type_enabled = Settings().value('advanced/search as type') | 132 | self.is_search_as_you_type_enabled = Settings().value('advanced/search as type') |
77 | 132 | self.update_service_on_edit = Settings().value(self.settings_section + '/update service on edit') | 133 | self.update_service_on_edit = Settings().value(self.settings_section + '/update service on edit') |
78 | 133 | self.add_song_from_service = Settings().value(self.settings_section + '/add song from service') | 134 | self.add_song_from_service = Settings().value(self.settings_section + '/add song from service') |
79 | 134 | self.display_songbook = Settings().value(self.settings_section + '/display songbook') | ||
80 | 135 | self.display_written_by_text = Settings().value(self.settings_section + '/display written by') | ||
81 | 136 | self.display_copyright_symbol = Settings().value(self.settings_section + '/display copyright symbol') | ||
82 | 137 | 135 | ||
83 | 138 | def retranslate_ui(self): | 136 | def retranslate_ui(self): |
84 | 139 | self.search_text_label.setText('{text}:'.format(text=UiStrings().Search)) | 137 | self.search_text_label.setText('{text}:'.format(text=UiStrings().Search)) |
85 | @@ -677,12 +675,8 @@ | |||
86 | 677 | item.raw_footer = [] | 675 | item.raw_footer = [] |
87 | 678 | item.raw_footer.append(song.title) | 676 | item.raw_footer.append(song.title) |
88 | 679 | if authors_none: | 677 | if authors_none: |
95 | 680 | # If the setting for showing "Written by:" is enabled, show it before unspecified authors. | 678 | item.raw_footer.append("{text}: {authors}".format(text=translate('OpenLP.Ui', 'Written by'), |
96 | 681 | if Settings().value('songs/display written by'): | 679 | authors=create_separated_list(authors_none))) |
91 | 682 | item.raw_footer.append("{text}: {authors}".format(text=translate('OpenLP.Ui', 'Written by'), | ||
92 | 683 | authors=create_separated_list(authors_none))) | ||
93 | 684 | else: | ||
94 | 685 | item.raw_footer.append("{authors}".format(authors=create_separated_list(authors_none))) | ||
97 | 686 | if authors_words_music: | 680 | if authors_words_music: |
98 | 687 | item.raw_footer.append("{text}: {authors}".format(text=AuthorType.Types[AuthorType.WordsAndMusic], | 681 | item.raw_footer.append("{text}: {authors}".format(text=AuthorType.Types[AuthorType.WordsAndMusic], |
99 | 688 | authors=create_separated_list(authors_words_music))) | 682 | authors=create_separated_list(authors_words_music))) |
100 | @@ -696,34 +690,44 @@ | |||
101 | 696 | item.raw_footer.append("{text}: {authors}".format(text=AuthorType.Types[AuthorType.Translation], | 690 | item.raw_footer.append("{text}: {authors}".format(text=AuthorType.Types[AuthorType.Translation], |
102 | 697 | authors=create_separated_list(authors_translation))) | 691 | authors=create_separated_list(authors_translation))) |
103 | 698 | if song.copyright: | 692 | if song.copyright: |
111 | 699 | if self.display_copyright_symbol: | 693 | item.raw_footer.append("{symbol} {song}".format(symbol=SongStrings.CopyrightSymbol, |
112 | 700 | item.raw_footer.append("{symbol} {song}".format(symbol=SongStrings.CopyrightSymbol, | 694 | song=song.copyright)) |
113 | 701 | song=song.copyright)) | 695 | songbooks = [str(songbook_entry) for songbook_entry in song.songbook_entries] |
114 | 702 | else: | 696 | if song.songbook_entries: |
108 | 703 | item.raw_footer.append(song.copyright) | ||
109 | 704 | if self.display_songbook and song.songbook_entries: | ||
110 | 705 | songbooks = [str(songbook_entry) for songbook_entry in song.songbook_entries] | ||
115 | 706 | item.raw_footer.append(", ".join(songbooks)) | 697 | item.raw_footer.append(", ".join(songbooks)) |
116 | 707 | if Settings().value('core/ccli number'): | 698 | if Settings().value('core/ccli number'): |
136 | 708 | item.raw_footer.append(translate('SongsPlugin.MediaItem', | 699 | item.raw_footer.append(translate('SongsPlugin.MediaItem', 'CCLI License: ') + |
137 | 709 | 'CCLI License: ') + Settings().value('core/ccli number')) | 700 | Settings().value('core/ccli number')) |
138 | 710 | item.metadata.append('<em>{label}:</em> {title}'.format(label=translate('SongsPlugin.MediaItem', 'Title'), | 701 | footer_template = Settings().value('songs/footer template') |
139 | 711 | title=song.title)) | 702 | # Keep this in sync with the list in songstab.py |
140 | 712 | if song.alternate_title: | 703 | vars = { |
141 | 713 | item.metadata.append('<em>{label}:</em> {title}'. | 704 | 'title': song.title, |
142 | 714 | format(label=translate('SongsPlugin.MediaItem', 'Alt Title'), | 705 | 'alternate_title': song.alternate_title, |
143 | 715 | title=song.alternate_title)) | 706 | 'authors_none_label': translate('OpenLP.Ui', 'Written by'), |
144 | 716 | if song.songbook_entries: | 707 | 'authors_none': authors_none, |
145 | 717 | for songbook_entry in song.songbook_entries: | 708 | 'authors_words_label': AuthorType.Types[AuthorType.Words], |
146 | 718 | item.metadata.append('<em>{label}:</em> {book}/{num}/{pub}'. | 709 | 'authors_words': authors_words, |
147 | 719 | format(label=translate('SongsPlugin.MediaItem', 'Songbook'), | 710 | 'authors_music_label': AuthorType.Types[AuthorType.Music], |
148 | 720 | book=songbook_entry.songbook.name, | 711 | 'authors_music': authors_music, |
149 | 721 | num=songbook_entry.entry, | 712 | 'authors_words_music_label': AuthorType.Types[AuthorType.WordsAndMusic], |
150 | 722 | pub=songbook_entry.songbook.publisher)) | 713 | 'authors_words_music': authors_words_music, |
151 | 723 | if song.topics: | 714 | 'authors_translation_label': AuthorType.Types[AuthorType.Translation], |
152 | 724 | for topics in song.topics: | 715 | 'authors_translation': authors_translation, |
153 | 725 | item.metadata.append('<em>{label}:</em> {topic}'. | 716 | 'authors_words_all': authors_words + authors_words_music, |
154 | 726 | format(label=translate('SongsPlugin.MediaItem', 'Topic'), topic=topics.name)) | 717 | 'authors_music_all': authors_music + authors_words_music, |
155 | 718 | 'copyright': song.copyright, | ||
156 | 719 | 'songbook_entries': songbooks, | ||
157 | 720 | 'ccli_license': Settings().value('core/ccli number'), | ||
158 | 721 | 'ccli_license_label': translate('SongsPlugin.MediaItem', 'CCLI License'), | ||
159 | 722 | 'ccli_number': song.ccli_number, | ||
160 | 723 | 'topics': [topic.name for topic in song.topics] | ||
161 | 724 | } | ||
162 | 725 | try: | ||
163 | 726 | item.footer_html = mako.template.Template(footer_template).render_unicode(**vars).replace('\n', '') | ||
164 | 727 | except mako.exceptions.SyntaxException: | ||
165 | 728 | log.error('Failed to render Song footer html:\n' + mako.exceptions.text_error_template().render()) | ||
166 | 729 | critical_error_message_box(message=translate('SongsPlugin.MediaItem', | ||
167 | 730 | 'Failed to render Song footer html.\nSee log for details')) | ||
168 | 727 | return authors_all | 731 | return authors_all |
169 | 728 | 732 | ||
170 | 729 | def service_load(self, item): | 733 | def service_load(self, item): |
171 | 730 | 734 | ||
172 | === modified file 'openlp/plugins/songs/lib/songstab.py' | |||
173 | --- openlp/plugins/songs/lib/songstab.py 2019-02-22 07:34:40 +0000 | |||
174 | +++ openlp/plugins/songs/lib/songstab.py 2019-04-08 21:08:13 +0000 | |||
175 | @@ -25,7 +25,7 @@ | |||
176 | 25 | from openlp.core.common.i18n import translate | 25 | from openlp.core.common.i18n import translate |
177 | 26 | from openlp.core.common.settings import Settings | 26 | from openlp.core.common.settings import Settings |
178 | 27 | from openlp.core.lib.settingstab import SettingsTab | 27 | from openlp.core.lib.settingstab import SettingsTab |
180 | 28 | from openlp.plugins.songs.lib.ui import SongStrings | 28 | from openlp.plugins.songs.lib.db import AuthorType |
181 | 29 | 29 | ||
182 | 30 | 30 | ||
183 | 31 | class SongsTab(SettingsTab): | 31 | class SongsTab(SettingsTab): |
184 | @@ -54,15 +54,6 @@ | |||
185 | 54 | self.songbook_slide_check_box = QtWidgets.QCheckBox(self.mode_group_box) | 54 | self.songbook_slide_check_box = QtWidgets.QCheckBox(self.mode_group_box) |
186 | 55 | self.songbook_slide_check_box.setObjectName('songbook_slide_check_box') | 55 | self.songbook_slide_check_box.setObjectName('songbook_slide_check_box') |
187 | 56 | self.mode_layout.addWidget(self.songbook_slide_check_box) | 56 | self.mode_layout.addWidget(self.songbook_slide_check_box) |
188 | 57 | self.display_songbook_check_box = QtWidgets.QCheckBox(self.mode_group_box) | ||
189 | 58 | self.display_songbook_check_box.setObjectName('songbook_check_box') | ||
190 | 59 | self.mode_layout.addWidget(self.display_songbook_check_box) | ||
191 | 60 | self.display_written_by_check_box = QtWidgets.QCheckBox(self.mode_group_box) | ||
192 | 61 | self.display_written_by_check_box.setObjectName('written_by_check_box') | ||
193 | 62 | self.mode_layout.addWidget(self.display_written_by_check_box) | ||
194 | 63 | self.display_copyright_check_box = QtWidgets.QCheckBox(self.mode_group_box) | ||
195 | 64 | self.display_copyright_check_box.setObjectName('copyright_check_box') | ||
196 | 65 | self.mode_layout.addWidget(self.display_copyright_check_box) | ||
197 | 66 | self.left_layout.addWidget(self.mode_group_box) | 57 | self.left_layout.addWidget(self.mode_group_box) |
198 | 67 | # Chords group box | 58 | # Chords group box |
199 | 68 | self.chords_group_box = QtWidgets.QGroupBox(self.left_column) | 59 | self.chords_group_box = QtWidgets.QGroupBox(self.left_column) |
200 | @@ -93,20 +84,34 @@ | |||
201 | 93 | self.neolatin_notation_radio_button.setObjectName('neolatin_notation_radio_button') | 84 | self.neolatin_notation_radio_button.setObjectName('neolatin_notation_radio_button') |
202 | 94 | self.chords_layout.addWidget(self.neolatin_notation_radio_button) | 85 | self.chords_layout.addWidget(self.neolatin_notation_radio_button) |
203 | 95 | self.left_layout.addWidget(self.chords_group_box) | 86 | self.left_layout.addWidget(self.chords_group_box) |
204 | 87 | # Footer group box | ||
205 | 88 | self.footer_group_box = QtWidgets.QGroupBox(self.left_column) | ||
206 | 89 | self.footer_group_box.setObjectName('footer_group_box') | ||
207 | 90 | self.footer_layout = QtWidgets.QVBoxLayout(self.footer_group_box) | ||
208 | 91 | self.footer_layout.setObjectName('chords_layout') | ||
209 | 92 | self.footer_info_label = QtWidgets.QLabel(self.footer_group_box) | ||
210 | 93 | self.footer_layout.addWidget(self.footer_info_label) | ||
211 | 94 | self.footer_placeholder_info = QtWidgets.QTextEdit(self.footer_group_box) | ||
212 | 95 | self.footer_layout.addWidget(self.footer_placeholder_info) | ||
213 | 96 | self.footer_desc_label = QtWidgets.QLabel(self.footer_group_box) | ||
214 | 97 | self.footer_layout.addWidget(self.footer_desc_label) | ||
215 | 98 | self.footer_edit_box = QtWidgets.QTextEdit(self.footer_group_box) | ||
216 | 99 | self.footer_layout.addWidget(self.footer_edit_box) | ||
217 | 100 | self.footer_reset_button = QtWidgets.QPushButton(self.footer_group_box) | ||
218 | 101 | self.footer_layout.addWidget(self.footer_reset_button, alignment=QtCore.Qt.AlignRight) | ||
219 | 102 | self.right_layout.addWidget(self.footer_group_box) | ||
220 | 96 | self.left_layout.addStretch() | 103 | self.left_layout.addStretch() |
221 | 97 | self.right_layout.addStretch() | 104 | self.right_layout.addStretch() |
222 | 98 | self.tool_bar_active_check_box.stateChanged.connect(self.on_tool_bar_active_check_box_changed) | 105 | self.tool_bar_active_check_box.stateChanged.connect(self.on_tool_bar_active_check_box_changed) |
223 | 99 | self.update_on_edit_check_box.stateChanged.connect(self.on_update_on_edit_check_box_changed) | 106 | self.update_on_edit_check_box.stateChanged.connect(self.on_update_on_edit_check_box_changed) |
224 | 100 | self.add_from_service_check_box.stateChanged.connect(self.on_add_from_service_check_box_changed) | 107 | self.add_from_service_check_box.stateChanged.connect(self.on_add_from_service_check_box_changed) |
225 | 101 | self.songbook_slide_check_box.stateChanged.connect(self.on_songbook_slide_check_box_changed) | 108 | self.songbook_slide_check_box.stateChanged.connect(self.on_songbook_slide_check_box_changed) |
226 | 102 | self.display_songbook_check_box.stateChanged.connect(self.on_songbook_check_box_changed) | ||
227 | 103 | self.display_written_by_check_box.stateChanged.connect(self.on_written_by_check_box_changed) | ||
228 | 104 | self.display_copyright_check_box.stateChanged.connect(self.on_copyright_check_box_changed) | ||
229 | 105 | self.mainview_chords_check_box.stateChanged.connect(self.on_mainview_chords_check_box_changed) | 109 | self.mainview_chords_check_box.stateChanged.connect(self.on_mainview_chords_check_box_changed) |
230 | 106 | self.disable_chords_import_check_box.stateChanged.connect(self.on_disable_chords_import_check_box_changed) | 110 | self.disable_chords_import_check_box.stateChanged.connect(self.on_disable_chords_import_check_box_changed) |
231 | 107 | self.english_notation_radio_button.clicked.connect(self.on_english_notation_button_clicked) | 111 | self.english_notation_radio_button.clicked.connect(self.on_english_notation_button_clicked) |
232 | 108 | self.german_notation_radio_button.clicked.connect(self.on_german_notation_button_clicked) | 112 | self.german_notation_radio_button.clicked.connect(self.on_german_notation_button_clicked) |
233 | 109 | self.neolatin_notation_radio_button.clicked.connect(self.on_neolatin_notation_button_clicked) | 113 | self.neolatin_notation_radio_button.clicked.connect(self.on_neolatin_notation_button_clicked) |
234 | 114 | self.footer_reset_button.clicked.connect(self.on_footer_reset_button_clicked) | ||
235 | 110 | 115 | ||
236 | 111 | def retranslate_ui(self): | 116 | def retranslate_ui(self): |
237 | 112 | self.mode_group_box.setTitle(translate('SongsPlugin.SongsTab', 'Song related settings')) | 117 | self.mode_group_box.setTitle(translate('SongsPlugin.SongsTab', 'Song related settings')) |
238 | @@ -117,12 +122,6 @@ | |||
239 | 117 | 'Import missing songs from Service files')) | 122 | 'Import missing songs from Service files')) |
240 | 118 | self.songbook_slide_check_box.setText(translate('SongsPlugin.SongsTab', | 123 | self.songbook_slide_check_box.setText(translate('SongsPlugin.SongsTab', |
241 | 119 | 'Add Songbooks as first slide')) | 124 | 'Add Songbooks as first slide')) |
242 | 120 | self.display_songbook_check_box.setText(translate('SongsPlugin.SongsTab', 'Display songbook in footer')) | ||
243 | 121 | self.display_written_by_check_box.setText(translate( | ||
244 | 122 | 'SongsPlugin.SongsTab', 'Show "Written by:" in footer for unspecified authors')) | ||
245 | 123 | self.display_copyright_check_box.setText(translate('SongsPlugin.SongsTab', | ||
246 | 124 | 'Display "{symbol}" symbol before copyright ' | ||
247 | 125 | 'info').format(symbol=SongStrings.CopyrightSymbol)) | ||
248 | 126 | self.chords_info_label.setText(translate('SongsPlugin.SongsTab', 'If enabled all text between "[" and "]" will ' | 125 | self.chords_info_label.setText(translate('SongsPlugin.SongsTab', 'If enabled all text between "[" and "]" will ' |
249 | 127 | 'be regarded as chords.')) | 126 | 'be regarded as chords.')) |
250 | 128 | self.chords_group_box.setTitle(translate('SongsPlugin.SongsTab', 'Chords')) | 127 | self.chords_group_box.setTitle(translate('SongsPlugin.SongsTab', 'Chords')) |
251 | @@ -134,6 +133,53 @@ | |||
252 | 134 | self.german_notation_radio_button.setText(translate('SongsPlugin.SongsTab', 'German') + ' (C-D-E-F-G-A-H)') | 133 | self.german_notation_radio_button.setText(translate('SongsPlugin.SongsTab', 'German') + ' (C-D-E-F-G-A-H)') |
253 | 135 | self.neolatin_notation_radio_button.setText( | 134 | self.neolatin_notation_radio_button.setText( |
254 | 136 | translate('SongsPlugin.SongsTab', 'Neo-Latin') + ' (Do-Re-Mi-Fa-Sol-La-Si)') | 135 | translate('SongsPlugin.SongsTab', 'Neo-Latin') + ' (Do-Re-Mi-Fa-Sol-La-Si)') |
255 | 136 | self.footer_group_box.setTitle(translate('SongsPlugin.SongsTab', 'Footer')) | ||
256 | 137 | # Keep this in sync with the list in mediaitem.py | ||
257 | 138 | const = '<code>"{}"</code>' | ||
258 | 139 | placeholders = [ | ||
259 | 140 | # placeholder, description, can be empty, is a list | ||
260 | 141 | ['title', translate('SongsPlugin.SongsTab', 'Song Title'), False, False], | ||
261 | 142 | ['alternate_title', translate('SongsPlugin.SongsTab', 'Alternate Title'), True, False], | ||
262 | 143 | ['written_by', const.format(translate('SongsPlugin.SongsTab', 'Written By')), True, False], | ||
263 | 144 | ['authors_none', translate('SongsPlugin.SongsTab', 'Authors when type is not set'), False, True], | ||
264 | 145 | ['authors_words_label', const.format(AuthorType.Types[AuthorType.Words]), False, False], | ||
265 | 146 | ['authors_words', translate('SongsPlugin.SongsTab', 'Authors (Type "Words")'), False, True], | ||
266 | 147 | ['authors_music_label', const.format(AuthorType.Types[AuthorType.Music]), False, False], | ||
267 | 148 | ['authors_music', translate('SongsPlugin.SongsTab', 'Authors (Type "Music")'), False, True], | ||
268 | 149 | ['authors_words_music_label', const.format(AuthorType.Types[AuthorType.WordsAndMusic]), False, False], | ||
269 | 150 | ['authors_words_music', translate('SongsPlugin.SongsTab', 'Authors (Type "Words and Music")'), False, True], | ||
270 | 151 | ['authors_translation_label', const.format(AuthorType.Types[AuthorType.Translation]), False, False], | ||
271 | 152 | ['authors_translation', translate('SongsPlugin.SongsTab', 'Authors (Type "Translation")'), False, True], | ||
272 | 153 | ['authors_words_all', translate('SongsPlugin.SongsTab', 'Authors (Type "Words" & "Words and Music")'), | ||
273 | 154 | False, True], | ||
274 | 155 | ['authors_music_all', translate('SongsPlugin.SongsTab', 'Authors (Type "Music" & "Words and Music")'), | ||
275 | 156 | False, True], | ||
276 | 157 | ['copyright', translate('SongsPlugin.SongsTab', 'Copyright information'), True, False], | ||
277 | 158 | ['songbook_entries', translate('SongsPlugin.SongsTab', 'Songbook Entries'), False, True], | ||
278 | 159 | ['ccli_license', translate('SongsPlugin.SongsTab', 'CCLI License'), True, False], | ||
279 | 160 | ['ccli_license_label', const.format(translate('SongsPlugin.SongsTab', 'CCLI License')), False, False], | ||
280 | 161 | ['ccli_number', translate('SongsPlugin.SongsTab', 'Song CCLI Number'), True, False], | ||
281 | 162 | ['topics', translate('SongsPlugin.SongsTab', 'Topics'), False, True], | ||
282 | 163 | ] | ||
283 | 164 | placeholder_info = '<table style="background: #eee">\n<tr><th><b>{ph}</b></th><th><b>{desc}</b></th></tr>\n'\ | ||
284 | 165 | .format(ph=translate('SongsPlugin.SongsTab', 'Placeholder'), | ||
285 | 166 | desc=translate('SongsPlugin.SongsTab', 'Description')) | ||
286 | 167 | for placeholder in placeholders: | ||
287 | 168 | placeholder_info += '<tr><td>${{{pl}}}</td><td>{des}{opt}</td></tr>\n'\ | ||
288 | 169 | .format(pl=placeholder[0], des=placeholder[1], | ||
289 | 170 | opt=(' ¹' if placeholder[2] else '') + | ||
290 | 171 | (' ²' if placeholder[3] else '')) | ||
291 | 172 | placeholder_info += '</table>' | ||
292 | 173 | placeholder_info += '\n<br/>¹ {}'.format(translate('SongsPlugin.SongsTab', 'can be empty')) | ||
293 | 174 | placeholder_info += '\n<br/>² {}'.format(translate('SongsPlugin.SongsTab', 'list of entries, can be empty')) | ||
294 | 175 | self.footer_placeholder_info.setHtml(placeholder_info) | ||
295 | 176 | self.footer_placeholder_info.setReadOnly(True) | ||
296 | 177 | |||
297 | 178 | self.footer_info_label.setText(translate('SongsPlugin.SongsTab', 'How to use Footers:')) | ||
298 | 179 | self.footer_desc_label.setText('{} (<a href="http://docs.makotemplates.org">{}</a>):' | ||
299 | 180 | .format(translate('SongsPlugin.SongsTab', 'Footer Template'), | ||
300 | 181 | translate('SongsPlugin.SongsTab', 'Mako Syntax'))) | ||
301 | 182 | self.footer_reset_button.setText(translate('SongsPlugin.SongsTab', 'Reset Template')) | ||
302 | 137 | 183 | ||
303 | 138 | def on_search_as_type_check_box_changed(self, check_state): | 184 | def on_search_as_type_check_box_changed(self, check_state): |
304 | 139 | self.song_search = (check_state == QtCore.Qt.Checked) | 185 | self.song_search = (check_state == QtCore.Qt.Checked) |
305 | @@ -150,15 +196,6 @@ | |||
306 | 150 | def on_songbook_slide_check_box_changed(self, check_state): | 196 | def on_songbook_slide_check_box_changed(self, check_state): |
307 | 151 | self.songbook_slide = (check_state == QtCore.Qt.Checked) | 197 | self.songbook_slide = (check_state == QtCore.Qt.Checked) |
308 | 152 | 198 | ||
309 | 153 | def on_songbook_check_box_changed(self, check_state): | ||
310 | 154 | self.display_songbook = (check_state == QtCore.Qt.Checked) | ||
311 | 155 | |||
312 | 156 | def on_written_by_check_box_changed(self, check_state): | ||
313 | 157 | self.display_written_by = (check_state == QtCore.Qt.Checked) | ||
314 | 158 | |||
315 | 159 | def on_copyright_check_box_changed(self, check_state): | ||
316 | 160 | self.display_copyright_symbol = (check_state == QtCore.Qt.Checked) | ||
317 | 161 | |||
318 | 162 | def on_mainview_chords_check_box_changed(self, check_state): | 199 | def on_mainview_chords_check_box_changed(self, check_state): |
319 | 163 | self.mainview_chords = (check_state == QtCore.Qt.Checked) | 200 | self.mainview_chords = (check_state == QtCore.Qt.Checked) |
320 | 164 | 201 | ||
321 | @@ -174,6 +211,9 @@ | |||
322 | 174 | def on_neolatin_notation_button_clicked(self): | 211 | def on_neolatin_notation_button_clicked(self): |
323 | 175 | self.chord_notation = 'neo-latin' | 212 | self.chord_notation = 'neo-latin' |
324 | 176 | 213 | ||
325 | 214 | def on_footer_reset_button_clicked(self): | ||
326 | 215 | self.footer_edit_box.setPlainText(Settings().get_default_value('songs/footer template')) | ||
327 | 216 | |||
328 | 177 | def load(self): | 217 | def load(self): |
329 | 178 | settings = Settings() | 218 | settings = Settings() |
330 | 179 | settings.beginGroup(self.settings_section) | 219 | settings.beginGroup(self.settings_section) |
331 | @@ -181,9 +221,6 @@ | |||
332 | 181 | self.update_edit = settings.value('update service on edit') | 221 | self.update_edit = settings.value('update service on edit') |
333 | 182 | self.update_load = settings.value('add song from service') | 222 | self.update_load = settings.value('add song from service') |
334 | 183 | self.songbook_slide = settings.value('add songbook slide') | 223 | self.songbook_slide = settings.value('add songbook slide') |
335 | 184 | self.display_songbook = settings.value('display songbook') | ||
336 | 185 | self.display_written_by = settings.value('display written by') | ||
337 | 186 | self.display_copyright_symbol = settings.value('display copyright symbol') | ||
338 | 187 | self.enable_chords = settings.value('enable chords') | 224 | self.enable_chords = settings.value('enable chords') |
339 | 188 | self.chord_notation = settings.value('chord notation') | 225 | self.chord_notation = settings.value('chord notation') |
340 | 189 | self.mainview_chords = settings.value('mainview chords') | 226 | self.mainview_chords = settings.value('mainview chords') |
341 | @@ -191,9 +228,6 @@ | |||
342 | 191 | self.tool_bar_active_check_box.setChecked(self.tool_bar) | 228 | self.tool_bar_active_check_box.setChecked(self.tool_bar) |
343 | 192 | self.update_on_edit_check_box.setChecked(self.update_edit) | 229 | self.update_on_edit_check_box.setChecked(self.update_edit) |
344 | 193 | self.add_from_service_check_box.setChecked(self.update_load) | 230 | self.add_from_service_check_box.setChecked(self.update_load) |
345 | 194 | self.display_songbook_check_box.setChecked(self.display_songbook) | ||
346 | 195 | self.display_written_by_check_box.setChecked(self.display_written_by) | ||
347 | 196 | self.display_copyright_check_box.setChecked(self.display_copyright_symbol) | ||
348 | 197 | self.chords_group_box.setChecked(self.enable_chords) | 231 | self.chords_group_box.setChecked(self.enable_chords) |
349 | 198 | self.mainview_chords_check_box.setChecked(self.mainview_chords) | 232 | self.mainview_chords_check_box.setChecked(self.mainview_chords) |
350 | 199 | self.disable_chords_import_check_box.setChecked(self.disable_chords_import) | 233 | self.disable_chords_import_check_box.setChecked(self.disable_chords_import) |
351 | @@ -203,6 +237,7 @@ | |||
352 | 203 | self.neolatin_notation_radio_button.setChecked(True) | 237 | self.neolatin_notation_radio_button.setChecked(True) |
353 | 204 | else: | 238 | else: |
354 | 205 | self.english_notation_radio_button.setChecked(True) | 239 | self.english_notation_radio_button.setChecked(True) |
355 | 240 | self.footer_edit_box.setPlainText(settings.value('footer template')) | ||
356 | 206 | settings.endGroup() | 241 | settings.endGroup() |
357 | 207 | 242 | ||
358 | 208 | def save(self): | 243 | def save(self): |
359 | @@ -211,13 +246,13 @@ | |||
360 | 211 | settings.setValue('display songbar', self.tool_bar) | 246 | settings.setValue('display songbar', self.tool_bar) |
361 | 212 | settings.setValue('update service on edit', self.update_edit) | 247 | settings.setValue('update service on edit', self.update_edit) |
362 | 213 | settings.setValue('add song from service', self.update_load) | 248 | settings.setValue('add song from service', self.update_load) |
363 | 214 | settings.setValue('display songbook', self.display_songbook) | ||
364 | 215 | settings.setValue('display written by', self.display_written_by) | ||
365 | 216 | settings.setValue('display copyright symbol', self.display_copyright_symbol) | ||
366 | 217 | settings.setValue('enable chords', self.chords_group_box.isChecked()) | 249 | settings.setValue('enable chords', self.chords_group_box.isChecked()) |
367 | 218 | settings.setValue('mainview chords', self.mainview_chords) | 250 | settings.setValue('mainview chords', self.mainview_chords) |
368 | 219 | settings.setValue('disable chords import', self.disable_chords_import) | 251 | settings.setValue('disable chords import', self.disable_chords_import) |
369 | 220 | settings.setValue('chord notation', self.chord_notation) | 252 | settings.setValue('chord notation', self.chord_notation) |
370 | 253 | # Only save footer template if it has been changed. This allows future updates | ||
371 | 254 | if self.footer_edit_box.toPlainText() != Settings().get_default_value('songs/footer template'): | ||
372 | 255 | settings.setValue('footer template', self.footer_edit_box.toPlainText()) | ||
373 | 221 | settings.setValue('add songbook slide', self.songbook_slide) | 256 | settings.setValue('add songbook slide', self.songbook_slide) |
374 | 222 | settings.endGroup() | 257 | settings.endGroup() |
375 | 223 | if self.tab_visited: | 258 | if self.tab_visited: |
376 | 224 | 259 | ||
377 | === modified file 'openlp/plugins/songs/songsplugin.py' | |||
378 | --- openlp/plugins/songs/songsplugin.py 2019-03-16 10:58:59 +0000 | |||
379 | +++ openlp/plugins/songs/songsplugin.py 2019-04-08 21:08:13 +0000 | |||
380 | @@ -66,11 +66,8 @@ | |||
381 | 66 | 'songs/add song from service': True, | 66 | 'songs/add song from service': True, |
382 | 67 | 'songs/add songbook slide': False, | 67 | 'songs/add songbook slide': False, |
383 | 68 | 'songs/display songbar': True, | 68 | 'songs/display songbar': True, |
389 | 69 | 'songs/display songbook': False, | 69 | 'songs/last directory import': '', |
390 | 70 | 'songs/display written by': True, | 70 | 'songs/last directory export': '', |
386 | 71 | 'songs/display copyright symbol': False, | ||
387 | 72 | 'songs/last directory import': None, | ||
388 | 73 | 'songs/last directory export': None, | ||
391 | 74 | 'songs/songselect username': '', | 71 | 'songs/songselect username': '', |
392 | 75 | 'songs/songselect password': '', | 72 | 'songs/songselect password': '', |
393 | 76 | 'songs/songselect searches': '', | 73 | 'songs/songselect searches': '', |
394 | @@ -78,6 +75,59 @@ | |||
395 | 78 | 'songs/chord notation': 'english', # Can be english, german or neo-latin | 75 | 'songs/chord notation': 'english', # Can be english, german or neo-latin |
396 | 79 | 'songs/mainview chords': False, | 76 | 'songs/mainview chords': False, |
397 | 80 | 'songs/disable chords import': False, | 77 | 'songs/disable chords import': False, |
398 | 78 | 'songs/footer template': """\ | ||
399 | 79 | ${title}<br/> | ||
400 | 80 | |||
401 | 81 | %if authors_none: | ||
402 | 82 | <% | ||
403 | 83 | authors = ", ".join(authors_none) | ||
404 | 84 | %> | ||
405 | 85 | ${authors_none_label}: ${authors}<br/> | ||
406 | 86 | %endif | ||
407 | 87 | |||
408 | 88 | %if authors_words_music: | ||
409 | 89 | <% | ||
410 | 90 | authors = ", ".join(authors_words_music) | ||
411 | 91 | %> | ||
412 | 92 | ${authors_words_music_label}: ${authors}<br/> | ||
413 | 93 | %endif | ||
414 | 94 | |||
415 | 95 | %if authors_words: | ||
416 | 96 | <% | ||
417 | 97 | authors = ", ".join(authors_words) | ||
418 | 98 | %> | ||
419 | 99 | ${authors_words_label}: ${authors}<br/> | ||
420 | 100 | %endif | ||
421 | 101 | |||
422 | 102 | %if authors_music: | ||
423 | 103 | <% | ||
424 | 104 | authors = ", ".join(authors_music) | ||
425 | 105 | %> | ||
426 | 106 | ${authors_music_label}: ${authors}<br/> | ||
427 | 107 | %endif | ||
428 | 108 | |||
429 | 109 | %if authors_translation: | ||
430 | 110 | <% | ||
431 | 111 | authors = ", ".join(authors_translation) | ||
432 | 112 | %> | ||
433 | 113 | ${authors_translation_label}: ${authors}<br/> | ||
434 | 114 | %endif | ||
435 | 115 | |||
436 | 116 | %if copyright: | ||
437 | 117 | © ${copyright}<br/> | ||
438 | 118 | %endif | ||
439 | 119 | |||
440 | 120 | %if songbook_entries: | ||
441 | 121 | <% | ||
442 | 122 | entries = ", ".join(songbook_entries) | ||
443 | 123 | %> | ||
444 | 124 | ${entries}<br/> | ||
445 | 125 | %endif | ||
446 | 126 | |||
447 | 127 | %if ccli_license: | ||
448 | 128 | ${ccli_license_label} ${ccli_license}<br/> | ||
449 | 129 | %endif | ||
450 | 130 | """, | ||
451 | 81 | } | 131 | } |
452 | 82 | 132 | ||
453 | 83 | 133 | ||
454 | 84 | 134 | ||
455 | === modified file 'tests/functional/openlp_plugins/songs/test_mediaitem.py' | |||
456 | --- tests/functional/openlp_plugins/songs/test_mediaitem.py 2019-02-14 15:09:09 +0000 | |||
457 | +++ tests/functional/openlp_plugins/songs/test_mediaitem.py 2019-04-08 21:08:13 +0000 | |||
458 | @@ -34,6 +34,62 @@ | |||
459 | 34 | from openlp.plugins.songs.lib.mediaitem import SongMediaItem | 34 | from openlp.plugins.songs.lib.mediaitem import SongMediaItem |
460 | 35 | from tests.helpers.testmixin import TestMixin | 35 | from tests.helpers.testmixin import TestMixin |
461 | 36 | 36 | ||
462 | 37 | __default_settings__ = { | ||
463 | 38 | 'songs/footer template': """ | ||
464 | 39 | ${title}<br/> | ||
465 | 40 | |||
466 | 41 | %if authors_none: | ||
467 | 42 | <% | ||
468 | 43 | authors = ", ".join(authors_none) | ||
469 | 44 | %> | ||
470 | 45 | ${authors_none_label}: ${authors}<br/> | ||
471 | 46 | %endif | ||
472 | 47 | |||
473 | 48 | %if authors_words_music: | ||
474 | 49 | <% | ||
475 | 50 | authors = ", ".join(authors_words_music) | ||
476 | 51 | %> | ||
477 | 52 | ${authors_words_music_label}: ${authors}<br/> | ||
478 | 53 | %endif | ||
479 | 54 | |||
480 | 55 | %if authors_words: | ||
481 | 56 | <% | ||
482 | 57 | authors = ", ".join(authors_words) | ||
483 | 58 | %> | ||
484 | 59 | ${authors_words_label}: ${authors}<br/> | ||
485 | 60 | %endif | ||
486 | 61 | |||
487 | 62 | %if authors_music: | ||
488 | 63 | <% | ||
489 | 64 | authors = ", ".join(authors_music) | ||
490 | 65 | %> | ||
491 | 66 | ${authors_music_label}: ${authors}<br/> | ||
492 | 67 | %endif | ||
493 | 68 | |||
494 | 69 | %if authors_translation: | ||
495 | 70 | <% | ||
496 | 71 | authors = ", ".join(authors_translation) | ||
497 | 72 | %> | ||
498 | 73 | ${authors_translation_label}: ${authors}<br/> | ||
499 | 74 | %endif | ||
500 | 75 | |||
501 | 76 | %if copyright: | ||
502 | 77 | © ${copyright}<br/> | ||
503 | 78 | %endif | ||
504 | 79 | |||
505 | 80 | %if songbook_entries: | ||
506 | 81 | <% | ||
507 | 82 | entries = ", ".join(songbook_entries) | ||
508 | 83 | %> | ||
509 | 84 | ${entries}<br/> | ||
510 | 85 | %endif | ||
511 | 86 | |||
512 | 87 | %if ccli_license: | ||
513 | 88 | ${ccli_license_label} ${ccli_license}<br/> | ||
514 | 89 | %endif | ||
515 | 90 | """ | ||
516 | 91 | } | ||
517 | 92 | |||
518 | 37 | 93 | ||
519 | 38 | class TestMediaItem(TestCase, TestMixin): | 94 | class TestMediaItem(TestCase, TestMixin): |
520 | 39 | """ | 95 | """ |
521 | @@ -61,6 +117,7 @@ | |||
522 | 61 | self.media_item.display_copyright_symbol = False | 117 | self.media_item.display_copyright_symbol = False |
523 | 62 | self.setup_application() | 118 | self.setup_application() |
524 | 63 | self.build_settings() | 119 | self.build_settings() |
525 | 120 | Settings().extend_default_settings(__default_settings__) | ||
526 | 64 | QtCore.QLocale.setDefault(QtCore.QLocale('en_GB')) | 121 | QtCore.QLocale.setDefault(QtCore.QLocale('en_GB')) |
527 | 65 | 122 | ||
528 | 66 | def tearDown(self): | 123 | def tearDown(self): |
529 | @@ -297,63 +354,45 @@ | |||
530 | 297 | """ | 354 | """ |
531 | 298 | Test build songs footer with basic song and one author | 355 | Test build songs footer with basic song and one author |
532 | 299 | """ | 356 | """ |
590 | 300 | # GIVEN: A Song and a Service Item, mocked settings: True for 'songs/display written by' | 357 | # GIVEN: A Song and a Service Item, mocked settings |
591 | 301 | # and False for 'core/ccli number' (ccli will cause traceback if true) | 358 | |
592 | 302 | 359 | mocked_settings = MagicMock() | |
593 | 303 | mocked_settings = MagicMock() | 360 | mocked_settings.value.side_effect = [False, "", "0"] |
594 | 304 | mocked_settings.value.side_effect = [True, False] | 361 | MockedSettings.return_value = mocked_settings |
595 | 305 | MockedSettings.return_value = mocked_settings | 362 | |
596 | 306 | 363 | with patch('mako.template.Template.render_unicode') as MockedRenderer: | |
597 | 307 | mock_song = MagicMock() | 364 | mock_song = MagicMock() |
598 | 308 | mock_song.title = 'My Song' | 365 | mock_song.title = 'My Song' |
599 | 309 | mock_song.authors_songs = [] | 366 | mock_song.alternate_title = '' |
600 | 310 | mock_author = MagicMock() | 367 | mock_song.ccli_number = '' |
601 | 311 | mock_author.display_name = 'my author' | 368 | mock_song.authors_songs = [] |
602 | 312 | mock_author_song = MagicMock() | 369 | mock_author = MagicMock() |
603 | 313 | mock_author_song.author = mock_author | 370 | mock_author.display_name = 'my author' |
604 | 314 | mock_song.authors_songs.append(mock_author_song) | 371 | mock_author_song = MagicMock() |
605 | 315 | mock_song.copyright = 'My copyright' | 372 | mock_author_song.author = mock_author |
606 | 316 | service_item = ServiceItem(None) | 373 | mock_song.authors_songs.append(mock_author_song) |
607 | 317 | 374 | mock_song.copyright = 'My copyright' | |
608 | 318 | # WHEN: I generate the Footer with default settings | 375 | mock_song.songbook_entries = [] |
609 | 319 | author_list = self.media_item.generate_footer(service_item, mock_song) | 376 | service_item = ServiceItem(None) |
610 | 320 | 377 | ||
611 | 321 | # THEN: I get the following Array returned | 378 | # WHEN: I generate the Footer with default settings |
612 | 322 | assert service_item.raw_footer == ['My Song', 'Written by: my author', 'My copyright'], \ | 379 | author_list = self.media_item.generate_footer(service_item, mock_song) |
613 | 323 | 'The array should be returned correctly with a song, one author and copyright' | 380 | |
614 | 324 | assert author_list == ['my author'], 'The author list should be returned correctly with one author' | 381 | # THEN: The mako function was called with the following arguments |
615 | 325 | 382 | args = {'authors_translation': [], 'authors_music_label': 'Music', | |
616 | 326 | @patch(u'openlp.plugins.songs.lib.mediaitem.Settings') | 383 | 'copyright': 'My copyright', 'songbook_entries': [], |
617 | 327 | def test_build_song_footer_one_author_hide_written_by(self, MockedSettings): | 384 | 'alternate_title': '', 'topics': [], 'authors_music_all': [], |
618 | 328 | """ | 385 | 'authors_words_label': 'Words', 'authors_music': [], |
619 | 329 | Test build songs footer with basic song and one author | 386 | 'authors_words_music': [], 'ccli_number': '', |
620 | 330 | """ | 387 | 'authors_none_label': 'Written by', 'title': 'My Song', |
621 | 331 | # GIVEN: A Song and a Service Item, mocked settings: False for 'songs/display written by' | 388 | 'authors_words_music_label': 'Words and Music', |
622 | 332 | # and False for 'core/ccli number' (ccli will cause traceback if true) | 389 | 'authors_none': ['my author'], |
623 | 333 | 390 | 'ccli_license_label': 'CCLI License', 'authors_words': [], | |
624 | 334 | mocked_settings = MagicMock() | 391 | 'ccli_license': '0', 'authors_translation_label': 'Translation', |
625 | 335 | mocked_settings.value.side_effect = [False, False] | 392 | 'authors_words_all': []} |
626 | 336 | MockedSettings.return_value = mocked_settings | 393 | MockedRenderer.assert_called_once_with(**args) |
627 | 337 | 394 | self.assertEqual(author_list, ['my author'], | |
628 | 338 | mock_song = MagicMock() | 395 | 'The author list should be returned correctly with one author') |
572 | 339 | mock_song.title = 'My Song' | ||
573 | 340 | mock_song.authors_songs = [] | ||
574 | 341 | mock_author = MagicMock() | ||
575 | 342 | mock_author.display_name = 'my author' | ||
576 | 343 | mock_author_song = MagicMock() | ||
577 | 344 | mock_author_song.author = mock_author | ||
578 | 345 | mock_song.authors_songs.append(mock_author_song) | ||
579 | 346 | mock_song.copyright = 'My copyright' | ||
580 | 347 | service_item = ServiceItem(None) | ||
581 | 348 | |||
582 | 349 | # WHEN: I generate the Footer with default settings | ||
583 | 350 | author_list = self.media_item.generate_footer(service_item, mock_song) | ||
584 | 351 | |||
585 | 352 | # THEN: I get the following Array returned | ||
586 | 353 | assert service_item.raw_footer == ['My Song', 'my author', 'My copyright'], \ | ||
587 | 354 | 'The array should be returned correctly with a song, one author and copyright, ' \ | ||
588 | 355 | 'text Written by should not be part of the text.' | ||
589 | 356 | assert author_list == ['my author'], 'The author list should be returned correctly with one author' | ||
629 | 357 | 396 | ||
630 | 358 | def test_build_song_footer_two_authors(self): | 397 | def test_build_song_footer_two_authors(self): |
631 | 359 | """ | 398 | """ |
632 | @@ -382,6 +421,7 @@ | |||
633 | 382 | mock_author_song.author_type = AuthorType.Translation | 421 | mock_author_song.author_type = AuthorType.Translation |
634 | 383 | mock_song.authors_songs.append(mock_author_song) | 422 | mock_song.authors_songs.append(mock_author_song) |
635 | 384 | mock_song.copyright = 'My copyright' | 423 | mock_song.copyright = 'My copyright' |
636 | 424 | mock_song.songbook_entries = [] | ||
637 | 385 | service_item = ServiceItem(None) | 425 | service_item = ServiceItem(None) |
638 | 386 | 426 | ||
639 | 387 | # WHEN: I generate the Footer with default settings | 427 | # WHEN: I generate the Footer with default settings |
640 | @@ -389,7 +429,7 @@ | |||
641 | 389 | 429 | ||
642 | 390 | # THEN: I get the following Array returned | 430 | # THEN: I get the following Array returned |
643 | 391 | assert service_item.raw_footer == ['My Song', 'Words: another author', 'Music: my author', | 431 | assert service_item.raw_footer == ['My Song', 'Words: another author', 'Music: my author', |
645 | 392 | 'Translation: translator', 'My copyright'], \ | 432 | 'Translation: translator', '© My copyright'], \ |
646 | 393 | 'The array should be returned correctly with a song, two authors and copyright' | 433 | 'The array should be returned correctly with a song, two authors and copyright' |
647 | 394 | assert author_list == ['another author', 'my author', 'translator'], \ | 434 | assert author_list == ['another author', 'my author', 'translator'], \ |
648 | 395 | 'The author list should be returned correctly with two authors' | 435 | 'The author list should be returned correctly with two authors' |
649 | @@ -402,6 +442,7 @@ | |||
650 | 402 | mock_song = MagicMock() | 442 | mock_song = MagicMock() |
651 | 403 | mock_song.title = 'My Song' | 443 | mock_song.title = 'My Song' |
652 | 404 | mock_song.copyright = 'My copyright' | 444 | mock_song.copyright = 'My copyright' |
653 | 445 | mock_song.songbook_entries = [] | ||
654 | 405 | service_item = ServiceItem(None) | 446 | service_item = ServiceItem(None) |
655 | 406 | Settings().setValue('core/ccli number', '1234') | 447 | Settings().setValue('core/ccli number', '1234') |
656 | 407 | 448 | ||
657 | @@ -409,7 +450,7 @@ | |||
658 | 409 | self.media_item.generate_footer(service_item, mock_song) | 450 | self.media_item.generate_footer(service_item, mock_song) |
659 | 410 | 451 | ||
660 | 411 | # THEN: I get the following Array returned | 452 | # THEN: I get the following Array returned |
662 | 412 | assert service_item.raw_footer == ['My Song', 'My copyright', 'CCLI License: 1234'], \ | 453 | assert service_item.raw_footer == ['My Song', '© My copyright', 'CCLI License: 1234'], \ |
663 | 413 | 'The array should be returned correctly with a song, an author, copyright and ccli' | 454 | 'The array should be returned correctly with a song, an author, copyright and ccli' |
664 | 414 | 455 | ||
665 | 415 | # WHEN: I amend the CCLI value | 456 | # WHEN: I amend the CCLI value |
666 | @@ -417,7 +458,7 @@ | |||
667 | 417 | self.media_item.generate_footer(service_item, mock_song) | 458 | self.media_item.generate_footer(service_item, mock_song) |
668 | 418 | 459 | ||
669 | 419 | # THEN: I would get an amended footer string | 460 | # THEN: I would get an amended footer string |
671 | 420 | assert service_item.raw_footer == ['My Song', 'My copyright', 'CCLI License: 4321'], \ | 461 | assert service_item.raw_footer == ['My Song', '© My copyright', 'CCLI License: 4321'], \ |
672 | 421 | 'The array should be returned correctly with a song, an author, copyright and amended ccli' | 462 | 'The array should be returned correctly with a song, an author, copyright and amended ccli' |
673 | 422 | 463 | ||
674 | 423 | def test_build_song_footer_base_songbook(self): | 464 | def test_build_song_footer_base_songbook(self): |
675 | @@ -431,6 +472,8 @@ | |||
676 | 431 | song.copyright = 'My copyright' | 472 | song.copyright = 'My copyright' |
677 | 432 | song.authors_songs = [] | 473 | song.authors_songs = [] |
678 | 433 | song.songbook_entries = [] | 474 | song.songbook_entries = [] |
679 | 475 | song.alternate_title = '' | ||
680 | 476 | song.topics = [] | ||
681 | 434 | song.ccli_number = '' | 477 | song.ccli_number = '' |
682 | 435 | book1 = MagicMock() | 478 | book1 = MagicMock() |
683 | 436 | book1.name = 'My songbook' | 479 | book1.name = 'My songbook' |
684 | @@ -444,15 +487,8 @@ | |||
685 | 444 | # WHEN: I generate the Footer with default settings | 487 | # WHEN: I generate the Footer with default settings |
686 | 445 | self.media_item.generate_footer(service_item, song) | 488 | self.media_item.generate_footer(service_item, song) |
687 | 446 | 489 | ||
688 | 447 | # THEN: The songbook should not be in the footer | ||
689 | 448 | assert service_item.raw_footer == ['My Song', 'My copyright'] | ||
690 | 449 | |||
691 | 450 | # WHEN: I activate the "display songbook" option | ||
692 | 451 | self.media_item.display_songbook = True | ||
693 | 452 | self.media_item.generate_footer(service_item, song) | ||
694 | 453 | |||
695 | 454 | # THEN: The songbook should be in the footer | 490 | # THEN: The songbook should be in the footer |
697 | 455 | assert service_item.raw_footer == ['My Song', 'My copyright', 'My songbook #12, Thy songbook #502A'] | 491 | assert service_item.raw_footer == ['My Song', '© My copyright', 'My songbook #12, Thy songbook #502A'] |
698 | 456 | 492 | ||
699 | 457 | def test_build_song_footer_copyright_enabled(self): | 493 | def test_build_song_footer_copyright_enabled(self): |
700 | 458 | """ | 494 | """ |
701 | @@ -463,6 +499,7 @@ | |||
702 | 463 | mock_song = MagicMock() | 499 | mock_song = MagicMock() |
703 | 464 | mock_song.title = 'My Song' | 500 | mock_song.title = 'My Song' |
704 | 465 | mock_song.copyright = 'My copyright' | 501 | mock_song.copyright = 'My copyright' |
705 | 502 | mock_song.songbook_entries = [] | ||
706 | 466 | service_item = ServiceItem(None) | 503 | service_item = ServiceItem(None) |
707 | 467 | 504 | ||
708 | 468 | # WHEN: I generate the Footer with default settings | 505 | # WHEN: I generate the Footer with default settings |
709 | @@ -479,13 +516,14 @@ | |||
710 | 479 | mock_song = MagicMock() | 516 | mock_song = MagicMock() |
711 | 480 | mock_song.title = 'My Song' | 517 | mock_song.title = 'My Song' |
712 | 481 | mock_song.copyright = 'My copyright' | 518 | mock_song.copyright = 'My copyright' |
713 | 519 | mock_song.songbook_entries = [] | ||
714 | 482 | service_item = ServiceItem(None) | 520 | service_item = ServiceItem(None) |
715 | 483 | 521 | ||
716 | 484 | # WHEN: I generate the Footer with default settings | 522 | # WHEN: I generate the Footer with default settings |
717 | 485 | self.media_item.generate_footer(service_item, mock_song) | 523 | self.media_item.generate_footer(service_item, mock_song) |
718 | 486 | 524 | ||
719 | 487 | # THEN: The copyright symbol should not be in the footer | 525 | # THEN: The copyright symbol should not be in the footer |
721 | 488 | assert service_item.raw_footer == ['My Song', 'My copyright'] | 526 | assert service_item.raw_footer == ['My Song', '© My copyright'] |
722 | 489 | 527 | ||
723 | 490 | def test_authors_match(self): | 528 | def test_authors_match(self): |
724 | 491 | """ | 529 | """ |
You need to run CI on this to show the success of tests.
Not sure why but this request did a full download when I did a check out. Also cannot run due to chord issues.
Cannot run this but there seem to be lots of line breaks in the footer display. As the footer area is small this will not be displayed and lead to forum comments.
You need some validation do defend against too long footers!