This commit is contained in:
Phill Ridout 2014-11-30 07:44:57 +00:00
commit 563347c8b8
19 changed files with 286 additions and 31 deletions

View File

@ -250,7 +250,13 @@ class Renderer(OpenLPMixin, RegistryMixin, RegistryProperties):
# 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')
while True:
while ' [---]' in text:
text = text.replace(' [---]', '[---]')
while '[---] ' in text:
text = text.replace('[---] ', '[---]')
count = 0
# only loop 5 times as there will never be more than 5 incorrect logical splits on a single slide.
while True and count < 5:
slides = text.split('\n[---]\n', 2)
# If there are (at least) two occurrences of [---] we use the first two slides (and neglect the last
# for now).
@ -296,6 +302,7 @@ class Renderer(OpenLPMixin, RegistryMixin, RegistryProperties):
lines = text.strip('\n').split('\n')
pages.extend(self._paginate_slide(lines, line_end))
break
count =+ 1
else:
# Clean up line endings.
pages = self._paginate_slide(text.split('\n'), line_end)

View File

@ -95,6 +95,17 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties):
"""
self.application.process_events()
if self.currentId() == FirstTimePage.Plugins:
if self.has_run_wizard:
self.songs_check_box.setChecked(self.plugin_manager.get_plugin_by_name('songs').is_active())
self.bible_check_box.setChecked(self.plugin_manager.get_plugin_by_name('bibles').is_active())
self.presentation_check_box.setChecked(self.plugin_manager.get_plugin_by_name(
'presentations').is_active())
self.image_check_box.setChecked(self.plugin_manager.get_plugin_by_name('images').is_active())
self.media_check_box.setChecked(self.plugin_manager.get_plugin_by_name('media').is_active())
self.remote_check_box.setChecked(self.plugin_manager.get_plugin_by_name('remotes').is_active())
self.custom_check_box.setChecked(self.plugin_manager.get_plugin_by_name('custom').is_active())
self.song_usage_check_box.setChecked(self.plugin_manager.get_plugin_by_name('songusage').is_active())
self.alert_check_box.setChecked(self.plugin_manager.get_plugin_by_name('alerts').is_active())
if not self.web_access:
return FirstTimePage.NoInternet
else:

View File

@ -978,7 +978,14 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow, RegistryProperties):
# FIXME: We are conflicting with the standard "General" section.
if 'eneral' in section_key:
section_key = section_key.lower()
key_value = settings.value(section_key)
try:
key_value = settings.value(section_key)
except KeyError:
QtGui.QMessageBox.critical(self, translate('OpenLP.MainWindow', 'Export setting error'),
translate('OpenLP.MainWindow', 'The key "%s" does not have a default value '
'so it will be skipped in this export.') % section_key,
QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Ok))
key_value = None
if key_value is not None:
export_settings.setValue(section_key, key_value)
export_settings.sync()

View File

@ -494,7 +494,7 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtGui.QWidget, Ui_ServiceManage
service.append({'openlp_core': core})
return service
def save_file(self):
def save_file(self, field=None):
"""
Save the current service file.
@ -1427,9 +1427,10 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtGui.QWidget, Ui_ServiceManage
self.drop_position = -1
self.set_modified()
def make_preview(self):
def make_preview(self, field=None):
"""
Send the current item to the Preview slide controller
:param field:
"""
self.application.set_busy_cursor()
item, child = self.find_service_item()

View File

@ -46,7 +46,7 @@ class Ui_SettingsDialog(object):
"""
settings_dialog.setObjectName('settings_dialog')
settings_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg'))
settings_dialog.resize(800, 500)
settings_dialog.resize(800, 700)
self.dialog_layout = QtGui.QGridLayout(settings_dialog)
self.dialog_layout.setObjectName('dialog_layout')
self.dialog_layout.setMargin(8)

View File

@ -683,7 +683,8 @@ class SlideController(DisplayController, RegistryProperties):
self.play_slides_loop.setChecked(False)
self.play_slides_loop.setIcon(build_icon(':/media/media_time.png'))
if item.is_text():
if Settings().value(self.main_window.songs_settings_section + '/display songbar') and self.slide_list:
if (Settings().value(self.main_window.songs_settings_section + '/display songbar')
and not self.song_menu.menu().isEmpty()):
self.toolbar.set_widget_visible(['song_menu'], True)
if item.is_capable(ItemCapabilities.CanLoop) and len(item.get_frames()) > 1:
self.toolbar.set_widget_visible(LOOP_LIST)
@ -691,7 +692,8 @@ class SlideController(DisplayController, RegistryProperties):
self.mediabar.show()
self.previous_item.setVisible(not item.is_media())
self.next_item.setVisible(not item.is_media())
self.set_blank_menu()
# The layout of the toolbar is size dependent, so make sure it fits
self.on_controller_size_changed(self.controller.width())
# Work-around for OS X, hide and then show the toolbar
# See bug #791050
self.toolbar.show()

View File

@ -37,7 +37,7 @@ from xml.etree.ElementTree import ElementTree, XML
from PyQt4 import QtCore, QtGui
from openlp.core.common import Registry, RegistryProperties, AppLocation, Settings, OpenLPMixin, RegistryMixin, \
check_directory_exists, UiStrings, translate
check_directory_exists, UiStrings, translate, is_win
from openlp.core.lib import FileDialog, ImageSource, OpenLPToolbar, get_text_file_string, build_icon, \
check_item_selected, create_thumb, validate_thumb
from openlp.core.lib.theme import ThemeXML, BackgroundType
@ -662,8 +662,12 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtGui.QWidget, Ui_ThemeManager, R
out_file.close()
if image_from and os.path.abspath(image_from) != os.path.abspath(image_to):
try:
encoding = get_filesystem_encoding()
shutil.copyfile(str(image_from).encode(encoding), str(image_to).encode(encoding))
# Windows is always unicode, so no need to encode filenames
if is_win():
shutil.copyfile(image_from, image_to)
else:
encoding = get_filesystem_encoding()
shutil.copyfile(image_from.encode(encoding), image_to.encode(encoding))
except IOError as xxx_todo_changeme:
shutil.Error = xxx_todo_changeme
self.log_exception('Failed to save theme image')

View File

@ -330,7 +330,10 @@ def parse_reference(reference, bible, language_selection, book_ref_id=False):
if not book_ref_id:
book_ref_id = bible.get_book_ref_id_by_localised_name(book, language_selection)
elif not bible.get_book_by_book_ref_id(book_ref_id):
book_ref_id = False
return False
# We have not found the book so do not continue
if not book_ref_id:
return False
ranges = match.group('ranges')
range_list = get_reference_match('range_separator').split(ranges)
ref_list = []

View File

@ -93,7 +93,7 @@ class WorshipAssistantImport(SongImport):
details = chardet.detect(detect_content)
detect_file.close()
songs_file = open(self.import_source, 'r', encoding=details['encoding'])
songs_reader = csv.DictReader(songs_file)
songs_reader = csv.DictReader(songs_file, escapechar='\\')
try:
records = list(songs_reader)
except csv.Error as e:
@ -104,6 +104,8 @@ class WorshipAssistantImport(SongImport):
num_records = len(records)
log.info('%s records found in CSV file' % num_records)
self.import_wizard.progress_bar.setMaximum(num_records)
# Create regex to strip html tags
re_html_strip = re.compile(r'<[^>]+>')
for index, record in enumerate(records, 1):
if self.stop_import_flag:
return
@ -119,13 +121,12 @@ class WorshipAssistantImport(SongImport):
self.title = record['TITLE']
if record['AUTHOR'] != EMPTY_STR:
self.parse_author(record['AUTHOR'])
print(record['AUTHOR'])
if record['COPYRIGHT'] != EMPTY_STR:
self.add_copyright(record['COPYRIGHT'])
if record['CCLINR'] != EMPTY_STR:
self.ccli_number = record['CCLINR']
if record['ROADMAP'] != EMPTY_STR:
verse_order_list = record['ROADMAP'].split(',')
verse_order_list = [x.strip() for x in record['ROADMAP'].split(',')]
lyrics = record['LYRICS2']
except UnicodeDecodeError as e:
self.log_error(translate('SongsPlugin.WorshipAssistantImport', 'Record %d' % index),
@ -136,8 +137,15 @@ class WorshipAssistantImport(SongImport):
'File not valid WorshipAssistant CSV format.'), 'TypeError: %s' % e)
return
verse = ''
used_verses = []
for line in lyrics.splitlines():
if line.startswith('['): # verse marker
# Add previous verse
if verse:
# remove trailing linebreak, part of the WA syntax
self.add_verse(verse[:-1], verse_id)
used_verses.append(verse_id)
verse = ''
# drop the square brackets
right_bracket = line.find(']')
content = line[1:right_bracket].lower()
@ -152,19 +160,31 @@ class WorshipAssistantImport(SongImport):
verse_index = VerseType.from_loose_input(verse_tag) if verse_tag else 0
verse_tag = VerseType.tags[verse_index]
# Update verse order when the verse name has changed
if content != verse_tag + verse_num:
verse_id = verse_tag + verse_num
# Make sure we've not choosen an id already used
while verse_id in verse_order_list and content in verse_order_list:
verse_num = str(int(verse_num) + 1)
verse_id = verse_tag + verse_num
if content != verse_id:
for i in range(len(verse_order_list)):
if verse_order_list[i].lower() == content.lower():
verse_order_list[i] = verse_tag + verse_num
elif line and not line.isspace():
verse += line + '\n'
elif verse:
self.add_verse(verse, verse_tag+verse_num)
verse = ''
verse_order_list[i] = verse_id
else:
# add line text to verse. Strip out html
verse += re_html_strip.sub('', line) + '\n'
if verse:
self.add_verse(verse, verse_tag+verse_num)
# remove trailing linebreak, part of the WA syntax
if verse.endswith('\n\n'):
verse = verse[:-1]
self.add_verse(verse, verse_id)
used_verses.append(verse_id)
if verse_order_list:
self.verse_order_list = verse_order_list
# Use the verse order in the import, but remove entries that doesn't have a text
cleaned_verse_order_list = []
for verse in verse_order_list:
if verse in used_verses:
cleaned_verse_order_list.append(verse)
self.verse_order_list = cleaned_verse_order_list
if not self.finish():
self.log_error(translate('SongsPlugin.WorshipAssistantImport', 'Record %d') % index
+ (': "' + self.title + '"' if self.title else ''))

View File

@ -339,6 +339,9 @@ class SongMediaItem(MediaManagerItem):
self.remote_song = -1
self.remote_triggered = None
if item:
if preview:
# A song can only be edited if it comes from plugin, so we set it again for the new item.
item.from_plugin = True
return item
return None

View File

@ -75,9 +75,14 @@ class OpenLyricsExport(RegistryProperties):
filename = '%s (%s)' % (song.title, ', '.join([author.display_name for author in song.authors]))
filename = clean_filename(filename)
# Ensure the filename isn't too long for some filesystems
filename = '%s.xml' % filename[0:250 - len(self.save_path)]
filename_with_ext = '%s.xml' % filename[0:250 - len(self.save_path)]
# Make sure we're not overwriting an existing file
conflicts = 0
while os.path.exists(os.path.join(self.save_path, filename_with_ext)):
conflicts += 1
filename_with_ext = '%s-%d.xml' % (filename[0:247 - len(self.save_path)], conflicts)
# Pass a file object, because lxml does not cope with some special
# characters in the path (see lp:757673 and lp:744337).
tree.write(open(os.path.join(self.save_path, filename), 'wb'), encoding='utf-8', xml_declaration=True,
pretty_print=True)
tree.write(open(os.path.join(self.save_path, filename_with_ext), 'wb'), encoding='utf-8',
xml_declaration=True, pretty_print=True)
return True

View File

@ -99,7 +99,7 @@ class SongUsageDetailForm(QtGui.QDialog, Ui_SongUsageDetailDialog, RegistryPrope
report_file_name = os.path.join(path, file_name)
file_handle = None
try:
file_handle = open(report_file_name, 'w')
file_handle = open(report_file_name, 'wb')
for instance in usage:
record = '\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",' \
'\"%s\",\"%s\"\n' % \

View File

@ -115,3 +115,15 @@ class TestSettings(TestCase, TestMixin):
# THEN the new value is returned when re-read
self.assertEqual('very short', Settings().value('test/extend'), 'The saved value should be returned')
def settings_nonexisting_test(self):
"""
Test the Settings on query for non-existing value
"""
# GIVEN: A new Settings setup
with self.assertRaises(KeyError) as cm:
# WHEN reading a setting that doesn't exists
does_not_exist_value = Settings().value('core/does not exists')
# THEN: An exception with the non-existing key should be thrown
self.assertEqual(str(cm.exception), "'core/does not exists'", 'We should get an exception')

View File

@ -34,7 +34,7 @@ from unittest import TestCase
from PyQt4 import QtCore
from openlp.core.common import Registry
from openlp.core.lib import Renderer, ScreenList
from openlp.core.lib import Renderer, ScreenList, ServiceItem
from tests.interfaces import MagicMock
@ -88,7 +88,7 @@ class TestRenderer(TestCase):
expected_tuple = ('{st}{r}Text text text{/r}{/st}', '{st}{r}',
'<strong><span style="-webkit-text-fill-color:red">')
# WHEN:
# WHEN: The renderer converts the start tags
result = renderer._get_start_tags(given_raw_text)
# THEN: Check if the correct tuple is returned.
@ -104,8 +104,59 @@ class TestRenderer(TestCase):
given_line = 'beginning asdf \n end asdf'
expected_words = ['beginning', 'asdf', 'end', 'asdf']
# WHEN: Split the line
# WHEN: Split the line based on word split rules
result_words = renderer._words_split(given_line)
# THEN: The word lists should be the same.
self.assertListEqual(result_words, expected_words)
def format_slide_logical_split_test(self):
"""
Test that a line with text and a logic break does not break the renderer just returns the input
"""
# GIVEN: A line of with a space text and the logical split
renderer = Renderer()
renderer.empty_height = 25
given_line = 'a\n[---]\nb'
expected_words = ['a<br>[---]<br>b']
service_item = ServiceItem(None)
# WHEN: Split the line based on word split rules
result_words = renderer.format_slide(given_line, service_item)
# THEN: The word lists should be the same.
self.assertListEqual(result_words, expected_words)
def format_slide_blank_before_split_test(self):
"""
Test that a line with blanks before the logical split at handled
"""
# GIVEN: A line of with a space before the logical split
renderer = Renderer()
renderer.empty_height = 25
given_line = '\n [---]\n'
expected_words = ['<br> [---]']
service_item = ServiceItem(None)
# WHEN: Split the line based on word split rules
result_words = renderer.format_slide(given_line, service_item)
# THEN: The blanks have been removed.
self.assertListEqual(result_words, expected_words)
def format_slide_blank_after_split_test(self):
"""
Test that a line with blanks before the logical split at handled
"""
# GIVEN: A line of with a space after the logical split
renderer = Renderer()
renderer.empty_height = 25
given_line = '\n[---] \n'
expected_words = ['<br>[---] ']
service_item = ServiceItem(None)
# WHEN: Split the line based on word split rules
result_words = renderer.format_slide(given_line, service_item)
# THEN: The blanks have been removed.
self.assertListEqual(result_words, expected_words)

View File

@ -0,0 +1,86 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2014 Raoul Snyman #
# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan #
# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, #
# Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
This module contains tests for the OpenLyrics song importer.
"""
import os
import shutil
from unittest import TestCase
from tempfile import mkdtemp
from tests.functional import MagicMock, patch
from tests.helpers.testmixin import TestMixin
from openlp.plugins.songs.lib.openlyricsexport import OpenLyricsExport
from openlp.core.common import Registry
class TestOpenLyricsExport(TestCase, TestMixin):
"""
Test the functions in the :mod:`openlyricsexport` module.
"""
def setUp(self):
"""
Create the registry
"""
Registry.create()
self.temp_folder = mkdtemp()
def tearDown(self):
"""
Cleanup
"""
shutil.rmtree(self.temp_folder)
def export_same_filename_test(self):
"""
Test that files is not overwritten if songs has same title and author
"""
# GIVEN: A mocked song_to_xml, 2 mocked songs, a mocked application and an OpenLyricsExport instance
with patch('openlp.plugins.songs.lib.openlyricsexport.OpenLyrics.song_to_xml') as mocked_song_to_xml:
mocked_song_to_xml.return_value = '<?xml version="1.0" encoding="UTF-8"?>\n<empty/>'
author = MagicMock()
author.display_name = 'Test Author'
song = MagicMock()
song.authors = [author]
song.title = 'Test Title'
parent = MagicMock()
parent.stop_export_flag = False
mocked_application_object = MagicMock()
Registry().register('application', mocked_application_object)
ol_export = OpenLyricsExport(parent, [song, song], self.temp_folder)
# WHEN: Doing the export
ol_export.do_export()
# THEN: The exporter should have created 2 files
self.assertTrue(os.path.exists(os.path.join(self.temp_folder,
'%s (%s).xml' % (song.title, author.display_name))))
self.assertTrue(os.path.exists(os.path.join(self.temp_folder,
'%s (%s)-1.xml' % (song.title, author.display_name))))

View File

@ -54,3 +54,5 @@ class TestWorshipAssistantFileImport(SongImportTestHelper):
self.load_external_result_data(os.path.join(TEST_PATH, 'du_herr.json')))
self.file_import(os.path.join(TEST_PATH, 'would_you_be_free.csv'),
self.load_external_result_data(os.path.join(TEST_PATH, 'would_you_be_free.json')))
self.file_import(os.path.join(TEST_PATH, 'would_you_be_free2.csv'),
self.load_external_result_data(os.path.join(TEST_PATH, 'would_you_be_free.json')))

View File

@ -105,3 +105,13 @@ class TestBibleManager(TestCase, TestMixin):
# THEN a verse array should be returned
self.assertEqual([(54, 1, 1, -1), (54, 2, 1, 1)], results,
"The bible verses should match the expected results")
def parse_reference_four_test(self):
"""
Test the parse_reference method with non existence book
"""
# GIVEN given a bible in the bible manager
# WHEN asking to parse the bible reference
results = parse_reference('Raoul 1', self.manager.db_cache['tests'], MagicMock())
# THEN a verse array should be returned
self.assertEqual(False, results, "The bible Search should return False")

View File

@ -8,7 +8,7 @@
"copyright": "Public Domain",
"verses": [
[
"Would you be free from your burden of sin? \nThere's power in the blood, power in the blood \nWould you o'er evil a victory win? \nThere's wonderful power in the blood \n",
"Would you be free from your burden of sin? \nThere's power in the blood, power in the blood \nWould you o'er evil a victory win? \nThere's wonderful power in the blood \n ",
"v1"
],
[

View File

@ -0,0 +1,31 @@
SONGNR,TITLE,AUTHOR,COPYRIGHT,FIRSTLINE,PRIKEY,ALTKEY,TEMPO,FOCUS,THEME,SCRIPTURE,ACTIVE,SONGBOOK,TIMESIG,INTRODUCED,LASTUSED,TIMESUSED,CCLINR,USER1,USER2,USER3,USER4,USER5,ROADMAP,FILELINK1,OVERMAP,FILELINK2,LYRICS,INFO,LYRICS2,Background
"7","Would You Be Free","Jones, Lewis E.","Public Domain","Would you be free from your burden of sin?","G","","Moderate","Only To Others","","","N","Y","","1899-12-30","1899-12-30","","","","","","","","1, C, 1","","","",".G C G
Would you be free from your burden of sin?
. D D7 G
There's power in the blood, power in the blood
. C G
Would you o'er evil a victory win?
. D D7 G
There's wonderful power in the blood
.G C G
There is power, power, wonder working power
.D G
In the blood of the Lamb
. C G
There is power, power, wonder working power
. D G
In the precious blood of the Lamb
","","[1]
Would you be free from your burden of sin?
There's power in the blood, power in the blood
Would you o'er evil a victory win?
There's wonderful power in the blood
[C]
There is power, power, wonder working power
In the blood of the Lamb
There is power, power, wonder working power
In the precious blood of the Lamb
[x]",""
1 SONGNR TITLE AUTHOR COPYRIGHT FIRSTLINE PRIKEY ALTKEY TEMPO FOCUS THEME SCRIPTURE ACTIVE SONGBOOK TIMESIG INTRODUCED LASTUSED TIMESUSED CCLINR USER1 USER2 USER3 USER4 USER5 ROADMAP FILELINK1 OVERMAP FILELINK2 LYRICS INFO LYRICS2 Background
2 7 Would You Be Free Jones, Lewis E. Public Domain Would you be free from your burden of sin? G Moderate Only To Others N Y 1899-12-30 1899-12-30 1, C, 1 .G C G Would you be free from your burden of sin? . D D7 G There's power in the blood, power in the blood . C G Would you o'er evil a victory win? . D D7 G There's wonderful power in the blood .G C G There is power, power, wonder working power .D G In the blood of the Lamb . C G There is power, power, wonder working power . D G In the precious blood of the Lamb [1] Would you be free from your burden of sin? There's power in the blood, power in the blood Would you o'er evil a victory win? There's wonderful power in the blood [C] There is power, power, wonder working power In the blood of the Lamb There is power, power, wonder working power In the precious blood of the Lamb [x]