From dc5de9a54d73b3233019da7746d148a8b9b814bd Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Sun, 22 Jan 2017 18:04:32 +0100 Subject: [PATCH 1/2] Clean search lyrics for formatting tags. Fixes bug #1655988. Fix an issue with easyslide import not handling verse order correctly. Fixes bug #1655985. Improve the songbeamer encoding detection. Fixes bug #1530597. Handle a few videopsalm quirks. Fixes bug #1652851. Fixes: https://launchpad.net/bugs/1655988, https://launchpad.net/bugs/1655985, https://launchpad.net/bugs/1530597, https://launchpad.net/bugs/1652851 --- openlp/plugins/songs/lib/__init__.py | 4 +- .../plugins/songs/lib/importers/easyslides.py | 24 ++++----- .../plugins/songs/lib/importers/songbeamer.py | 10 ++-- .../plugins/songs/lib/importers/videopsalm.py | 6 ++- .../songs/test_easyslidesimport.py | 2 + .../easyslidessongs/Amazing Grace.json | 12 ++--- .../easyslidessongs/Export_2017-01-12_BB.json | 44 ++++++++++++++++ .../easyslidessongs/Export_2017-01-12_BB.xml | 50 +++++++++++++++++++ .../videopsalm-as-safe-a-stronghold.json | 2 +- 9 files changed, 128 insertions(+), 26 deletions(-) create mode 100644 tests/resources/easyslidessongs/Export_2017-01-12_BB.json create mode 100644 tests/resources/easyslidessongs/Export_2017-01-12_BB.xml diff --git a/openlp/plugins/songs/lib/__init__.py b/openlp/plugins/songs/lib/__init__.py index 6e1990508..00af9334a 100644 --- a/openlp/plugins/songs/lib/__init__.py +++ b/openlp/plugins/songs/lib/__init__.py @@ -30,7 +30,7 @@ import re from PyQt5 import QtWidgets from openlp.core.common import AppLocation -from openlp.core.lib import translate +from openlp.core.lib import translate, clean_tags from openlp.core.utils import CONTROL_CHARS from openlp.plugins.songs.lib.db import Author, MediaFile, Song, Topic from openlp.plugins.songs.lib.ui import SongStrings @@ -381,7 +381,7 @@ def clean_song(manager, song): if isinstance(song.lyrics, bytes): song.lyrics = str(song.lyrics, encoding='utf8') verses = SongXML().get_verses(song.lyrics) - song.search_lyrics = ' '.join([clean_string(verse[1]) for verse in verses]) + song.search_lyrics = ' '.join([clean_string(clean_tags(verse[1])) for verse in verses]) # The song does not have any author, add one. if not song.authors_songs: name = SongStrings.AuthorUnknown diff --git a/openlp/plugins/songs/lib/importers/easyslides.py b/openlp/plugins/songs/lib/importers/easyslides.py index 9b7352f75..b65d06153 100644 --- a/openlp/plugins/songs/lib/importers/easyslides.py +++ b/openlp/plugins/songs/lib/importers/easyslides.py @@ -179,7 +179,7 @@ class EasySlidesImport(SongImport): reg = default_region verses[reg] = {} # instance differentiates occurrences of same verse tag - vt = 'V' + vt = 'v' vn = '1' inst = 1 for line in lines: @@ -192,14 +192,14 @@ class EasySlidesImport(SongImport): inst += 1 else: # separators are not used, so empty line starts a new verse - vt = 'V' + vt = 'v' vn = len(verses[reg].get(vt, {})) + 1 inst = 1 elif line[0:7] == '[region': reg = self._extract_region(line) verses.setdefault(reg, {}) if not regions_in_verses: - vt = 'V' + vt = 'v' vn = '1' inst = 1 elif line[0] == '[': @@ -212,7 +212,7 @@ class EasySlidesImport(SongImport): if match: marker = match.group(1).strip() vn = match.group(2) - vt = MarkTypes.get(marker, 'O') if marker else 'V' + vt = MarkTypes.get(marker, 'o') if marker else 'v' if regions_in_verses: region = default_region inst = 1 @@ -237,13 +237,13 @@ class EasySlidesImport(SongImport): lines = '\n'.join(verses[reg][vt][vn][inst]) self.add_verse(lines, versetag) SeqTypes = { - 'p': 'P1', - 'q': 'P2', - 'c': 'C1', - 't': 'C2', - 'b': 'B1', - 'w': 'B2', - 'e': 'E1'} + 'p': 'p1', + 'q': 'p2', + 'c': 'c1', + 't': 'c2', + 'b': 'b1', + 'w': 'b2', + 'e': 'e1'} # Make use of Sequence data, determining the order of verses try: order = str(song.Sequence).strip().split(',') @@ -251,7 +251,7 @@ class EasySlidesImport(SongImport): if not tag: continue elif tag[0].isdigit(): - tag = 'V' + tag + tag = 'v' + tag elif tag.lower() in SeqTypes: tag = SeqTypes[tag.lower()] else: diff --git a/openlp/plugins/songs/lib/importers/songbeamer.py b/openlp/plugins/songs/lib/importers/songbeamer.py index f85f5d361..8dc7deb5e 100644 --- a/openlp/plugins/songs/lib/importers/songbeamer.py +++ b/openlp/plugins/songs/lib/importers/songbeamer.py @@ -115,11 +115,15 @@ class SongBeamerImport(SongImport): if os.path.isfile(import_file): # First open in binary mode to detect the encoding detect_file = open(import_file, 'rb') - details = chardet.detect(detect_file.read()) + self.input_file_encoding = chardet.detect(detect_file.read())['encoding'] detect_file.close() - infile = codecs.open(import_file, 'r', details['encoding']) + # The encoding should only be ANSI (cp1252), UTF-8, Unicode, Big-Endian-Unicode. + # So if it doesn't start with 'u' we default to cp1252. See: + # https://forum.songbeamer.com/viewtopic.php?p=419&sid=ca4814924e37c11e4438b7272a98b6f2 + if self.input_file_encoding.lower().startswith('u'): + self.input_file_encoding = 'cp1252' + infile = open(import_file, 'rt', encoding=self.input_file_encoding) song_data = infile.readlines() - infile.close() else: continue self.title = file_name.split('.sng')[0] diff --git a/openlp/plugins/songs/lib/importers/videopsalm.py b/openlp/plugins/songs/lib/importers/videopsalm.py index eb80b9dcb..f5b531b9a 100644 --- a/openlp/plugins/songs/lib/importers/videopsalm.py +++ b/openlp/plugins/songs/lib/importers/videopsalm.py @@ -65,8 +65,8 @@ class VideoPsalmImport(SongImport): if c == '\n': if inside_quotes: processed_content += '\\n' - # Put keys in quotes - elif c.isalnum() and not inside_quotes: + # Put keys in quotes. The '-' is for handling nagative numbers + elif (c.isalnum() or c == '-') and not inside_quotes: processed_content += '"' + c c = next(file_content_it) while c.isalnum(): @@ -121,6 +121,8 @@ class VideoPsalmImport(SongImport): if 'Memo3' in song: self.add_comment(song['Memo3']) for verse in song['Verses']: + if 'Text' not in verse: + continue self.add_verse(verse['Text'], 'v') if not self.finish(): self.log_error('Could not import %s' % self.title) diff --git a/tests/functional/openlp_plugins/songs/test_easyslidesimport.py b/tests/functional/openlp_plugins/songs/test_easyslidesimport.py index 9e5468a5b..bfe9abcc2 100644 --- a/tests/functional/openlp_plugins/songs/test_easyslidesimport.py +++ b/tests/functional/openlp_plugins/songs/test_easyslidesimport.py @@ -43,3 +43,5 @@ class TestEasySlidesFileImport(SongImportTestHelper): """ self.file_import(os.path.join(TEST_PATH, 'amazing-grace.xml'), self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json'))) + self.file_import(os.path.join(TEST_PATH, 'Export_2017-01-12_BB.xml'), + self.load_external_result_data(os.path.join(TEST_PATH, 'Export_2017-01-12_BB.json'))) diff --git a/tests/resources/easyslidessongs/Amazing Grace.json b/tests/resources/easyslidessongs/Amazing Grace.json index 10579e652..500639657 100644 --- a/tests/resources/easyslidessongs/Amazing Grace.json +++ b/tests/resources/easyslidessongs/Amazing Grace.json @@ -6,27 +6,27 @@ "verses": [ [ "Amazing grace! How sweet the sound\nThat saved a wretch like me;\nI once was lost, but now am found,\nWas blind, but now I see.", - "V1" + "v1" ], [ "'Twas grace that taught my heart to fear,\nAnd grace my fears relieved;\nHow precious did that grace appear,\nThe hour I first believed!", - "V2" + "v2" ], [ "Through many dangers, toils and snares\nI have already come;\n'Tis grace that brought me safe thus far,\nAnd grace will lead me home.", - "V3" + "v3" ], [ "The Lord has promised good to me,\nHis word my hope secures;\nHe will my shield and portion be\nAs long as life endures.", - "V4" + "v4" ], [ "Yes, when this heart and flesh shall fail,\nAnd mortal life shall cease,\nI shall possess within the veil\nA life of joy and peace.", - "V5" + "v5" ], [ "When we've been there a thousand years,\nBright shining as the sun,\nWe've no less days to sing God's praise\nThan when we first begun.", - "V6" + "v6" ] ] } diff --git a/tests/resources/easyslidessongs/Export_2017-01-12_BB.json b/tests/resources/easyslidessongs/Export_2017-01-12_BB.json new file mode 100644 index 000000000..06583e123 --- /dev/null +++ b/tests/resources/easyslidessongs/Export_2017-01-12_BB.json @@ -0,0 +1,44 @@ +{ + "title": "BBBBBBBBB", + "authors": [ + "John Newton (1725-1807)" + ], + "verses": [ + [ + "V1V1V1V1V1V1\nV1V1V1V1V1V1", + "v1" + ], + [ + "V2V2V2V2V2V2\nV2V2V2V2V2V2", + "v2" + ], + [ + "C1C1C1C1C1C1\nC1C1C1C1C1C1", + "c1" + ], + [ + "C2C2C2C2C2C2\nC2C2C2C2C2C2", + "c2" + ], + [ + "B1B1B1B1B1B1\nB1B1B1B1B1B1", + "b1" + ], + [ + "B2B2B2B2B2B2\nB2B2B2B2B2B2", + "b2" + ], + [ + "PRE1PRE1PRE1\nPRE1PRE1PRE1", + "p1" + ], + [ + "PRE2PRE2PRE2\nPRE2PRE2PRE2", + "p2" + ], + [ + "ENDENDENDEND\nENDENDENDEND", + "e1" + ] + ] +} diff --git a/tests/resources/easyslidessongs/Export_2017-01-12_BB.xml b/tests/resources/easyslidessongs/Export_2017-01-12_BB.xml new file mode 100644 index 000000000..cb2da7675 --- /dev/null +++ b/tests/resources/easyslidessongs/Export_2017-01-12_BB.xml @@ -0,0 +1,50 @@ + + + + BBBBBBBBB + + NAGY + 0 + [1] +V1V1V1V1V1V1 +V1V1V1V1V1V1 +[2] +V2V2V2V2V2V2 +V2V2V2V2V2V2 +[chorus] +C1C1C1C1C1C1 +C1C1C1C1C1C1 +[chorus 2] +C2C2C2C2C2C2 +C2C2C2C2C2C2 +[bridge] +B1B1B1B1B1B1 +B1B1B1B1B1B1 +[bridge 2] +B2B2B2B2B2B2 +B2B2B2B2B2B2 +[prechorus] +PRE1PRE1PRE1 +PRE1PRE1PRE1 +[prechorus 2] +PRE2PRE2PRE2 +PRE2PRE2PRE2 +[ending] +ENDENDENDEND +ENDENDENDEND + + 1,2,c,t,b,w,p,q,e + + + + + + -1 + + + + + + 10=> + + \ No newline at end of file diff --git a/tests/resources/videopsalmsongs/videopsalm-as-safe-a-stronghold.json b/tests/resources/videopsalmsongs/videopsalm-as-safe-a-stronghold.json index d3e9296c7..707ef4c42 100644 --- a/tests/resources/videopsalmsongs/videopsalm-as-safe-a-stronghold.json +++ b/tests/resources/videopsalmsongs/videopsalm-as-safe-a-stronghold.json @@ -1,4 +1,4 @@ -{Abbreviation:"SB1",Copyright:"Public domain",Songs:[{ID:3,Composer:"Unknown",Author:"Martin Luther",Copyright:"Public +{Abbreviation:"SB1",Copyright:"Public domain",Songs:[{ID:3,Composer:"Unknown",Author:"Martin Luther",Capo:-1,Copyright:"Public Domain",Theme:"tema1 tema2",CCLI:"12345",Alias:"A safe stronghold",Memo1:"This is the first comment From 3cb1dae0f07db7381d0725ebb1128e0a76d25b6e Mon Sep 17 00:00:00 2001 From: Tomas Groth Date: Sun, 22 Jan 2017 21:01:53 +0100 Subject: [PATCH 2/2] Bug #1487788: Importing photos does not give focus to OpenLP Bug #1512040: Loop tooltip gets stuck to "Stop playing..." Bug #1624661: Missing DB in unmounted disk results in Traceback Fixes: https://launchpad.net/bugs/1487788, https://launchpad.net/bugs/1512040, https://launchpad.net/bugs/1624661 --- openlp/core/__init__.py | 40 ++++++++++++++++++++++++++-- openlp/core/lib/treewidgetwithdnd.py | 7 ++++- openlp/core/ui/slidecontroller.py | 2 ++ 3 files changed, 46 insertions(+), 3 deletions(-) diff --git a/openlp/core/__init__.py b/openlp/core/__init__.py index dba3bfb38..dfd710523 100644 --- a/openlp/core/__init__.py +++ b/openlp/core/__init__.py @@ -177,6 +177,38 @@ class OpenLP(OpenLPMixin, QtWidgets.QApplication): self.shared_memory.create(1) return False + def is_data_path_missing(self): + """ + Check if the data folder path exists. + """ + data_folder_path = AppLocation.get_data_path() + if not os.path.exists(data_folder_path): + log.critical('Database was not found in: ' + data_folder_path) + status = QtWidgets.QMessageBox.critical(None, translate('OpenLP', 'Data Directory Error'), + translate('OpenLP', 'OpenLP data folder was not found in:\n\n{path}' + '\n\nThe location of the data folder was ' + 'previously changed from the OpenLP\'s ' + 'default location. If the data was stored on ' + 'removable device, that device needs to be ' + 'made available.\n\nYou may reset the data ' + 'location back to the default location, ' + 'or you can try to make the current location ' + 'available.\n\nDo you want to reset to the ' + 'default data location? If not, OpenLP will be ' + 'closed so you can try to fix the the problem.') + .format(path=data_folder_path), + QtWidgets.QMessageBox.StandardButtons(QtWidgets.QMessageBox.Yes | + QtWidgets.QMessageBox.No), + QtWidgets.QMessageBox.No) + if status == QtWidgets.QMessageBox.No: + # If answer was "No", return "True", it will shutdown OpenLP in def main + log.info('User requested termination') + return True + # If answer was "Yes", remove the custom data path thus resetting the default location. + Settings().remove('advanced/data path') + log.info('Database location has been reset to the default settings.') + return False + def hook_exception(self, exc_type, value, traceback): """ Add an exception hook so that any uncaught exceptions are displayed in this window rather than somewhere where @@ -213,7 +245,7 @@ class OpenLP(OpenLPMixin, QtWidgets.QApplication): Settings().setValue('core/application version', openlp_version) # If data_version is different from the current version ask if we should backup the data folder elif data_version != openlp_version: - if self.splash.isVisible(): + if can_show_splash and self.splash.isVisible(): self.splash.hide() if QtWidgets.QMessageBox.question(None, translate('OpenLP', 'Backup'), translate('OpenLP', 'OpenLP has been upgraded, do you want to create ' @@ -378,9 +410,13 @@ def main(args=None): Registry.create() Registry().register('application', application) application.setApplicationVersion(get_application_version()['version']) - # Instance check + # Check if an instance of OpenLP is already running. Quit if there is a running instance and the user only wants one if application.is_already_running(): sys.exit() + # If the custom data path is missing and the user wants to restore the data path, quit OpenLP. + if application.is_data_path_missing(): + application.shared_memory.detach() + sys.exit() # Remove/convert obsolete settings. Settings().remove_obsolete_settings() # First time checks in settings diff --git a/openlp/core/lib/treewidgetwithdnd.py b/openlp/core/lib/treewidgetwithdnd.py index b025498a3..fe45666f5 100644 --- a/openlp/core/lib/treewidgetwithdnd.py +++ b/openlp/core/lib/treewidgetwithdnd.py @@ -26,7 +26,7 @@ import os from PyQt5 import QtCore, QtGui, QtWidgets -from openlp.core.common import Registry +from openlp.core.common import Registry, is_win class TreeWidgetWithDnD(QtWidgets.QTreeWidget): @@ -108,6 +108,11 @@ class TreeWidgetWithDnD(QtWidgets.QTreeWidget): :param event: Handle of the event pint passed """ + # If we are on Windows, OpenLP window will not be set on top. For example, user can drag images to Library and + # the folder stays on top of the group creation box. This piece of code fixes this issue. + if is_win(): + self.setWindowState(self.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive) + self.setWindowState(QtCore.Qt.WindowNoState) if event.mimeData().hasUrls(): event.setDropAction(QtCore.Qt.CopyAction) event.accept() diff --git a/openlp/core/ui/slidecontroller.py b/openlp/core/ui/slidecontroller.py index 489d9955f..456399869 100644 --- a/openlp/core/ui/slidecontroller.py +++ b/openlp/core/ui/slidecontroller.py @@ -714,8 +714,10 @@ class SlideController(DisplayController, RegistryProperties): # Reset the button self.play_slides_once.setChecked(False) self.play_slides_once.setIcon(build_icon(':/media/media_time.png')) + self.play_slides_once.setText(UiStrings().PlaySlidesToEnd) self.play_slides_loop.setChecked(False) self.play_slides_loop.setIcon(build_icon(':/media/media_time.png')) + self.play_slides_loop.setText(UiStrings().PlaySlidesInLoop) if item.is_text(): if (Settings().value(self.main_window.songs_settings_section + '/display songbar') and not self.song_menu.menu().isEmpty()):