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:
Tim Bentley 2017-10-03 17:19:06 +01:00
commit df39497cc1
10 changed files with 121 additions and 59 deletions

View File

@ -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...')

View File

@ -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')

View File

@ -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):

View File

@ -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

View File

@ -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.'))

View File

@ -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
"""

View File

@ -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:

View File

@ -577,7 +577,7 @@ class SongMediaItem(MediaManagerItem):
if not song.verse_order.strip():
for verse in verse_list:
# We cannot use from_loose_input() here, because database is supposed to contain English lowercase
# singlechar tags.
# single char tags.
verse_tag = verse[0]['type']
verse_index = None
if len(verse_tag) > 1:
@ -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)}

View File

@ -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'))

View File

@ -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')