diff --git a/openlp/core/ui/slidecontroller.py b/openlp/core/ui/slidecontroller.py index b12438679..062a8440f 100644 --- a/openlp/core/ui/slidecontroller.py +++ b/openlp/core/ui/slidecontroller.py @@ -374,7 +374,8 @@ class SlideController(DisplayController, RegistryProperties): triggers=self._slide_shortcut_activated) for s in shortcuts]) self.shortcut_timer.timeout.connect(self._slide_shortcut_activated) # Signals - self.preview_widget.itemSelectionChanged.connect(self.on_slide_selected) + self.preview_widget.clicked.connect(self.on_slide_selected) + self.preview_widget.verticalHeader().sectionClicked.connect(self.on_slide_selected) if self.is_live: # Need to use event as called across threads and UI is updated QtCore.QObject.connect(self, QtCore.SIGNAL('slidecontroller_toggle_display'), self.toggle_display) diff --git a/openlp/plugins/presentations/lib/mediaitem.py b/openlp/plugins/presentations/lib/mediaitem.py index 661f8f906..54c570c9a 100644 --- a/openlp/plugins/presentations/lib/mediaitem.py +++ b/openlp/plugins/presentations/lib/mediaitem.py @@ -38,7 +38,7 @@ from openlp.core.lib import MediaManagerItem, ItemCapabilities, ServiceItemConte from openlp.core.lib.ui import critical_error_message_box, create_horizontal_adjusting_combo_box from openlp.core.utils import get_locale_key from openlp.plugins.presentations.lib import MessageListener - +from openlp.plugins.presentations.lib.pdfcontroller import PDF_CONTROLLER_FILETYPES log = logging.getLogger(__name__) @@ -260,11 +260,11 @@ class PresentationMediaItem(MediaManagerItem): filename = presentation_file if filename is None: filename = items[0].data(QtCore.Qt.UserRole) - file_type = os.path.splitext(filename)[1][1:] + file_type = os.path.splitext(filename.lower())[1][1:] if not self.display_type_combo_box.currentText(): return False service_item.add_capability(ItemCapabilities.CanEditTitle) - if (file_type == 'pdf' or file_type == 'xps') and context != ServiceItemContext.Service: + if file_type in PDF_CONTROLLER_FILETYPES and context != ServiceItemContext.Service: service_item.add_capability(ItemCapabilities.CanMaintain) service_item.add_capability(ItemCapabilities.CanPreview) service_item.add_capability(ItemCapabilities.CanLoop) diff --git a/openlp/plugins/presentations/lib/messagelistener.py b/openlp/plugins/presentations/lib/messagelistener.py index ac115228a..fb37ef403 100644 --- a/openlp/plugins/presentations/lib/messagelistener.py +++ b/openlp/plugins/presentations/lib/messagelistener.py @@ -29,12 +29,14 @@ import logging import copy +import os from PyQt4 import QtCore from openlp.core.common import Registry from openlp.core.ui import HideMode from openlp.core.lib import ServiceItemContext, ServiceItem +from openlp.plugins.presentations.lib.pdfcontroller import PDF_CONTROLLER_FILETYPES log = logging.getLogger(__name__) @@ -320,10 +322,11 @@ class MessageListener(object): file = item.get_frame_path() self.handler = item.processor # When starting presentation from the servicemanager we convert - # PDF/XPS-serviceitems into image-serviceitems. When started from the mediamanager + # PDF/XPS/OXPS-serviceitems into image-serviceitems. When started from the mediamanager # the conversion has already been done at this point. - if file.endswith('.pdf') or file.endswith('.xps'): - log.debug('Converting from pdf/xps to images for serviceitem with file %s', file) + file_type = os.path.splitext(file.lower())[1][1:] + if file_type in PDF_CONTROLLER_FILETYPES: + log.debug('Converting from pdf/xps/oxps to images for serviceitem with file %s', file) # Create a copy of the original item, and then clear the original item so it can be filled with images item_cpy = copy.copy(item) item.__init__(None) @@ -338,7 +341,7 @@ class MessageListener(object): item.image_border = item_cpy.image_border item.main = item_cpy.main item.theme_data = item_cpy.theme_data - # When presenting PDF or XPS, we are using the image presentation code, + # When presenting PDF/XPS/OXPS, we are using the image presentation code, # so handler & processor is set to None, and we skip adding the handler. self.handler = None if self.handler == self.media_item.automatic: @@ -349,7 +352,7 @@ class MessageListener(object): controller = self.live_handler else: controller = self.preview_handler - # When presenting PDF or XPS, we are using the image presentation code, + # When presenting PDF/XPS/OXPS, we are using the image presentation code, # so handler & processor is set to None, and we skip adding the handler. if self.handler is None: self.controller = controller diff --git a/openlp/plugins/presentations/lib/pdfcontroller.py b/openlp/plugins/presentations/lib/pdfcontroller.py index 0283fefd4..e1d0dc8f0 100644 --- a/openlp/plugins/presentations/lib/pdfcontroller.py +++ b/openlp/plugins/presentations/lib/pdfcontroller.py @@ -34,12 +34,17 @@ import re from subprocess import check_output, CalledProcessError, STDOUT from openlp.core.utils import AppLocation -from openlp.core.common import Settings, is_win +from openlp.core.common import Settings, is_win, trace_error_handler from openlp.core.lib import ScreenList from .presentationcontroller import PresentationController, PresentationDocument +if is_win(): + from subprocess import STARTUPINFO, STARTF_USESHOWWINDOW + log = logging.getLogger(__name__) +PDF_CONTROLLER_FILETYPES = ['pdf', 'xps', 'oxps'] + class PdfController(PresentationController): """ @@ -74,11 +79,19 @@ class PdfController(PresentationController): runlog = '' log.debug('testing program_path: %s', program_path) try: - runlog = check_output([program_path, '--help'], stderr=STDOUT) + # Setup startupinfo options for check_output to avoid console popping up on windows + if is_win(): + startupinfo = STARTUPINFO() + startupinfo.dwFlags |= STARTF_USESHOWWINDOW + else: + startupinfo = None + runlog = check_output([program_path, '--help'], stderr=STDOUT, startupinfo=startupinfo) except CalledProcessError as e: runlog = e.output except Exception: + trace_error_handler(log) runlog = '' + log.debug('check_output returned: %s' % runlog) # Analyse the output to see it the program is mudraw, ghostscript or neither for line in runlog.splitlines(): decoded_line = line.decode() @@ -148,7 +161,7 @@ class PdfController(PresentationController): if os.path.isfile(os.path.join(application_path, 'mudraw')): self.mudrawbin = os.path.join(application_path, 'mudraw') if self.mudrawbin: - self.also_supports = ['xps'] + self.also_supports = ['xps', 'oxps'] return True elif self.gsbin: return True @@ -182,6 +195,12 @@ class PdfDocument(PresentationDocument): self.hidden = False self.image_files = [] self.num_pages = -1 + # Setup startupinfo options for check_output to avoid console popping up on windows + if is_win(): + self.startupinfo = STARTUPINFO() + self.startupinfo.dwFlags |= STARTF_USESHOWWINDOW + else: + self.startupinfo = None def gs_get_resolution(self, size): """ @@ -199,7 +218,8 @@ class PdfDocument(PresentationDocument): runlog = [] try: runlog = check_output([self.controller.gsbin, '-dNOPAUSE', '-dNODISPLAY', '-dBATCH', - '-sFile=' + self.file_path, gs_resolution_script]) + '-sFile=' + self.file_path, gs_resolution_script], + startupinfo=self.startupinfo) except CalledProcessError as e: log.debug(' '.join(e.cmd)) log.debug(e.output) @@ -248,13 +268,14 @@ class PdfDocument(PresentationDocument): os.makedirs(self.get_temp_folder()) if self.controller.mudrawbin: runlog = check_output([self.controller.mudrawbin, '-w', str(size.right()), '-h', str(size.bottom()), - '-o', os.path.join(self.get_temp_folder(), 'mainslide%03d.png'), self.file_path]) + '-o', os.path.join(self.get_temp_folder(), 'mainslide%03d.png'), self.file_path], + startupinfo=self.startupinfo) elif self.controller.gsbin: resolution = self.gs_get_resolution(size) runlog = check_output([self.controller.gsbin, '-dSAFER', '-dNOPAUSE', '-dBATCH', '-sDEVICE=png16m', '-r' + str(resolution), '-dTextAlphaBits=4', '-dGraphicsAlphaBits=4', '-sOutputFile=' + os.path.join(self.get_temp_folder(), 'mainslide%03d.png'), - self.file_path]) + self.file_path], startupinfo=self.startupinfo) created_files = sorted(os.listdir(self.get_temp_folder())) for fn in created_files: if os.path.isfile(os.path.join(self.get_temp_folder(), fn)): diff --git a/openlp/plugins/songs/lib/openlyricsxml.py b/openlp/plugins/songs/lib/openlyricsxml.py index 9458d6180..cab51093c 100644 --- a/openlp/plugins/songs/lib/openlyricsxml.py +++ b/openlp/plugins/songs/lib/openlyricsxml.py @@ -239,6 +239,7 @@ class OpenLyrics(object): def __init__(self, manager): self.manager = manager + FormattingTags.load_tags() def song_to_xml(self, song): """ @@ -582,18 +583,20 @@ class OpenLyrics(object): # Some tags have only start html e.g. {br} 'end html': tag.close.text if hasattr(tag, 'close') else '', 'protected': False, + # Add 'temporary' key in case the formatting tag should not be saved otherwise it is supposed that + # formatting tag is permanent. + 'temporary': temporary } - # Add 'temporary' key in case the formatting tag should not be saved otherwise it is supposed that - # formatting tag is permanent. - if temporary: - openlp_tag['temporary'] = temporary found_tags.append(openlp_tag) existing_tag_ids = [tag['start tag'] for tag in FormattingTags.get_html_tags()] new_tags = [tag for tag in found_tags if tag['start tag'] not in existing_tag_ids] # Do not save an empty list. if new_tags: FormattingTags.add_html_tags(new_tags) - FormattingTags.save_html_tags() + if not temporary: + custom_tags = [tag for tag in FormattingTags.get_html_tags() + if not tag['protected'] and not tag['temporary']] + FormattingTags.save_html_tags(custom_tags) def _process_lines_mixed_content(self, element, newlines=True): """ diff --git a/tests/functional/openlp_plugins/presentations/test_mediaitem.py b/tests/functional/openlp_plugins/presentations/test_mediaitem.py index 2210c7d1f..b826a4cae 100644 --- a/tests/functional/openlp_plugins/presentations/test_mediaitem.py +++ b/tests/functional/openlp_plugins/presentations/test_mediaitem.py @@ -71,7 +71,7 @@ class TestMediaItem(TestCase, TestMixin): pdf_controller = MagicMock() pdf_controller.enabled.return_value = True pdf_controller.supports = ['pdf'] - pdf_controller.also_supports = ['xps'] + pdf_controller.also_supports = ['xps', 'oxps'] # Mock the controllers. self.media_item.controllers = { 'Impress': impress_controller, @@ -90,3 +90,4 @@ class TestMediaItem(TestCase, TestMixin): self.assertIn('*.ppt', self.media_item.on_new_file_masks, 'The file mask should contain the ppt extension') self.assertIn('*.pdf', self.media_item.on_new_file_masks, 'The file mask should contain the pdf extension') self.assertIn('*.xps', self.media_item.on_new_file_masks, 'The file mask should contain the xps extension') + self.assertIn('*.oxps', self.media_item.on_new_file_masks, 'The file mask should contain the oxps extension') diff --git a/tests/functional/openlp_plugins/remotes/test_router.py b/tests/functional/openlp_plugins/remotes/test_router.py index c0190ecda..a88a8822f 100644 --- a/tests/functional/openlp_plugins/remotes/test_router.py +++ b/tests/functional/openlp_plugins/remotes/test_router.py @@ -132,6 +132,7 @@ class TestRouter(TestCase, TestMixin): Test the main poll logic """ # GIVEN: a defined router with two slides + Registry.create() Registry().register('live_controller', MagicMock) router = HttpRouter() router.send_response = MagicMock() diff --git a/tests/functional/openlp_plugins/songs/test_ewimport.py b/tests/functional/openlp_plugins/songs/test_ewimport.py index f441084e7..3cb2c3bbd 100644 --- a/tests/functional/openlp_plugins/songs/test_ewimport.py +++ b/tests/functional/openlp_plugins/songs/test_ewimport.py @@ -35,6 +35,7 @@ from unittest import TestCase from tests.functional import MagicMock, patch +from openlp.core.common import Registry from openlp.plugins.songs.lib.importers.easyworship import EasyWorshipSongImport, FieldDescEntry, FieldType TEST_PATH = os.path.abspath( @@ -153,6 +154,11 @@ class TestEasyWorshipSongImport(TestCase): """ Test the functions in the :mod:`ewimport` module. """ + def setUp(self): + """ + Create the registry + """ + Registry.create() def create_field_desc_entry_test(self): """ diff --git a/tests/functional/openlp_plugins/songs/test_openlyricsimport.py b/tests/functional/openlp_plugins/songs/test_openlyricsimport.py index 25db3e9e4..d7ba07beb 100644 --- a/tests/functional/openlp_plugins/songs/test_openlyricsimport.py +++ b/tests/functional/openlp_plugins/songs/test_openlyricsimport.py @@ -31,11 +31,18 @@ This module contains tests for the OpenLyrics song importer. """ import os +import json from unittest import TestCase +from lxml import etree, objectify from tests.functional import MagicMock, patch +from tests.helpers.testmixin import TestMixin from openlp.plugins.songs.lib.importers.openlyrics import OpenLyricsImport from openlp.plugins.songs.lib.importers.songimport import SongImport +from openlp.plugins.songs.lib.openlyricsxml import OpenLyrics +from openlp.core.common import Registry, Settings +from openlp.core.lib import FormattingTags + TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..', 'resources', 'openlyricssongs')) @@ -59,11 +66,33 @@ SONG_TEST_DATA = { } } +start_tags = [{"protected": False, "desc": "z", "start tag": "{z}", "end html": "", "temporary": False, + "end tag": "{/z}", "start html": "strong>"}] +result_tags = [{"temporary": False, "protected": False, "desc": "z", "start tag": "{z}", "start html": "strong>", + "end html": "", "end tag": "{/z}"}, + {"temporary": False, "end tag": "{/c}", "desc": "c", "start tag": "{c}", + "start html": "", "end html": "", + "protected": False}] -class TestOpenLyricsImport(TestCase): + +class TestOpenLyricsImport(TestCase, TestMixin): """ Test the functions in the :mod:`openlyricsimport` module. """ + def setUp(self): + """ + Create the registry + """ + self.get_application() + Registry.create() + self.build_settings() + + def tearDown(self): + """ + Cleanup + """ + self.destroy_settings() + def create_importer_test(self): """ Test creating an instance of the OpenLyrics file importer @@ -97,3 +126,24 @@ class TestOpenLyricsImport(TestCase): # THEN: The xml_to_song() method should have been called self.assertTrue(importer.open_lyrics.xml_to_song.called) + + def process_formatting_tags_test(self): + """ + Test that _process_formatting_tags works + """ + # GIVEN: A OpenLyric XML with formatting tags and a mocked out manager + mocked_manager = MagicMock() + Settings().setValue('formattingTags/html_tags', json.dumps(start_tags)) + ol = OpenLyrics(mocked_manager) + parser = etree.XMLParser(remove_blank_text=True) + parsed_file = etree.parse(open(os.path.join(TEST_PATH, 'duchu-tags.xml'), 'rb'), parser) + xml = etree.tostring(parsed_file).decode() + song_xml = objectify.fromstring(xml) + + # WHEN: processing the formatting tags + ol._process_formatting_tags(song_xml, False) + + # THEN: New tags should have been saved + self.assertListEqual(json.loads(json.dumps(result_tags)), + json.loads(str(Settings().value('formattingTags/html_tags'))), + 'The formatting tags should contain both the old and the new') diff --git a/tests/functional/openlp_plugins/songs/test_opensongimport.py b/tests/functional/openlp_plugins/songs/test_opensongimport.py index 07b275f98..09d9eb61f 100644 --- a/tests/functional/openlp_plugins/songs/test_opensongimport.py +++ b/tests/functional/openlp_plugins/songs/test_opensongimport.py @@ -35,6 +35,7 @@ from unittest import TestCase from tests.helpers.songfileimport import SongImportTestHelper from openlp.plugins.songs.lib.importers.opensong import OpenSongImport +from openlp.core.common import Registry from tests.functional import patch, MagicMock TEST_PATH = os.path.abspath( @@ -64,6 +65,12 @@ class TestOpenSongImport(TestCase): """ Test the functions in the :mod:`opensongimport` module. """ + def setUp(self): + """ + Create the registry + """ + Registry.create() + def create_importer_test(self): """ Test creating an instance of the OpenSong file importer diff --git a/tests/functional/openlp_plugins/songs/test_powerpraiseimport.py b/tests/functional/openlp_plugins/songs/test_powerpraiseimport.py index e6a2a5194..eac51f6da 100644 --- a/tests/functional/openlp_plugins/songs/test_powerpraiseimport.py +++ b/tests/functional/openlp_plugins/songs/test_powerpraiseimport.py @@ -34,6 +34,7 @@ ProPresenter song files into the current installation database. import os from tests.helpers.songfileimport import SongImportTestHelper +from openlp.core.common import Registry TEST_PATH = os.path.abspath( os.path.join(os.path.dirname(__file__), '..', '..', '..', 'resources', 'powerpraisesongs')) diff --git a/tests/functional/openlp_plugins/songs/test_songbeamerimport.py b/tests/functional/openlp_plugins/songs/test_songbeamerimport.py index 3d872ae65..493d8fd2a 100644 --- a/tests/functional/openlp_plugins/songs/test_songbeamerimport.py +++ b/tests/functional/openlp_plugins/songs/test_songbeamerimport.py @@ -36,6 +36,7 @@ from unittest import TestCase from tests.functional import MagicMock, patch from openlp.plugins.songs.lib.importers.songbeamer import SongBeamerImport from openlp.plugins.songs.lib import VerseType +from openlp.core.common import Registry TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..', 'resources', 'songbeamersongs')) @@ -59,6 +60,12 @@ class TestSongBeamerImport(TestCase): """ Test the functions in the :mod:`songbeamerimport` module. """ + def setUp(self): + """ + Create the registry + """ + Registry.create() + def create_importer_test(self): """ Test creating an instance of the SongBeamer file importer diff --git a/tests/functional/openlp_plugins/songs/test_zionworximport.py b/tests/functional/openlp_plugins/songs/test_zionworximport.py index faedc7005..2d2bae4d0 100644 --- a/tests/functional/openlp_plugins/songs/test_zionworximport.py +++ b/tests/functional/openlp_plugins/songs/test_zionworximport.py @@ -35,12 +35,19 @@ from unittest import TestCase from tests.functional import MagicMock, patch from openlp.plugins.songs.lib.importers.zionworx import ZionWorxImport from openlp.plugins.songs.lib.importers.songimport import SongImport +from openlp.core.common import Registry class TestZionWorxImport(TestCase): """ Test the functions in the :mod:`zionworximport` module. """ + def setUp(self): + """ + Create the registry + """ + Registry.create() + def create_importer_test(self): """ Test creating an instance of the ZionWorx file importer diff --git a/tests/helpers/songfileimport.py b/tests/helpers/songfileimport.py index 01bfafdd8..7a911dea8 100644 --- a/tests/helpers/songfileimport.py +++ b/tests/helpers/songfileimport.py @@ -34,6 +34,8 @@ import json import logging from unittest import TestCase +from openlp.plugins.songs.lib.importers.opensong import OpenSongImport +from openlp.core.common import Registry from tests.functional import patch, MagicMock, call log = logging.getLogger(__name__) @@ -53,6 +55,7 @@ class SongImportTestHelper(TestCase): """ Patch and set up the mocks required. """ + Registry.create() self.add_copyright_patcher = patch('openlp.plugins.songs.lib.importers.%s.%s.add_copyright' % (self.importer_module_name, self.importer_class_name)) self.add_verse_patcher = patch('openlp.plugins.songs.lib.importers.%s.%s.add_verse' % diff --git a/tests/resources/openlyricssongs/duchu-tags.xml b/tests/resources/openlyricssongs/duchu-tags.xml new file mode 100644 index 000000000..e082ce5fc --- /dev/null +++ b/tests/resources/openlyricssongs/duchu-tags.xml @@ -0,0 +1,27 @@ + + + + + Duchu svätý volám príď <akordy> + + + Author Unknown + + + + + + <span class="chord" style="display:none"><strong> + </strong></span> + + + + + + [D]Duchu svätý volám príď, [Ami]oheň mojej duši daj,
[G]Oheň môjmu telu daj, [D]rozpáľ ma.
+
+ + Všemoh[Ami]úci [G]Boh tu s nami [D]je,
neko[Ami]nečne [G]milostivý [D]je,
Uka[Ami]zuje [G]dobrotivú [D]tvár voči [Ami]t[G]ým,
ktorí milovať ho [D]chcú.
+
+
+
\ No newline at end of file