forked from openlp/openlp
Add option to add a split to a song
This will split the verse when added to the service but keep the verse together for editing. Useful for the 10 line hymn verses which need 2 slides to display. Fix some iffy spelling lp:~trb143/openlp/splitter (revision 2738) [SUCCESS] https://ci.openlp.io/job/Branch-01-Pull/2182/ [SUCCESS] https://ci.openlp.io/job/Branch-02-Functional-Tests/2085/ [SUCCESS] https://ci.openlp.io/job/Branch-03-Interface-Tests/1972/ [SUCCESS] https://ci.openlp.io/job/Branch... bzr-revno: 2777
This commit is contained in:
commit
df39497cc1
@ -147,7 +147,7 @@ class UiStrings(object):
|
||||
self.SaveService = translate('OpenLP.Ui', 'Save Service')
|
||||
self.Service = translate('OpenLP.Ui', 'Service')
|
||||
self.ShortResults = translate('OpenLP.Ui', 'Please type more text to use \'Search As You Type\'')
|
||||
self.Split = translate('OpenLP.Ui', 'Optional &Split')
|
||||
self.Split = translate('OpenLP.Ui', 'Overflow &Split')
|
||||
self.SplitToolTip = translate('OpenLP.Ui',
|
||||
'Split a slide into two only if it does not fit on the screen as one slide.')
|
||||
self.StartingImport = translate('OpenLP.Ui', 'Starting import...')
|
||||
|
@ -243,6 +243,9 @@ class Renderer(OpenLPMixin, RegistryMixin, RegistryProperties):
|
||||
elif item.is_capable(ItemCapabilities.CanSoftBreak):
|
||||
pages = []
|
||||
if '[---]' in text:
|
||||
# Remove Overflow split if at start of the text
|
||||
if text.startswith('[---]'):
|
||||
text = text[5:]
|
||||
# Remove two or more option slide breaks next to each other (causing infinite loop).
|
||||
while '\n[---]\n[---]\n' in text:
|
||||
text = text.replace('\n[---]\n[---]\n', '\n[---]\n')
|
||||
|
@ -209,21 +209,21 @@ class ListPreviewWidget(QtWidgets.QTableWidget, RegistryProperties):
|
||||
Switches to the given row.
|
||||
"""
|
||||
# Retrieve setting
|
||||
autoscrolling = Settings().value('advanced/autoscrolling')
|
||||
auto_scrolling = Settings().value('advanced/autoscrolling')
|
||||
# Check if auto-scroll disabled (None) and validate value as dict containing 'dist' and 'pos'
|
||||
# 'dist' represents the slide to scroll to relative to the new slide (-1 = previous, 0 = current, 1 = next)
|
||||
# 'pos' represents the vert position of of the slide (0 = in view, 1 = top, 2 = middle, 3 = bottom)
|
||||
if not (isinstance(autoscrolling, dict) and 'dist' in autoscrolling and 'pos' in autoscrolling and
|
||||
isinstance(autoscrolling['dist'], int) and isinstance(autoscrolling['pos'], int)):
|
||||
if not (isinstance(auto_scrolling, dict) and 'dist' in auto_scrolling and 'pos' in auto_scrolling and
|
||||
isinstance(auto_scrolling['dist'], int) and isinstance(auto_scrolling['pos'], int)):
|
||||
return
|
||||
# prevent scrolling past list bounds
|
||||
scroll_to_slide = slide + autoscrolling['dist']
|
||||
scroll_to_slide = slide + auto_scrolling['dist']
|
||||
if scroll_to_slide < 0:
|
||||
scroll_to_slide = 0
|
||||
if scroll_to_slide >= self.slide_count():
|
||||
scroll_to_slide = self.slide_count() - 1
|
||||
# Scroll to item if possible.
|
||||
self.scrollToItem(self.item(scroll_to_slide, 0), autoscrolling['pos'])
|
||||
self.scrollToItem(self.item(scroll_to_slide, 0), auto_scrolling['pos'])
|
||||
self.selectRow(slide)
|
||||
|
||||
def current_slide_number(self):
|
||||
|
@ -25,14 +25,13 @@ The duplicate song removal logic for OpenLP.
|
||||
|
||||
import logging
|
||||
import multiprocessing
|
||||
import os
|
||||
|
||||
from PyQt5 import QtCore, QtWidgets
|
||||
|
||||
from openlp.core.common import Registry, RegistryProperties, translate
|
||||
from openlp.core.ui.lib.wizard import OpenLPWizard, WizardStrings
|
||||
from openlp.plugins.songs.lib import delete_song
|
||||
from openlp.plugins.songs.lib.db import Song, MediaFile
|
||||
from openlp.plugins.songs.lib.db import Song
|
||||
from openlp.plugins.songs.forms.songreviewwidget import SongReviewWidget
|
||||
from openlp.plugins.songs.lib.songcompare import songs_probably_equal
|
||||
|
||||
|
@ -42,10 +42,14 @@ class Ui_EditVerseDialog(object):
|
||||
self.dialog_layout.addWidget(self.verse_text_edit)
|
||||
self.verse_type_layout = QtWidgets.QHBoxLayout()
|
||||
self.verse_type_layout.setObjectName('verse_type_layout')
|
||||
self.split_button = QtWidgets.QPushButton(edit_verse_dialog)
|
||||
self.split_button.setIcon(build_icon(':/general/general_add.png'))
|
||||
self.split_button.setObjectName('split_button')
|
||||
self.verse_type_layout.addWidget(self.split_button)
|
||||
self.forced_split_button = QtWidgets.QPushButton(edit_verse_dialog)
|
||||
self.forced_split_button.setIcon(build_icon(':/general/general_add.png'))
|
||||
self.forced_split_button.setObjectName('forced_split_button')
|
||||
self.verse_type_layout.addWidget(self.forced_split_button)
|
||||
self.overflow_split_button = QtWidgets.QPushButton(edit_verse_dialog)
|
||||
self.overflow_split_button.setIcon(build_icon(':/general/general_add.png'))
|
||||
self.overflow_split_button.setObjectName('overflow_split_button')
|
||||
self.verse_type_layout.addWidget(self.overflow_split_button)
|
||||
self.verse_type_label = QtWidgets.QLabel(edit_verse_dialog)
|
||||
self.verse_type_label.setObjectName('verse_type_label')
|
||||
self.verse_type_layout.addWidget(self.verse_type_label)
|
||||
@ -93,8 +97,11 @@ class Ui_EditVerseDialog(object):
|
||||
self.verse_type_combo_box.setItemText(VerseType.Intro, VerseType.translated_names[VerseType.Intro])
|
||||
self.verse_type_combo_box.setItemText(VerseType.Ending, VerseType.translated_names[VerseType.Ending])
|
||||
self.verse_type_combo_box.setItemText(VerseType.Other, VerseType.translated_names[VerseType.Other])
|
||||
self.split_button.setText(UiStrings().Split)
|
||||
self.split_button.setToolTip(UiStrings().SplitToolTip)
|
||||
self.overflow_split_button.setText(UiStrings().Split)
|
||||
self.overflow_split_button.setToolTip(UiStrings().SplitToolTip)
|
||||
self.forced_split_button.setText(translate('SongsPlugin.EditVerseForm', '&Forced Split'))
|
||||
self.forced_split_button.setToolTip(translate('SongsPlugin.EditVerseForm', 'Split the verse when displayed '
|
||||
'regardless of the screen size.'))
|
||||
self.insert_button.setText(translate('SongsPlugin.EditVerseForm', '&Insert'))
|
||||
self.insert_button.setToolTip(translate('SongsPlugin.EditVerseForm',
|
||||
'Split a slide into two by inserting a verse splitter.'))
|
||||
|
@ -48,12 +48,13 @@ class EditVerseForm(QtWidgets.QDialog, Ui_EditVerseDialog):
|
||||
self.setupUi(self)
|
||||
self.has_single_verse = False
|
||||
self.insert_button.clicked.connect(self.on_insert_button_clicked)
|
||||
self.split_button.clicked.connect(self.on_split_button_clicked)
|
||||
self.overflow_split_button.clicked.connect(self.on_overflow_split_button_clicked)
|
||||
self.verse_text_edit.cursorPositionChanged.connect(self.on_cursor_position_changed)
|
||||
self.verse_type_combo_box.currentIndexChanged.connect(self.on_verse_type_combo_box_changed)
|
||||
self.forced_split_button.clicked.connect(self.on_forced_split_button_clicked)
|
||||
if Settings().value('songs/enable chords'):
|
||||
self.transpose_down_button.clicked.connect(self.on_transepose_down_button_clicked)
|
||||
self.transpose_up_button.clicked.connect(self.on_transepose_up_button_clicked)
|
||||
self.transpose_down_button.clicked.connect(self.on_transpose_down_button_clicked)
|
||||
self.transpose_up_button.clicked.connect(self.on_transpose_up_button_clicked)
|
||||
|
||||
def insert_verse(self, verse_tag, verse_num=1):
|
||||
"""
|
||||
@ -68,13 +69,27 @@ class EditVerseForm(QtWidgets.QDialog, Ui_EditVerseDialog):
|
||||
self.verse_text_edit.insertPlainText('---[{tag}:{number}]---\n'.format(tag=verse_tag, number=verse_num))
|
||||
self.verse_text_edit.setFocus()
|
||||
|
||||
def on_split_button_clicked(self):
|
||||
def on_overflow_split_button_clicked(self):
|
||||
"""
|
||||
The split button has been pressed
|
||||
The optional split button has been pressed so we need add the split
|
||||
"""
|
||||
self._add_splitter_to_text('[---]')
|
||||
|
||||
def on_forced_split_button_clicked(self):
|
||||
"""
|
||||
The force split button has been pressed so we need add the split
|
||||
"""
|
||||
self._add_splitter_to_text('[--}{--]')
|
||||
|
||||
def _add_splitter_to_text(self, insert_string):
|
||||
"""
|
||||
Add a custom splitter to the song text
|
||||
|
||||
:param insert_string: The string to insert
|
||||
:return:
|
||||
"""
|
||||
text = self.verse_text_edit.toPlainText()
|
||||
position = self.verse_text_edit.textCursor().position()
|
||||
insert_string = '[---]'
|
||||
if position and text[position - 1] != '\n':
|
||||
insert_string = '\n' + insert_string
|
||||
if position == len(text) or text[position] != '\n':
|
||||
@ -101,7 +116,7 @@ class EditVerseForm(QtWidgets.QDialog, Ui_EditVerseDialog):
|
||||
"""
|
||||
self.update_suggested_verse_number()
|
||||
|
||||
def on_transepose_up_button_clicked(self):
|
||||
def on_transpose_up_button_clicked(self):
|
||||
"""
|
||||
The transpose up button clicked
|
||||
"""
|
||||
@ -118,7 +133,7 @@ class EditVerseForm(QtWidgets.QDialog, Ui_EditVerseDialog):
|
||||
self.verse_text_edit.setFocus()
|
||||
self.verse_text_edit.moveCursor(QtGui.QTextCursor.End)
|
||||
|
||||
def on_transepose_down_button_clicked(self):
|
||||
def on_transpose_down_button_clicked(self):
|
||||
"""
|
||||
The transpose down button clicked
|
||||
"""
|
||||
|
@ -546,12 +546,12 @@ def delete_song(song_id, song_plugin):
|
||||
song_plugin.manager.delete_object(Song, song_id)
|
||||
|
||||
|
||||
def transpose_lyrics(lyrics, transepose_value):
|
||||
def transpose_lyrics(lyrics, transpose_value):
|
||||
"""
|
||||
Transepose lyrics
|
||||
Transpose lyrics
|
||||
|
||||
:param lyrcs: The lyrics to be transposed
|
||||
:param transepose_value: The value to transpose the lyrics with
|
||||
:param lyrics: The lyrics to be transposed
|
||||
:param transpose_value: The value to transpose the lyrics with
|
||||
:return: The transposed lyrics
|
||||
"""
|
||||
# Split text by verse delimiter - both normal and optional
|
||||
@ -562,16 +562,17 @@ def transpose_lyrics(lyrics, transepose_value):
|
||||
if verse.startswith('---[') or verse == '[---]':
|
||||
transposed_lyrics += verse
|
||||
else:
|
||||
transposed_lyrics += transpose_verse(verse, transepose_value, notation)
|
||||
transposed_lyrics += transpose_verse(verse, transpose_value, notation)
|
||||
return transposed_lyrics
|
||||
|
||||
|
||||
def transpose_verse(verse_text, transepose_value, notation):
|
||||
def transpose_verse(verse_text, transpose_value, notation):
|
||||
"""
|
||||
Transepose lyrics
|
||||
Transpose Verse
|
||||
|
||||
:param lyrcs: The lyrics to be transposed
|
||||
:param transepose_value: The value to transpose the lyrics with
|
||||
:param verse_text: The lyrics to be transposed
|
||||
:param transpose_value: The value to transpose the lyrics with
|
||||
:param notation: which notation to use
|
||||
:return: The transposed lyrics
|
||||
"""
|
||||
if '[' not in verse_text:
|
||||
@ -589,11 +590,11 @@ def transpose_verse(verse_text, transepose_value, notation):
|
||||
if word == ']':
|
||||
in_tag = False
|
||||
transposed_lyrics += word
|
||||
elif word == '/':
|
||||
elif word == '/' or word == '--}{--':
|
||||
transposed_lyrics += word
|
||||
else:
|
||||
# This MUST be a chord
|
||||
transposed_lyrics += transpose_chord(word, transepose_value, notation)
|
||||
transposed_lyrics += transpose_chord(word, transpose_value, notation)
|
||||
# If still inside a chord tag something is wrong!
|
||||
if in_tag:
|
||||
return verse_text
|
||||
@ -629,36 +630,36 @@ def transpose_chord(chord, transpose_value, notation):
|
||||
for i in range(0, len(chord_split)):
|
||||
if i > 0:
|
||||
transposed_chord += '/'
|
||||
currentchord = chord_split[i]
|
||||
if currentchord and currentchord[0] == '(':
|
||||
current_chord = chord_split[i]
|
||||
if current_chord and current_chord[0] == '(':
|
||||
transposed_chord += '('
|
||||
if len(currentchord) > 1:
|
||||
currentchord = currentchord[1:]
|
||||
if len(current_chord) > 1:
|
||||
current_chord = current_chord[1:]
|
||||
else:
|
||||
currentchord = ''
|
||||
if len(currentchord) > 0:
|
||||
if len(currentchord) > 1:
|
||||
if '#b'.find(currentchord[1]) == -1:
|
||||
note = currentchord[0:1]
|
||||
rest = currentchord[1:]
|
||||
current_chord = ''
|
||||
if len(current_chord) > 0:
|
||||
if len(current_chord) > 1:
|
||||
if '#b'.find(current_chord[1]) == -1:
|
||||
note = current_chord[0:1]
|
||||
rest = current_chord[1:]
|
||||
else:
|
||||
note = currentchord[0:2]
|
||||
rest = currentchord[2:]
|
||||
note = current_chord[0:2]
|
||||
rest = current_chord[2:]
|
||||
else:
|
||||
note = currentchord
|
||||
note = current_chord
|
||||
rest = ''
|
||||
notenumber = notes_flat.index(note) if note not in notes_sharp else notes_sharp.index(note)
|
||||
notenumber += transpose_value
|
||||
while notenumber > 11:
|
||||
notenumber -= 12
|
||||
while notenumber < 0:
|
||||
notenumber += 12
|
||||
note_number = notes_flat.index(note) if note not in notes_sharp else notes_sharp.index(note)
|
||||
note_number += transpose_value
|
||||
while note_number > 11:
|
||||
note_number -= 12
|
||||
while note_number < 0:
|
||||
note_number += 12
|
||||
if i == 0:
|
||||
current_chord = notes_sharp[notenumber] if notes_preferred[notenumber] == '#' else notes_flat[
|
||||
notenumber]
|
||||
current_chord = notes_sharp[note_number] if notes_preferred[note_number] == '#' else notes_flat[
|
||||
note_number]
|
||||
last_chord = current_chord
|
||||
else:
|
||||
current_chord = notes_flat[notenumber] if last_chord not in notes_sharp else notes_sharp[notenumber]
|
||||
current_chord = notes_flat[note_number] if last_chord not in notes_sharp else notes_sharp[note_number]
|
||||
if not (note not in notes_flat and note not in notes_sharp):
|
||||
transposed_chord += current_chord + rest
|
||||
else:
|
||||
|
@ -588,7 +588,9 @@ class SongMediaItem(MediaManagerItem):
|
||||
verse_index = VerseType.from_tag(verse_tag)
|
||||
verse_tag = VerseType.translated_tags[verse_index].upper()
|
||||
verse_def = '{tag}{label}'.format(tag=verse_tag, label=verse[0]['label'])
|
||||
service_item.add_from_text(str(verse[1]), verse_def)
|
||||
force_verse = verse[1].split('[--}{--]\n')
|
||||
for split_verse in force_verse:
|
||||
service_item.add_from_text(split_verse, verse_def)
|
||||
else:
|
||||
# Loop through the verse list and expand the song accordingly.
|
||||
for order in song.verse_order.lower().split():
|
||||
@ -603,7 +605,9 @@ class SongMediaItem(MediaManagerItem):
|
||||
verse_index = VerseType.from_tag(verse[0]['type'])
|
||||
verse_tag = VerseType.translated_tags[verse_index]
|
||||
verse_def = '{tag}{label}'.format(tag=verse_tag, label=verse[0]['label'])
|
||||
service_item.add_from_text(verse[1], verse_def)
|
||||
force_verse = verse[1].split('[--}{--]\n')
|
||||
for split_verse in force_verse:
|
||||
service_item.add_from_text(split_verse, verse_def)
|
||||
service_item.title = song.title
|
||||
author_list = self.generate_footer(service_item, song)
|
||||
service_item.data_string = {'title': song.search_title, 'authors': ', '.join(author_list)}
|
||||
|
@ -71,6 +71,7 @@ log = logging.getLogger(__name__)
|
||||
|
||||
NAMESPACE = 'http://openlyrics.info/namespace/2009/song'
|
||||
NSMAP = '{{' + NAMESPACE + '}}{tag}'
|
||||
NEWPAGETAG = '<p style="page-break-after: always;"/>'
|
||||
|
||||
|
||||
class SongXML(object):
|
||||
@ -281,7 +282,7 @@ class OpenLyrics(object):
|
||||
tags_element = None
|
||||
match = re.search('\{/?\w+\}', song.lyrics, re.UNICODE)
|
||||
if match:
|
||||
# Named 'format_' - 'format' is built-in fuction in Python.
|
||||
# Named 'format_' - 'format' is built-in function in Python.
|
||||
format_ = etree.SubElement(song_xml, 'format')
|
||||
tags_element = etree.SubElement(format_, 'tags')
|
||||
tags_element.set('application', 'OpenLP')
|
||||
@ -472,6 +473,7 @@ class OpenLyrics(object):
|
||||
text = text.replace('{{/{tag}}}'.format(tag=tag), '</tag>')
|
||||
# Replace \n with <br/>.
|
||||
text = text.replace('\n', '<br/>')
|
||||
text = text.replace('[--}{--]', NEWPAGETAG)
|
||||
element = etree.XML('<lines>{text}</lines>'.format(text=text))
|
||||
verse_element.append(element)
|
||||
return element
|
||||
@ -634,6 +636,9 @@ class OpenLyrics(object):
|
||||
if element.tail:
|
||||
text += element.tail
|
||||
return text
|
||||
elif newlines and element.tag == NSMAP.format(tag='p') and 'page-break-after' in str(element.attrib):
|
||||
text += '[--}{--]'
|
||||
return text
|
||||
# Start formatting tag.
|
||||
if element.tag == NSMAP.format(tag='tag'):
|
||||
text += '{{{name}}}'.format(name=element.get('name'))
|
||||
|
@ -72,3 +72,31 @@ class TestEditVerseForm(TestCase, TestMixin):
|
||||
|
||||
# THEN the verse number must not be changed
|
||||
self.assertEqual(3, self.edit_verse_form.verse_number_box.value(), 'The verse number should be 3')
|
||||
|
||||
def test_on_divide_split_button_clicked(self):
|
||||
"""
|
||||
Test that divide adds text at the correct position
|
||||
"""
|
||||
# GIVEN some input values
|
||||
self.edit_verse_form.verse_type_combo_box.currentIndex = MagicMock(return_value=4)
|
||||
self.edit_verse_form.verse_text_edit.setPlainText('Text\n')
|
||||
|
||||
# WHEN the method is called
|
||||
self.edit_verse_form.on_forced_split_button_clicked()
|
||||
# THEN the verse number must not be changed
|
||||
self.assertEqual('[--}{--]\nText\n', self.edit_verse_form.verse_text_edit.toPlainText(),
|
||||
'The verse number should be [--}{--]\nText\n')
|
||||
|
||||
def test_on_split_button_clicked(self):
|
||||
"""
|
||||
Test that divide adds text at the correct position
|
||||
"""
|
||||
# GIVEN some input values
|
||||
self.edit_verse_form.verse_type_combo_box.currentIndex = MagicMock(return_value=4)
|
||||
self.edit_verse_form.verse_text_edit.setPlainText('Text\n')
|
||||
|
||||
# WHEN the method is called
|
||||
self.edit_verse_form.on_overflow_split_button_clicked()
|
||||
# THEN the verse number must not be changed
|
||||
self.assertEqual('[---]\nText\n', self.edit_verse_form.verse_text_edit.toPlainText(),
|
||||
'The verse number should be [---]\nText\n')
|
||||
|
Loading…
Reference in New Issue
Block a user