forked from openlp/openlp
Merging to trunk on 3.4.16
This commit is contained in:
commit
447f7a9ead
@ -36,8 +36,8 @@ import shutil
|
|||||||
import time
|
import time
|
||||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||||
|
|
||||||
from openlp.core.common import Registry, OpenLPMixin, AppLocation, Settings, UiStrings, check_directory_exists, \
|
from openlp.core.common import Registry, OpenLPMixin, AppLocation, LanguageManager, Settings, UiStrings, \
|
||||||
is_macosx, is_win, translate
|
check_directory_exists, is_macosx, is_win, translate
|
||||||
from openlp.core.lib import ScreenList
|
from openlp.core.lib import ScreenList
|
||||||
from openlp.core.resources import qInitResources
|
from openlp.core.resources import qInitResources
|
||||||
from openlp.core.ui.mainwindow import MainWindow
|
from openlp.core.ui.mainwindow import MainWindow
|
||||||
@ -45,7 +45,7 @@ from openlp.core.ui.firsttimelanguageform import FirstTimeLanguageForm
|
|||||||
from openlp.core.ui.firsttimeform import FirstTimeForm
|
from openlp.core.ui.firsttimeform import FirstTimeForm
|
||||||
from openlp.core.ui.exceptionform import ExceptionForm
|
from openlp.core.ui.exceptionform import ExceptionForm
|
||||||
from openlp.core.ui import SplashScreen
|
from openlp.core.ui import SplashScreen
|
||||||
from openlp.core.utils import LanguageManager, VersionThread, get_application_version
|
from openlp.core.utils import VersionThread, get_application_version
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['OpenLP', 'main']
|
__all__ = ['OpenLP', 'main']
|
||||||
|
@ -242,3 +242,5 @@ from .uistrings import UiStrings
|
|||||||
from .settings import Settings
|
from .settings import Settings
|
||||||
from .applocation import AppLocation
|
from .applocation import AppLocation
|
||||||
from .historycombobox import HistoryComboBox
|
from .historycombobox import HistoryComboBox
|
||||||
|
from .actions import ActionList
|
||||||
|
from .languagemanager import LanguageManager
|
||||||
|
@ -131,6 +131,7 @@ class Settings(QtCore.QSettings):
|
|||||||
'advanced/save current plugin': False,
|
'advanced/save current plugin': False,
|
||||||
'advanced/slide limits': SlideLimits.End,
|
'advanced/slide limits': SlideLimits.End,
|
||||||
'advanced/single click preview': False,
|
'advanced/single click preview': False,
|
||||||
|
'advanced/single click service preview': False,
|
||||||
'advanced/x11 bypass wm': X11_BYPASS_DEFAULT,
|
'advanced/x11 bypass wm': X11_BYPASS_DEFAULT,
|
||||||
'advanced/search as type': True,
|
'advanced/search as type': True,
|
||||||
'crashreport/last directory': '',
|
'crashreport/last directory': '',
|
||||||
|
@ -27,8 +27,8 @@ import logging
|
|||||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||||
|
|
||||||
from openlp.core.common import Registry, UiStrings, translate, is_macosx
|
from openlp.core.common import Registry, UiStrings, translate, is_macosx
|
||||||
|
from openlp.core.common.actions import ActionList
|
||||||
from openlp.core.lib import build_icon
|
from openlp.core.lib import build_icon
|
||||||
from openlp.core.utils.actions import ActionList
|
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
@ -77,6 +77,9 @@ class AdvancedTab(SettingsTab):
|
|||||||
self.single_click_preview_check_box = QtWidgets.QCheckBox(self.ui_group_box)
|
self.single_click_preview_check_box = QtWidgets.QCheckBox(self.ui_group_box)
|
||||||
self.single_click_preview_check_box.setObjectName('single_click_preview_check_box')
|
self.single_click_preview_check_box.setObjectName('single_click_preview_check_box')
|
||||||
self.ui_layout.addRow(self.single_click_preview_check_box)
|
self.ui_layout.addRow(self.single_click_preview_check_box)
|
||||||
|
self.single_click_service_preview_check_box = QtWidgets.QCheckBox(self.ui_group_box)
|
||||||
|
self.single_click_service_preview_check_box.setObjectName('single_click_service_preview_check_box')
|
||||||
|
self.ui_layout.addRow(self.single_click_service_preview_check_box)
|
||||||
self.expand_service_item_check_box = QtWidgets.QCheckBox(self.ui_group_box)
|
self.expand_service_item_check_box = QtWidgets.QCheckBox(self.ui_group_box)
|
||||||
self.expand_service_item_check_box.setObjectName('expand_service_item_check_box')
|
self.expand_service_item_check_box.setObjectName('expand_service_item_check_box')
|
||||||
self.ui_layout.addRow(self.expand_service_item_check_box)
|
self.ui_layout.addRow(self.expand_service_item_check_box)
|
||||||
@ -270,6 +273,8 @@ class AdvancedTab(SettingsTab):
|
|||||||
'Double-click to send items straight to live'))
|
'Double-click to send items straight to live'))
|
||||||
self.single_click_preview_check_box.setText(translate('OpenLP.AdvancedTab',
|
self.single_click_preview_check_box.setText(translate('OpenLP.AdvancedTab',
|
||||||
'Preview items when clicked in Media Manager'))
|
'Preview items when clicked in Media Manager'))
|
||||||
|
self.single_click_service_preview_check_box.setText(translate('OpenLP.AdvancedTab',
|
||||||
|
'Preview items when clicked in Service Manager'))
|
||||||
self.expand_service_item_check_box.setText(translate('OpenLP.AdvancedTab',
|
self.expand_service_item_check_box.setText(translate('OpenLP.AdvancedTab',
|
||||||
'Expand new service items on creation'))
|
'Expand new service items on creation'))
|
||||||
self.enable_auto_close_check_box.setText(translate('OpenLP.AdvancedTab',
|
self.enable_auto_close_check_box.setText(translate('OpenLP.AdvancedTab',
|
||||||
@ -339,6 +344,7 @@ class AdvancedTab(SettingsTab):
|
|||||||
self.media_plugin_check_box.setChecked(settings.value('save current plugin'))
|
self.media_plugin_check_box.setChecked(settings.value('save current plugin'))
|
||||||
self.double_click_live_check_box.setChecked(settings.value('double click live'))
|
self.double_click_live_check_box.setChecked(settings.value('double click live'))
|
||||||
self.single_click_preview_check_box.setChecked(settings.value('single click preview'))
|
self.single_click_preview_check_box.setChecked(settings.value('single click preview'))
|
||||||
|
self.single_click_service_preview_check_box.setChecked(settings.value('single click service preview'))
|
||||||
self.expand_service_item_check_box.setChecked(settings.value('expand service item'))
|
self.expand_service_item_check_box.setChecked(settings.value('expand service item'))
|
||||||
self.enable_auto_close_check_box.setChecked(settings.value('enable exit confirmation'))
|
self.enable_auto_close_check_box.setChecked(settings.value('enable exit confirmation'))
|
||||||
self.hide_mouse_check_box.setChecked(settings.value('hide mouse'))
|
self.hide_mouse_check_box.setChecked(settings.value('hide mouse'))
|
||||||
@ -420,6 +426,7 @@ class AdvancedTab(SettingsTab):
|
|||||||
settings.setValue('save current plugin', self.media_plugin_check_box.isChecked())
|
settings.setValue('save current plugin', self.media_plugin_check_box.isChecked())
|
||||||
settings.setValue('double click live', self.double_click_live_check_box.isChecked())
|
settings.setValue('double click live', self.double_click_live_check_box.isChecked())
|
||||||
settings.setValue('single click preview', self.single_click_preview_check_box.isChecked())
|
settings.setValue('single click preview', self.single_click_preview_check_box.isChecked())
|
||||||
|
settings.setValue('single click service preview', self.single_click_service_preview_check_box.isChecked())
|
||||||
settings.setValue('expand service item', self.expand_service_item_check_box.isChecked())
|
settings.setValue('expand service item', self.expand_service_item_check_box.isChecked())
|
||||||
settings.setValue('enable exit confirmation', self.enable_auto_close_check_box.isChecked())
|
settings.setValue('enable exit confirmation', self.enable_auto_close_check_box.isChecked())
|
||||||
settings.setValue('hide mouse', self.hide_mouse_check_box.isChecked())
|
settings.setValue('hide mouse', self.hide_mouse_check_box.isChecked())
|
||||||
|
@ -180,11 +180,13 @@ class ExceptionForm(QtWidgets.QDialog, Ui_ExceptionDialog, RegistryProperties):
|
|||||||
if ':' in line:
|
if ':' in line:
|
||||||
exception = line.split('\n')[-1].split(':')[0]
|
exception = line.split('\n')[-1].split(':')[0]
|
||||||
subject = 'Bug report: %s in %s' % (exception, source)
|
subject = 'Bug report: %s in %s' % (exception, source)
|
||||||
mail_to_url = QtCore.QUrlQuery('mailto:bugs@openlp.org')
|
mail_urlquery = QtCore.QUrlQuery()
|
||||||
mail_to_url.addQueryItem('subject', subject)
|
mail_urlquery.addQueryItem('subject', subject)
|
||||||
mail_to_url.addQueryItem('body', self.report_text % content)
|
mail_urlquery.addQueryItem('body', self.report_text % content)
|
||||||
if self.file_attachment:
|
if self.file_attachment:
|
||||||
mail_to_url.addQueryItem('attach', self.file_attachment)
|
mail_urlquery.addQueryItem('attach', self.file_attachment)
|
||||||
|
mail_to_url = QtCore.QUrl('mailto:bugs@openlp.org')
|
||||||
|
mail_to_url.setQuery(mail_urlquery)
|
||||||
QtGui.QDesktopServices.openUrl(mail_to_url)
|
QtGui.QDesktopServices.openUrl(mail_to_url)
|
||||||
|
|
||||||
def on_description_updated(self):
|
def on_description_updated(self):
|
||||||
|
@ -25,7 +25,7 @@ The language selection dialog.
|
|||||||
from PyQt5 import QtCore, QtWidgets
|
from PyQt5 import QtCore, QtWidgets
|
||||||
|
|
||||||
from openlp.core.lib.ui import create_action
|
from openlp.core.lib.ui import create_action
|
||||||
from openlp.core.utils import LanguageManager
|
from openlp.core.common import LanguageManager
|
||||||
from .firsttimelanguagedialog import Ui_FirstTimeLanguageDialog
|
from .firsttimelanguagedialog import Ui_FirstTimeLanguageDialog
|
||||||
|
|
||||||
|
|
||||||
|
@ -24,30 +24,29 @@ This is the main window, where all the action happens.
|
|||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import sys
|
|
||||||
import shutil
|
import shutil
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
from datetime import datetime
|
||||||
from distutils import dir_util
|
from distutils import dir_util
|
||||||
from distutils.errors import DistutilsFileError
|
from distutils.errors import DistutilsFileError
|
||||||
from tempfile import gettempdir
|
from tempfile import gettempdir
|
||||||
import time
|
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||||
|
|
||||||
from openlp.core.common import Registry, RegistryProperties, AppLocation, Settings, check_directory_exists, translate, \
|
from openlp.core.common import Registry, RegistryProperties, AppLocation, LanguageManager, Settings, \
|
||||||
is_win, is_macosx
|
check_directory_exists, translate, is_win, is_macosx
|
||||||
|
from openlp.core.common.actions import ActionList, CategoryOrder
|
||||||
from openlp.core.lib import Renderer, OpenLPDockWidget, PluginManager, ImageManager, PluginStatus, ScreenList, \
|
from openlp.core.lib import Renderer, OpenLPDockWidget, PluginManager, ImageManager, PluginStatus, ScreenList, \
|
||||||
build_icon
|
build_icon
|
||||||
from openlp.core.lib.ui import UiStrings, create_action
|
from openlp.core.lib.ui import UiStrings, create_action
|
||||||
from openlp.core.ui import AboutForm, SettingsForm, ServiceManager, ThemeManager, LiveController, PluginForm, \
|
from openlp.core.ui import AboutForm, SettingsForm, ServiceManager, ThemeManager, LiveController, PluginForm, \
|
||||||
MediaDockManager, ShortcutListForm, FormattingTagForm, PreviewController
|
MediaDockManager, ShortcutListForm, FormattingTagForm, PreviewController
|
||||||
|
|
||||||
from openlp.core.ui.media import MediaController
|
|
||||||
from openlp.core.utils import LanguageManager, add_actions, get_application_version
|
|
||||||
from openlp.core.utils.actions import ActionList, CategoryOrder
|
|
||||||
from openlp.core.ui.firsttimeform import FirstTimeForm
|
from openlp.core.ui.firsttimeform import FirstTimeForm
|
||||||
from openlp.core.ui.projector.manager import ProjectorManager
|
from openlp.core.ui.media import MediaController
|
||||||
from openlp.core.ui.printserviceform import PrintServiceForm
|
from openlp.core.ui.printserviceform import PrintServiceForm
|
||||||
|
from openlp.core.ui.projector.manager import ProjectorManager
|
||||||
|
from openlp.core.utils import get_application_version, add_actions
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -83,60 +83,60 @@ class Ui_ProjectorManager(object):
|
|||||||
self.one_toolbar.add_toolbar_action('new_projector',
|
self.one_toolbar.add_toolbar_action('new_projector',
|
||||||
text=translate('OpenLP.ProjectorManager', 'Add Projector'),
|
text=translate('OpenLP.ProjectorManager', 'Add Projector'),
|
||||||
icon=':/projector/projector_new.png',
|
icon=':/projector/projector_new.png',
|
||||||
tooltip=translate('OpenLP.ProjectorManager', 'Add a new projector'),
|
tooltip=translate('OpenLP.ProjectorManager', 'Add a new projector.'),
|
||||||
triggers=self.on_add_projector)
|
triggers=self.on_add_projector)
|
||||||
# Show edit/delete when projector not connected
|
# Show edit/delete when projector not connected
|
||||||
self.one_toolbar.add_toolbar_action('edit_projector',
|
self.one_toolbar.add_toolbar_action('edit_projector',
|
||||||
text=translate('OpenLP.ProjectorManager', 'Edit Projector'),
|
text=translate('OpenLP.ProjectorManager', 'Edit Projector'),
|
||||||
icon=':/general/general_edit.png',
|
icon=':/general/general_edit.png',
|
||||||
tooltip=translate('OpenLP.ProjectorManager', 'Edit selected projector'),
|
tooltip=translate('OpenLP.ProjectorManager', 'Edit selected projector.'),
|
||||||
triggers=self.on_edit_projector)
|
triggers=self.on_edit_projector)
|
||||||
self.one_toolbar.add_toolbar_action('delete_projector',
|
self.one_toolbar.add_toolbar_action('delete_projector',
|
||||||
text=translate('OpenLP.ProjectorManager', 'Delete Projector'),
|
text=translate('OpenLP.ProjectorManager', 'Delete Projector'),
|
||||||
icon=':/general/general_delete.png',
|
icon=':/general/general_delete.png',
|
||||||
tooltip=translate('OpenLP.ProjectorManager', 'Delete selected projector'),
|
tooltip=translate('OpenLP.ProjectorManager', 'Delete selected projector.'),
|
||||||
triggers=self.on_delete_projector)
|
triggers=self.on_delete_projector)
|
||||||
# Show source/view when projector connected
|
# Show source/view when projector connected
|
||||||
self.one_toolbar.add_toolbar_action('source_view_projector',
|
self.one_toolbar.add_toolbar_action('source_view_projector',
|
||||||
text=translate('OpenLP.ProjectorManager', 'Select Input Source'),
|
text=translate('OpenLP.ProjectorManager', 'Select Input Source'),
|
||||||
icon=':/projector/projector_hdmi.png',
|
icon=':/projector/projector_hdmi.png',
|
||||||
tooltip=translate('OpenLP.ProjectorManager',
|
tooltip=translate('OpenLP.ProjectorManager',
|
||||||
'Choose input source on selected projector'),
|
'Choose input source on selected projector.'),
|
||||||
triggers=self.on_select_input)
|
triggers=self.on_select_input)
|
||||||
self.one_toolbar.add_toolbar_action('view_projector',
|
self.one_toolbar.add_toolbar_action('view_projector',
|
||||||
text=translate('OpenLP.ProjectorManager', 'View Projector'),
|
text=translate('OpenLP.ProjectorManager', 'View Projector'),
|
||||||
icon=':/system/system_about.png',
|
icon=':/system/system_about.png',
|
||||||
tooltip=translate('OpenLP.ProjectorManager',
|
tooltip=translate('OpenLP.ProjectorManager',
|
||||||
'View selected projector information'),
|
'View selected projector information.'),
|
||||||
triggers=self.on_status_projector)
|
triggers=self.on_status_projector)
|
||||||
self.one_toolbar.addSeparator()
|
self.one_toolbar.addSeparator()
|
||||||
self.one_toolbar.add_toolbar_action('connect_projector',
|
self.one_toolbar.add_toolbar_action('connect_projector',
|
||||||
text=translate('OpenLP.ProjectorManager',
|
text=translate('OpenLP.ProjectorManager',
|
||||||
'Connect to selected projector'),
|
'Connect to selected projector.'),
|
||||||
icon=':/projector/projector_connect.png',
|
icon=':/projector/projector_connect.png',
|
||||||
tooltip=translate('OpenLP.ProjectorManager',
|
tooltip=translate('OpenLP.ProjectorManager',
|
||||||
'Connect to selected projector'),
|
'Connect to selected projector.'),
|
||||||
triggers=self.on_connect_projector)
|
triggers=self.on_connect_projector)
|
||||||
self.one_toolbar.add_toolbar_action('connect_projector_multiple',
|
self.one_toolbar.add_toolbar_action('connect_projector_multiple',
|
||||||
text=translate('OpenLP.ProjectorManager',
|
text=translate('OpenLP.ProjectorManager',
|
||||||
'Connect to selected projectors'),
|
'Connect to selected projectors'),
|
||||||
icon=':/projector/projector_connect_tiled.png',
|
icon=':/projector/projector_connect_tiled.png',
|
||||||
tooltip=translate('OpenLP.ProjectorManager',
|
tooltip=translate('OpenLP.ProjectorManager',
|
||||||
'Connect to selected projector'),
|
'Connect to selected projectors.'),
|
||||||
triggers=self.on_connect_projector)
|
triggers=self.on_connect_projector)
|
||||||
self.one_toolbar.add_toolbar_action('disconnect_projector',
|
self.one_toolbar.add_toolbar_action('disconnect_projector',
|
||||||
text=translate('OpenLP.ProjectorManager',
|
text=translate('OpenLP.ProjectorManager',
|
||||||
'Disconnect from selected projectors'),
|
'Disconnect from selected projectors'),
|
||||||
icon=':/projector/projector_disconnect.png',
|
icon=':/projector/projector_disconnect.png',
|
||||||
tooltip=translate('OpenLP.ProjectorManager',
|
tooltip=translate('OpenLP.ProjectorManager',
|
||||||
'Disconnect from selected projector'),
|
'Disconnect from selected projector.'),
|
||||||
triggers=self.on_disconnect_projector)
|
triggers=self.on_disconnect_projector)
|
||||||
self.one_toolbar.add_toolbar_action('disconnect_projector_multiple',
|
self.one_toolbar.add_toolbar_action('disconnect_projector_multiple',
|
||||||
text=translate('OpenLP.ProjectorManager',
|
text=translate('OpenLP.ProjectorManager',
|
||||||
'Disconnect from selected projector'),
|
'Disconnect from selected projector'),
|
||||||
icon=':/projector/projector_disconnect_tiled.png',
|
icon=':/projector/projector_disconnect_tiled.png',
|
||||||
tooltip=translate('OpenLP.ProjectorManager',
|
tooltip=translate('OpenLP.ProjectorManager',
|
||||||
'Disconnect from selected projector'),
|
'Disconnect from selected projectors.'),
|
||||||
triggers=self.on_disconnect_projector)
|
triggers=self.on_disconnect_projector)
|
||||||
self.one_toolbar.addSeparator()
|
self.one_toolbar.addSeparator()
|
||||||
self.one_toolbar.add_toolbar_action('poweron_projector',
|
self.one_toolbar.add_toolbar_action('poweron_projector',
|
||||||
@ -144,26 +144,26 @@ class Ui_ProjectorManager(object):
|
|||||||
'Power on selected projector'),
|
'Power on selected projector'),
|
||||||
icon=':/projector/projector_power_on.png',
|
icon=':/projector/projector_power_on.png',
|
||||||
tooltip=translate('OpenLP.ProjectorManager',
|
tooltip=translate('OpenLP.ProjectorManager',
|
||||||
'Power on selected projector'),
|
'Power on selected projector.'),
|
||||||
triggers=self.on_poweron_projector)
|
triggers=self.on_poweron_projector)
|
||||||
self.one_toolbar.add_toolbar_action('poweron_projector_multiple',
|
self.one_toolbar.add_toolbar_action('poweron_projector_multiple',
|
||||||
text=translate('OpenLP.ProjectorManager',
|
text=translate('OpenLP.ProjectorManager',
|
||||||
'Power on selected projector'),
|
'Power on selected projector'),
|
||||||
icon=':/projector/projector_power_on_tiled.png',
|
icon=':/projector/projector_power_on_tiled.png',
|
||||||
tooltip=translate('OpenLP.ProjectorManager',
|
tooltip=translate('OpenLP.ProjectorManager',
|
||||||
'Power on selected projector'),
|
'Power on selected projectors.'),
|
||||||
triggers=self.on_poweron_projector)
|
triggers=self.on_poweron_projector)
|
||||||
self.one_toolbar.add_toolbar_action('poweroff_projector',
|
self.one_toolbar.add_toolbar_action('poweroff_projector',
|
||||||
text=translate('OpenLP.ProjectorManager', 'Standby selected projector'),
|
text=translate('OpenLP.ProjectorManager', 'Standby selected projector'),
|
||||||
icon=':/projector/projector_power_off.png',
|
icon=':/projector/projector_power_off.png',
|
||||||
tooltip=translate('OpenLP.ProjectorManager',
|
tooltip=translate('OpenLP.ProjectorManager',
|
||||||
'Put selected projector in standby'),
|
'Put selected projector in standby.'),
|
||||||
triggers=self.on_poweroff_projector)
|
triggers=self.on_poweroff_projector)
|
||||||
self.one_toolbar.add_toolbar_action('poweroff_projector_multiple',
|
self.one_toolbar.add_toolbar_action('poweroff_projector_multiple',
|
||||||
text=translate('OpenLP.ProjectorManager', 'Standby selected projector'),
|
text=translate('OpenLP.ProjectorManager', 'Standby selected projector'),
|
||||||
icon=':/projector/projector_power_off_tiled.png',
|
icon=':/projector/projector_power_off_tiled.png',
|
||||||
tooltip=translate('OpenLP.ProjectorManager',
|
tooltip=translate('OpenLP.ProjectorManager',
|
||||||
'Put selected projector in standby'),
|
'Put selected projectors in standby.'),
|
||||||
triggers=self.on_poweroff_projector)
|
triggers=self.on_poweroff_projector)
|
||||||
self.one_toolbar.addSeparator()
|
self.one_toolbar.addSeparator()
|
||||||
self.one_toolbar.add_toolbar_action('blank_projector',
|
self.one_toolbar.add_toolbar_action('blank_projector',
|
||||||
@ -175,24 +175,24 @@ class Ui_ProjectorManager(object):
|
|||||||
triggers=self.on_blank_projector)
|
triggers=self.on_blank_projector)
|
||||||
self.one_toolbar.add_toolbar_action('blank_projector_multiple',
|
self.one_toolbar.add_toolbar_action('blank_projector_multiple',
|
||||||
text=translate('OpenLP.ProjectorManager',
|
text=translate('OpenLP.ProjectorManager',
|
||||||
'Blank selected projector screen'),
|
'Blank selected projectors screen'),
|
||||||
icon=':/projector/projector_blank_tiled.png',
|
icon=':/projector/projector_blank_tiled.png',
|
||||||
tooltip=translate('OpenLP.ProjectorManager',
|
tooltip=translate('OpenLP.ProjectorManager',
|
||||||
'Blank selected projector screen'),
|
'Blank selected projectors screen.'),
|
||||||
triggers=self.on_blank_projector)
|
triggers=self.on_blank_projector)
|
||||||
self.one_toolbar.add_toolbar_action('show_projector',
|
self.one_toolbar.add_toolbar_action('show_projector',
|
||||||
text=translate('OpenLP.ProjectorManager',
|
text=translate('OpenLP.ProjectorManager',
|
||||||
'Show selected projector screen'),
|
'Show selected projector screen'),
|
||||||
icon=':/projector/projector_show.png',
|
icon=':/projector/projector_show.png',
|
||||||
tooltip=translate('OpenLP.ProjectorManager',
|
tooltip=translate('OpenLP.ProjectorManager',
|
||||||
'Show selected projector screen'),
|
'Show selected projector screen.'),
|
||||||
triggers=self.on_show_projector)
|
triggers=self.on_show_projector)
|
||||||
self.one_toolbar.add_toolbar_action('show_projector_multiple',
|
self.one_toolbar.add_toolbar_action('show_projector_multiple',
|
||||||
text=translate('OpenLP.ProjectorManager',
|
text=translate('OpenLP.ProjectorManager',
|
||||||
'Show selected projector screen'),
|
'Show selected projector screen'),
|
||||||
icon=':/projector/projector_show_tiled.png',
|
icon=':/projector/projector_show_tiled.png',
|
||||||
tooltip=translate('OpenLP.ProjectorManager',
|
tooltip=translate('OpenLP.ProjectorManager',
|
||||||
'Show selected projector screen'),
|
'Show selected projectors screen.'),
|
||||||
triggers=self.on_show_projector)
|
triggers=self.on_show_projector)
|
||||||
self.layout.addWidget(self.one_toolbar)
|
self.layout.addWidget(self.one_toolbar)
|
||||||
self.projector_one_widget = QtWidgets.QWidgetAction(self.one_toolbar)
|
self.projector_one_widget = QtWidgets.QWidgetAction(self.one_toolbar)
|
||||||
|
@ -23,23 +23,22 @@
|
|||||||
The service manager sets up, loads, saves and manages services.
|
The service manager sets up, loads, saves and manages services.
|
||||||
"""
|
"""
|
||||||
import html
|
import html
|
||||||
|
import json
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import zipfile
|
import zipfile
|
||||||
import json
|
|
||||||
from tempfile import mkstemp
|
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
from tempfile import mkstemp
|
||||||
|
|
||||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||||
|
|
||||||
from openlp.core.common import Registry, RegistryProperties, AppLocation, Settings, ThemeLevel, OpenLPMixin, \
|
from openlp.core.common import Registry, RegistryProperties, AppLocation, Settings, ThemeLevel, OpenLPMixin, \
|
||||||
RegistryMixin, check_directory_exists, UiStrings, translate
|
RegistryMixin, check_directory_exists, UiStrings, translate
|
||||||
|
from openlp.core.common.actions import ActionList, CategoryOrder
|
||||||
from openlp.core.lib import OpenLPToolbar, ServiceItem, ItemCapabilities, PluginStatus, build_icon
|
from openlp.core.lib import OpenLPToolbar, ServiceItem, ItemCapabilities, PluginStatus, build_icon
|
||||||
from openlp.core.lib.ui import critical_error_message_box, create_widget_action, find_and_set_in_combo_box
|
from openlp.core.lib.ui import critical_error_message_box, create_widget_action, find_and_set_in_combo_box
|
||||||
from openlp.core.ui import ServiceNoteForm, ServiceItemEditForm, StartTimeForm
|
from openlp.core.ui import ServiceNoteForm, ServiceItemEditForm, StartTimeForm
|
||||||
from openlp.core.ui.printserviceform import PrintServiceForm
|
|
||||||
from openlp.core.utils import delete_file, split_filename, format_time
|
from openlp.core.utils import delete_file, split_filename, format_time
|
||||||
from openlp.core.utils.actions import ActionList, CategoryOrder
|
|
||||||
|
|
||||||
|
|
||||||
class ServiceManagerList(QtWidgets.QTreeWidget):
|
class ServiceManagerList(QtWidgets.QTreeWidget):
|
||||||
@ -211,7 +210,8 @@ class Ui_ServiceManager(object):
|
|||||||
self.layout.addWidget(self.order_toolbar)
|
self.layout.addWidget(self.order_toolbar)
|
||||||
# Connect up our signals and slots
|
# Connect up our signals and slots
|
||||||
self.theme_combo_box.activated.connect(self.on_theme_combo_box_selected)
|
self.theme_combo_box.activated.connect(self.on_theme_combo_box_selected)
|
||||||
self.service_manager_list.doubleClicked.connect(self.on_make_live)
|
self.service_manager_list.doubleClicked.connect(self.on_double_click_live)
|
||||||
|
self.service_manager_list.clicked.connect(self.on_single_click_preview)
|
||||||
self.service_manager_list.itemCollapsed.connect(self.collapsed)
|
self.service_manager_list.itemCollapsed.connect(self.collapsed)
|
||||||
self.service_manager_list.itemExpanded.connect(self.expanded)
|
self.service_manager_list.itemExpanded.connect(self.expanded)
|
||||||
# Last little bits of setting up
|
# Last little bits of setting up
|
||||||
@ -319,6 +319,7 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ServiceMa
|
|||||||
self._modified = False
|
self._modified = False
|
||||||
self._file_name = ''
|
self._file_name = ''
|
||||||
self.service_has_all_original_files = True
|
self.service_has_all_original_files = True
|
||||||
|
self.list_double_clicked = False
|
||||||
|
|
||||||
def bootstrap_initialise(self):
|
def bootstrap_initialise(self):
|
||||||
"""
|
"""
|
||||||
@ -1454,13 +1455,38 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ServiceMa
|
|||||||
else:
|
else:
|
||||||
return self.service_items[item]['service_item']
|
return self.service_items[item]['service_item']
|
||||||
|
|
||||||
def on_make_live(self, field=None):
|
def on_double_click_live(self, field=None):
|
||||||
"""
|
"""
|
||||||
Send the current item to the Live slide controller but triggered by a tablewidget click event.
|
Send the current item to the Live slide controller but triggered by a tablewidget click event.
|
||||||
:param field:
|
:param field:
|
||||||
"""
|
"""
|
||||||
|
self.list_double_clicked = True
|
||||||
self.make_live()
|
self.make_live()
|
||||||
|
|
||||||
|
def on_single_click_preview(self, field=None):
|
||||||
|
"""
|
||||||
|
If single click previewing is enabled, and triggered by a tablewidget click event,
|
||||||
|
start a timeout to verify a double-click hasn't triggered.
|
||||||
|
:param field:
|
||||||
|
"""
|
||||||
|
if Settings().value('advanced/single click service preview'):
|
||||||
|
if not self.list_double_clicked:
|
||||||
|
# If a double click has not registered start a timer, otherwise wait for the existing timer to finish.
|
||||||
|
QtCore.QTimer.singleShot(QtWidgets.QApplication.instance().doubleClickInterval(),
|
||||||
|
self.on_single_click_preview_timeout)
|
||||||
|
|
||||||
|
def on_single_click_preview_timeout(self):
|
||||||
|
"""
|
||||||
|
If a single click ok, but double click not triggered, send the current item to the Preview slide controller.
|
||||||
|
:param field:
|
||||||
|
"""
|
||||||
|
if self.list_double_clicked:
|
||||||
|
# If a double click has registered, clear it.
|
||||||
|
self.list_double_clicked = False
|
||||||
|
else:
|
||||||
|
# Otherwise preview the item.
|
||||||
|
self.make_preview()
|
||||||
|
|
||||||
def make_live(self, row=-1):
|
def make_live(self, row=-1):
|
||||||
"""
|
"""
|
||||||
Send the current item to the Live slide controller
|
Send the current item to the Live slide controller
|
||||||
|
@ -27,7 +27,7 @@ import re
|
|||||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||||
|
|
||||||
from openlp.core.common import RegistryProperties, Settings, translate
|
from openlp.core.common import RegistryProperties, Settings, translate
|
||||||
from openlp.core.utils.actions import ActionList
|
from openlp.core.common.actions import ActionList
|
||||||
from .shortcutlistdialog import Ui_ShortcutListDialog
|
from .shortcutlistdialog import Ui_ShortcutListDialog
|
||||||
|
|
||||||
REMOVE_AMPERSAND = re.compile(r'&{1}')
|
REMOVE_AMPERSAND = re.compile(r'&{1}')
|
||||||
|
@ -23,20 +23,20 @@
|
|||||||
The :mod:`slidecontroller` module contains the most important part of OpenLP - the slide controller
|
The :mod:`slidecontroller` module contains the most important part of OpenLP - the slide controller
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
|
||||||
import copy
|
import copy
|
||||||
|
import os
|
||||||
from collections import deque
|
from collections import deque
|
||||||
from threading import Lock
|
from threading import Lock
|
||||||
|
|
||||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||||
|
|
||||||
from openlp.core.common import Registry, RegistryProperties, Settings, SlideLimits, UiStrings, translate, \
|
from openlp.core.common import Registry, RegistryProperties, Settings, SlideLimits, UiStrings, translate, \
|
||||||
RegistryMixin, OpenLPMixin, is_win
|
RegistryMixin, OpenLPMixin
|
||||||
|
from openlp.core.common.actions import ActionList, CategoryOrder
|
||||||
from openlp.core.lib import OpenLPToolbar, ItemCapabilities, ServiceItem, ImageSource, ServiceItemAction, \
|
from openlp.core.lib import OpenLPToolbar, ItemCapabilities, ServiceItem, ImageSource, ServiceItemAction, \
|
||||||
ScreenList, build_icon, build_html
|
ScreenList, build_icon, build_html
|
||||||
from openlp.core.ui import HideMode, MainDisplay, Display, DisplayControllerType
|
|
||||||
from openlp.core.lib.ui import create_action
|
from openlp.core.lib.ui import create_action
|
||||||
from openlp.core.utils.actions import ActionList, CategoryOrder
|
from openlp.core.ui import HideMode, MainDisplay, Display, DisplayControllerType
|
||||||
from openlp.core.ui.listpreviewwidget import ListPreviewWidget
|
from openlp.core.ui.listpreviewwidget import ListPreviewWidget
|
||||||
|
|
||||||
# Threshold which has to be trespassed to toggle.
|
# Threshold which has to be trespassed to toggle.
|
||||||
|
@ -22,29 +22,28 @@
|
|||||||
"""
|
"""
|
||||||
The :mod:`openlp.core.utils` module provides the utility libraries for OpenLP.
|
The :mod:`openlp.core.utils` module provides the utility libraries for OpenLP.
|
||||||
"""
|
"""
|
||||||
from datetime import datetime
|
|
||||||
from distutils.version import LooseVersion
|
|
||||||
from http.client import HTTPException
|
|
||||||
import logging
|
|
||||||
import locale
|
import locale
|
||||||
|
import logging
|
||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
import re
|
import re
|
||||||
import socket
|
import socket
|
||||||
import time
|
|
||||||
from shutil import which
|
|
||||||
from subprocess import Popen, PIPE
|
|
||||||
import sys
|
import sys
|
||||||
import urllib.request
|
import time
|
||||||
import urllib.error
|
import urllib.error
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
|
import urllib.request
|
||||||
|
from datetime import datetime
|
||||||
|
from distutils.version import LooseVersion
|
||||||
|
from http.client import HTTPException
|
||||||
from random import randint
|
from random import randint
|
||||||
|
from shutil import which
|
||||||
|
from subprocess import Popen, PIPE
|
||||||
|
|
||||||
from PyQt5 import QtGui, QtCore
|
from PyQt5 import QtGui, QtCore
|
||||||
|
|
||||||
from openlp.core.common import Registry, AppLocation, Settings, is_win, is_macosx
|
from openlp.core.common import Registry, AppLocation, Settings, is_win, is_macosx
|
||||||
|
|
||||||
|
|
||||||
if not is_win() and not is_macosx():
|
if not is_win() and not is_macosx():
|
||||||
try:
|
try:
|
||||||
from xdg import BaseDirectory
|
from xdg import BaseDirectory
|
||||||
@ -511,7 +510,7 @@ def get_locale_key(string):
|
|||||||
try:
|
try:
|
||||||
if ICU_COLLATOR is None:
|
if ICU_COLLATOR is None:
|
||||||
import icu
|
import icu
|
||||||
from .languagemanager import LanguageManager
|
from openlp.core.common.languagemanager import LanguageManager
|
||||||
language = LanguageManager.get_language()
|
language = LanguageManager.get_language()
|
||||||
icu_locale = icu.Locale(language)
|
icu_locale = icu.Locale(language)
|
||||||
ICU_COLLATOR = icu.Collator.createInstance(icu_locale)
|
ICU_COLLATOR = icu.Collator.createInstance(icu_locale)
|
||||||
@ -523,21 +522,18 @@ def get_locale_key(string):
|
|||||||
def get_natural_key(string):
|
def get_natural_key(string):
|
||||||
"""
|
"""
|
||||||
Generate a key for locale aware natural string sorting.
|
Generate a key for locale aware natural string sorting.
|
||||||
|
|
||||||
|
:param string: string to be sorted by
|
||||||
Returns a list of string compare keys and integers.
|
Returns a list of string compare keys and integers.
|
||||||
"""
|
"""
|
||||||
key = DIGITS_OR_NONDIGITS.findall(string)
|
key = DIGITS_OR_NONDIGITS.findall(string)
|
||||||
key = [int(part) if part.isdigit() else get_locale_key(part) for part in key]
|
key = [int(part) if part.isdigit() else get_locale_key(part) for part in key]
|
||||||
# Python 3 does not support comparison of different types anymore. So make sure, that we do not compare str
|
# Python 3 does not support comparison of different types anymore. So make sure, that we do not compare str
|
||||||
# and int.
|
# and int.
|
||||||
if string[0].isdigit():
|
if string and string[0].isdigit():
|
||||||
return [b''] + key
|
return [b''] + key
|
||||||
return key
|
return key
|
||||||
|
|
||||||
|
__all__ = ['get_application_version', 'check_latest_version',
|
||||||
from .languagemanager import LanguageManager
|
|
||||||
from .actions import ActionList
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['ActionList', 'LanguageManager', 'get_application_version', 'check_latest_version',
|
|
||||||
'add_actions', 'get_filesystem_encoding', 'get_web_page', 'get_uno_command', 'get_uno_instance',
|
'add_actions', 'get_filesystem_encoding', 'get_web_page', 'get_uno_command', 'get_uno_instance',
|
||||||
'delete_file', 'clean_filename', 'format_time', 'get_locale_key', 'get_natural_key']
|
'delete_file', 'clean_filename', 'format_time', 'get_locale_key', 'get_natural_key']
|
||||||
|
@ -24,17 +24,16 @@ import logging
|
|||||||
|
|
||||||
from PyQt5 import QtGui
|
from PyQt5 import QtGui
|
||||||
|
|
||||||
|
|
||||||
from openlp.core.common import Settings, translate
|
from openlp.core.common import Settings, translate
|
||||||
|
from openlp.core.common.actions import ActionList
|
||||||
from openlp.core.lib import Plugin, StringContent, build_icon
|
from openlp.core.lib import Plugin, StringContent, build_icon
|
||||||
from openlp.core.lib.db import Manager
|
from openlp.core.lib.db import Manager
|
||||||
from openlp.core.lib.ui import create_action, UiStrings
|
|
||||||
from openlp.core.lib.theme import VerticalType
|
from openlp.core.lib.theme import VerticalType
|
||||||
|
from openlp.core.lib.ui import create_action, UiStrings
|
||||||
from openlp.core.ui import AlertLocation
|
from openlp.core.ui import AlertLocation
|
||||||
from openlp.core.utils.actions import ActionList
|
from openlp.plugins.alerts.forms import AlertForm
|
||||||
from openlp.plugins.alerts.lib import AlertsManager, AlertsTab
|
from openlp.plugins.alerts.lib import AlertsManager, AlertsTab
|
||||||
from openlp.plugins.alerts.lib.db import init_schema
|
from openlp.plugins.alerts.lib.db import init_schema
|
||||||
from openlp.plugins.alerts.forms import AlertForm
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -24,13 +24,13 @@ import logging
|
|||||||
|
|
||||||
from PyQt5 import QtWidgets
|
from PyQt5 import QtWidgets
|
||||||
|
|
||||||
|
from openlp.core.common.actions import ActionList
|
||||||
from openlp.core.lib import Plugin, StringContent, build_icon, translate
|
from openlp.core.lib import Plugin, StringContent, build_icon, translate
|
||||||
from openlp.core.lib.ui import UiStrings, create_action
|
from openlp.core.lib.ui import UiStrings, create_action
|
||||||
from openlp.core.utils.actions import ActionList
|
from openlp.plugins.bibles.forms import BibleUpgradeForm
|
||||||
from openlp.plugins.bibles.lib import BibleManager, BiblesTab, BibleMediaItem, LayoutStyle, DisplayStyle, \
|
from openlp.plugins.bibles.lib import BibleManager, BiblesTab, BibleMediaItem, LayoutStyle, DisplayStyle, \
|
||||||
LanguageSelection
|
LanguageSelection
|
||||||
from openlp.plugins.bibles.lib.mediaitem import BibleSearch
|
from openlp.plugins.bibles.lib.mediaitem import BibleSearch
|
||||||
from openlp.plugins.bibles.forms import BibleUpgradeForm
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -198,6 +198,7 @@ class EditCustomForm(QtWidgets.QDialog, Ui_CustomEditDialog):
|
|||||||
# Insert all slides to make the old_slides list complete.
|
# Insert all slides to make the old_slides list complete.
|
||||||
for slide in slides:
|
for slide in slides:
|
||||||
old_slides.insert(old_row, slide)
|
old_slides.insert(old_row, slide)
|
||||||
|
old_row += 1
|
||||||
self.slide_list_view.addItems(old_slides)
|
self.slide_list_view.addItems(old_slides)
|
||||||
self.slide_list_view.repaint()
|
self.slide_list_view.repaint()
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
(function ( root, doc, factory ) {
|
(function ( root, doc, factory ) {
|
||||||
if ( typeof define === "function" && define.amd ) {
|
if ( typeof define === "function" && define.amd ) {
|
||||||
// AMD. Register as an anonymous module.
|
// AMD. Register as an anonymous module.
|
||||||
define( [ "jquery" ], function ( $ ) {
|
define( [ "jquery" ], function ($ ) {
|
||||||
factory( $, root, doc );
|
factory( $, root, doc );
|
||||||
return $.mobile;
|
return $.mobile;
|
||||||
});
|
});
|
2
openlp/plugins/remotes/html/assets/jquery.mobile.min.css
vendored
Normal file
2
openlp/plugins/remotes/html/assets/jquery.mobile.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
2
openlp/plugins/remotes/html/assets/jquery.mobile.min.js
vendored
Normal file
2
openlp/plugins/remotes/html/assets/jquery.mobile.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -18,11 +18,11 @@
|
|||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
|
|
||||||
.ui-icon-blank {
|
.ui-icon-blank {
|
||||||
background-image: url(images/ui-icon-blank.png);
|
background-image: url(../images/ui-icon-blank.png);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui-icon-unblank {
|
.ui-icon-unblank {
|
||||||
background-image: url(images/ui-icon-unblank.png);
|
background-image: url(../images/ui-icon-unblank.png);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Overwrite style from jquery-mobile.min.css */
|
/* Overwrite style from jquery-mobile.min.css */
|
@ -24,12 +24,12 @@
|
|||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, minimum-scale=1, maximum-scale=1" />
|
<meta name="viewport" content="width=device-width, minimum-scale=1, maximum-scale=1" />
|
||||||
<title>${app_title}</title>
|
<title>${app_title}</title>
|
||||||
<link rel="stylesheet" href="/files/jquery.mobile.min.css" />
|
<link rel="stylesheet" href="/assets/jquery.mobile.min.css" />
|
||||||
<link rel="stylesheet" href="/files/openlp.css" />
|
<link rel="stylesheet" href="/css/openlp.css" />
|
||||||
<link rel="shortcut icon" type="image/x-icon" href="/files/images/favicon.ico">
|
<link rel="shortcut icon" type="image/x-icon" href="/images/favicon.ico">
|
||||||
<script type="text/javascript" src="/files/jquery.min.js"></script>
|
<script type="text/javascript" src="/assets/jquery.min.js"></script>
|
||||||
<script type="text/javascript" src="/files/openlp.js"></script>
|
<script type="text/javascript" src="/js/openlp.js"></script>
|
||||||
<script type="text/javascript" src="/files/jquery.mobile.min.js"></script>
|
<script type="text/javascript" src="/assets/jquery.mobile.min.js"></script>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
translationStrings = {
|
translationStrings = {
|
||||||
"go_live": "${go_live}",
|
"go_live": "${go_live}",
|
||||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -23,10 +23,10 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<title>${live_title}</title>
|
<title>${live_title}</title>
|
||||||
<link rel="stylesheet" href="/files/main.css" />
|
<link rel="stylesheet" href="/css/main.css" />
|
||||||
<link rel="shortcut icon" type="image/x-icon" href="/files/images/favicon.ico">
|
<link rel="shortcut icon" type="image/x-icon" href="/images/favicon.ico">
|
||||||
<script type="text/javascript" src="/files/jquery.min.js"></script>
|
<script type="text/javascript" src="/assets/jquery.min.js"></script>
|
||||||
<script type="text/javascript" src="/files/main.js"></script>
|
<script type="text/javascript" src="/js/main.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<img id="image" class="size"/>
|
<img id="image" class="size"/>
|
||||||
|
@ -23,10 +23,10 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<title>${stage_title}</title>
|
<title>${stage_title}</title>
|
||||||
<link rel="stylesheet" href="/files/stage.css" />
|
<link rel="stylesheet" href="/css/stage.css" />
|
||||||
<link rel="shortcut icon" type="image/x-icon" href="/files/images/favicon.ico">
|
<link rel="shortcut icon" type="image/x-icon" href="/images/favicon.ico">
|
||||||
<script type="text/javascript" src="/files/jquery.min.js"></script>
|
<script type="text/javascript" src="/assets/jquery.min.js"></script>
|
||||||
<script type="text/javascript" src="/files/stage.js"></script>
|
<script type="text/javascript" src="/js/stage.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<input type="hidden" id="next-text" value="${next}" />
|
<input type="hidden" id="next-text" value="${next}" />
|
||||||
|
@ -146,12 +146,12 @@ class HttpRouter(RegistryProperties):
|
|||||||
self.auth = base64.b64encode(auth_code)
|
self.auth = base64.b64encode(auth_code)
|
||||||
except TypeError:
|
except TypeError:
|
||||||
self.auth = base64.b64encode(auth_code.encode()).decode()
|
self.auth = base64.b64encode(auth_code.encode()).decode()
|
||||||
|
self.default_route = {'function': self.serve_file, 'secure': False}
|
||||||
self.routes = [
|
self.routes = [
|
||||||
('^/$', {'function': self.serve_file, 'secure': False}),
|
('^/$', {'function': self.serve_file, 'secure': False}),
|
||||||
('^/(stage)$', {'function': self.serve_file, 'secure': False}),
|
('^/(stage)$', {'function': self.serve_file, 'secure': False}),
|
||||||
('^/(stage)/(.*)$', {'function': self.stages, 'secure': False}),
|
('^/(stage)/(.*)$', {'function': self.stages, 'secure': False}),
|
||||||
('^/(main)$', {'function': self.serve_file, 'secure': False}),
|
('^/(main)$', {'function': self.serve_file, 'secure': False}),
|
||||||
(r'^/files/(.*)$', {'function': self.serve_file, 'secure': False}),
|
|
||||||
(r'^/(\w+)/thumbnails([^/]+)?/(.*)$', {'function': self.serve_thumbnail, 'secure': False}),
|
(r'^/(\w+)/thumbnails([^/]+)?/(.*)$', {'function': self.serve_thumbnail, 'secure': False}),
|
||||||
(r'^/api/poll$', {'function': self.poll, 'secure': False}),
|
(r'^/api/poll$', {'function': self.poll, 'secure': False}),
|
||||||
(r'^/main/poll$', {'function': self.main_poll, 'secure': False}),
|
(r'^/main/poll$', {'function': self.main_poll, 'secure': False}),
|
||||||
@ -221,6 +221,7 @@ class HttpRouter(RegistryProperties):
|
|||||||
self.request_data = None
|
self.request_data = None
|
||||||
url_path_split = urlparse(url_path)
|
url_path_split = urlparse(url_path)
|
||||||
url_query = parse_qs(url_path_split.query)
|
url_query = parse_qs(url_path_split.query)
|
||||||
|
# GET
|
||||||
if 'data' in url_query.keys():
|
if 'data' in url_query.keys():
|
||||||
self.request_data = url_query['data'][0]
|
self.request_data = url_query['data'][0]
|
||||||
for route, func in self.routes:
|
for route, func in self.routes:
|
||||||
@ -231,7 +232,7 @@ class HttpRouter(RegistryProperties):
|
|||||||
for param in match.groups():
|
for param in match.groups():
|
||||||
args.append(param)
|
args.append(param)
|
||||||
return func, args
|
return func, args
|
||||||
return None, None
|
return self.default_route, [url_path_split.path]
|
||||||
|
|
||||||
def set_cache_headers(self):
|
def set_cache_headers(self):
|
||||||
self.send_header("Cache-Control", "no-cache, no-store, must-revalidate")
|
self.send_header("Cache-Control", "no-cache, no-store, must-revalidate")
|
||||||
@ -404,6 +405,8 @@ class HttpRouter(RegistryProperties):
|
|||||||
file_name = 'stage.html'
|
file_name = 'stage.html'
|
||||||
elif file_name == 'main':
|
elif file_name == 'main':
|
||||||
file_name = 'main.html'
|
file_name = 'main.html'
|
||||||
|
if file_name.startswith('/'):
|
||||||
|
file_name = file_name[1:]
|
||||||
path = os.path.normpath(os.path.join(self.html_dir, file_name))
|
path = os.path.normpath(os.path.join(self.html_dir, file_name))
|
||||||
if not path.startswith(self.html_dir):
|
if not path.startswith(self.html_dir):
|
||||||
return self.do_not_found()
|
return self.do_not_found()
|
||||||
|
@ -78,6 +78,13 @@ if is_win():
|
|||||||
HAS_WORSHIPCENTERPRO = True
|
HAS_WORSHIPCENTERPRO = True
|
||||||
except ImportError:
|
except ImportError:
|
||||||
log.exception('Error importing %s', 'WorshipCenterProImport')
|
log.exception('Error importing %s', 'WorshipCenterProImport')
|
||||||
|
HAS_OPSPRO = False
|
||||||
|
if is_win():
|
||||||
|
try:
|
||||||
|
from .importers.opspro import OPSProImport
|
||||||
|
HAS_OPSPRO = True
|
||||||
|
except ImportError:
|
||||||
|
log.exception('Error importing %s', 'OPSProImport')
|
||||||
|
|
||||||
|
|
||||||
class SongFormatSelect(object):
|
class SongFormatSelect(object):
|
||||||
@ -156,20 +163,21 @@ class SongFormat(object):
|
|||||||
Lyrix = 9
|
Lyrix = 9
|
||||||
MediaShout = 10
|
MediaShout = 10
|
||||||
OpenSong = 11
|
OpenSong = 11
|
||||||
PowerPraise = 12
|
OPSPro = 12
|
||||||
PowerSong = 13
|
PowerPraise = 13
|
||||||
PresentationManager = 14
|
PowerSong = 14
|
||||||
ProPresenter = 15
|
PresentationManager = 15
|
||||||
SongBeamer = 16
|
ProPresenter = 16
|
||||||
SongPro = 17
|
SongBeamer = 17
|
||||||
SongShowPlus = 18
|
SongPro = 18
|
||||||
SongsOfFellowship = 19
|
SongShowPlus = 19
|
||||||
SundayPlus = 20
|
SongsOfFellowship = 20
|
||||||
VideoPsalm = 21
|
SundayPlus = 21
|
||||||
WordsOfWorship = 22
|
VideoPsalm = 22
|
||||||
WorshipAssistant = 23
|
WordsOfWorship = 23
|
||||||
WorshipCenterPro = 24
|
WorshipAssistant = 24
|
||||||
ZionWorx = 25
|
WorshipCenterPro = 25
|
||||||
|
ZionWorx = 26
|
||||||
|
|
||||||
# Set optional attribute defaults
|
# Set optional attribute defaults
|
||||||
__defaults__ = {
|
__defaults__ = {
|
||||||
@ -272,6 +280,17 @@ class SongFormat(object):
|
|||||||
'name': WizardStrings.OS,
|
'name': WizardStrings.OS,
|
||||||
'prefix': 'openSong'
|
'prefix': 'openSong'
|
||||||
},
|
},
|
||||||
|
OPSPro: {
|
||||||
|
'name': 'OPS Pro',
|
||||||
|
'prefix': 'OPSPro',
|
||||||
|
'canDisable': True,
|
||||||
|
'selectMode': SongFormatSelect.SingleFile,
|
||||||
|
'filter': '%s (*.mdb)' % translate('SongsPlugin.ImportWizardForm', 'OPS Pro database'),
|
||||||
|
'disabledLabelText': translate('SongsPlugin.ImportWizardForm',
|
||||||
|
'The OPS Pro importer is only supported on Windows. It has been '
|
||||||
|
'disabled due to a missing Python module. If you want to use this '
|
||||||
|
'importer, you will need to install the "pyodbc" module.')
|
||||||
|
},
|
||||||
PowerPraise: {
|
PowerPraise: {
|
||||||
'class': PowerPraiseImport,
|
'class': PowerPraiseImport,
|
||||||
'name': 'PowerPraise',
|
'name': 'PowerPraise',
|
||||||
@ -403,6 +422,7 @@ class SongFormat(object):
|
|||||||
SongFormat.Lyrix,
|
SongFormat.Lyrix,
|
||||||
SongFormat.MediaShout,
|
SongFormat.MediaShout,
|
||||||
SongFormat.OpenSong,
|
SongFormat.OpenSong,
|
||||||
|
SongFormat.OPSPro,
|
||||||
SongFormat.PowerPraise,
|
SongFormat.PowerPraise,
|
||||||
SongFormat.PowerSong,
|
SongFormat.PowerSong,
|
||||||
SongFormat.PresentationManager,
|
SongFormat.PresentationManager,
|
||||||
@ -416,7 +436,7 @@ class SongFormat(object):
|
|||||||
SongFormat.WordsOfWorship,
|
SongFormat.WordsOfWorship,
|
||||||
SongFormat.WorshipAssistant,
|
SongFormat.WorshipAssistant,
|
||||||
SongFormat.WorshipCenterPro,
|
SongFormat.WorshipCenterPro,
|
||||||
SongFormat.ZionWorx,
|
SongFormat.ZionWorx
|
||||||
])
|
])
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -465,6 +485,9 @@ if HAS_MEDIASHOUT:
|
|||||||
SongFormat.set(SongFormat.WorshipCenterPro, 'availability', HAS_WORSHIPCENTERPRO)
|
SongFormat.set(SongFormat.WorshipCenterPro, 'availability', HAS_WORSHIPCENTERPRO)
|
||||||
if HAS_WORSHIPCENTERPRO:
|
if HAS_WORSHIPCENTERPRO:
|
||||||
SongFormat.set(SongFormat.WorshipCenterPro, 'class', WorshipCenterProImport)
|
SongFormat.set(SongFormat.WorshipCenterPro, 'class', WorshipCenterProImport)
|
||||||
|
SongFormat.set(SongFormat.OPSPro, 'availability', HAS_OPSPRO)
|
||||||
|
if HAS_OPSPRO:
|
||||||
|
SongFormat.set(SongFormat.OPSPro, 'class', OPSProImport)
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['SongFormat', 'SongFormatSelect']
|
__all__ = ['SongFormat', 'SongFormatSelect']
|
||||||
|
@ -289,40 +289,45 @@ class EasyWorshipSongImport(SongImport):
|
|||||||
for i in range(rec_count):
|
for i in range(rec_count):
|
||||||
if self.stop_import_flag:
|
if self.stop_import_flag:
|
||||||
break
|
break
|
||||||
raw_record = db_file.read(record_size)
|
try:
|
||||||
self.fields = self.record_structure.unpack(raw_record)
|
raw_record = db_file.read(record_size)
|
||||||
self.set_defaults()
|
self.fields = self.record_structure.unpack(raw_record)
|
||||||
self.title = self.get_field(fi_title).decode(self.encoding)
|
self.set_defaults()
|
||||||
# Get remaining fields.
|
self.title = self.get_field(fi_title).decode(self.encoding)
|
||||||
copy = self.get_field(fi_copy)
|
# Get remaining fields.
|
||||||
admin = self.get_field(fi_admin)
|
copy = self.get_field(fi_copy)
|
||||||
ccli = self.get_field(fi_ccli)
|
admin = self.get_field(fi_admin)
|
||||||
authors = self.get_field(fi_author)
|
ccli = self.get_field(fi_ccli)
|
||||||
words = self.get_field(fi_words)
|
authors = self.get_field(fi_author)
|
||||||
if copy:
|
words = self.get_field(fi_words)
|
||||||
self.copyright = copy.decode(self.encoding)
|
|
||||||
if admin:
|
|
||||||
if copy:
|
if copy:
|
||||||
self.copyright += ', '
|
self.copyright = copy.decode(self.encoding)
|
||||||
self.copyright += translate('SongsPlugin.EasyWorshipSongImport',
|
if admin:
|
||||||
'Administered by %s') % admin.decode(self.encoding)
|
if copy:
|
||||||
if ccli:
|
self.copyright += ', '
|
||||||
self.ccli_number = ccli.decode(self.encoding)
|
self.copyright += translate('SongsPlugin.EasyWorshipSongImport',
|
||||||
if authors:
|
'Administered by %s') % admin.decode(self.encoding)
|
||||||
authors = authors.decode(self.encoding)
|
if ccli:
|
||||||
else:
|
self.ccli_number = ccli.decode(self.encoding)
|
||||||
authors = ''
|
if authors:
|
||||||
# Set the SongImport object members.
|
authors = authors.decode(self.encoding)
|
||||||
self.set_song_import_object(authors, words)
|
else:
|
||||||
if self.stop_import_flag:
|
authors = ''
|
||||||
break
|
# Set the SongImport object members.
|
||||||
if self.entry_error_log:
|
self.set_song_import_object(authors, words)
|
||||||
|
if self.stop_import_flag:
|
||||||
|
break
|
||||||
|
if self.entry_error_log:
|
||||||
|
self.log_error(self.import_source,
|
||||||
|
translate('SongsPlugin.EasyWorshipSongImport', '"%s" could not be imported. %s')
|
||||||
|
% (self.title, self.entry_error_log))
|
||||||
|
self.entry_error_log = ''
|
||||||
|
elif not self.finish():
|
||||||
|
self.log_error(self.import_source)
|
||||||
|
except Exception as e:
|
||||||
self.log_error(self.import_source,
|
self.log_error(self.import_source,
|
||||||
translate('SongsPlugin.EasyWorshipSongImport', '"%s" could not be imported. %s')
|
translate('SongsPlugin.EasyWorshipSongImport', '"%s" could not be imported. %s')
|
||||||
% (self.title, self.entry_error_log))
|
% (self.title, e))
|
||||||
self.entry_error_log = ''
|
|
||||||
elif not self.finish():
|
|
||||||
self.log_error(self.import_source)
|
|
||||||
db_file.close()
|
db_file.close()
|
||||||
self.memo_file.close()
|
self.memo_file.close()
|
||||||
|
|
||||||
@ -368,7 +373,7 @@ class EasyWorshipSongImport(SongImport):
|
|||||||
first_line_is_tag = False
|
first_line_is_tag = False
|
||||||
# EW tags: verse, chorus, pre-chorus, bridge, tag,
|
# EW tags: verse, chorus, pre-chorus, bridge, tag,
|
||||||
# intro, ending, slide
|
# intro, ending, slide
|
||||||
for tag in VerseType.tags + ['tag', 'slide']:
|
for tag in VerseType.names + ['tag', 'slide', 'end']:
|
||||||
tag = tag.lower()
|
tag = tag.lower()
|
||||||
ew_tag = verse_split[0].strip().lower()
|
ew_tag = verse_split[0].strip().lower()
|
||||||
if ew_tag.startswith(tag):
|
if ew_tag.startswith(tag):
|
||||||
@ -390,6 +395,9 @@ class EasyWorshipSongImport(SongImport):
|
|||||||
if not number_found:
|
if not number_found:
|
||||||
verse_type += '1'
|
verse_type += '1'
|
||||||
break
|
break
|
||||||
|
# If the verse only consist of the tag-line, add an empty line to create an empty slide
|
||||||
|
if first_line_is_tag and len(verse_split) == 1:
|
||||||
|
verse_split.append("")
|
||||||
self.add_verse(verse_split[-1].strip() if first_line_is_tag else verse, verse_type)
|
self.add_verse(verse_split[-1].strip() if first_line_is_tag else verse, verse_type)
|
||||||
if len(self.comments) > 5:
|
if len(self.comments) > 5:
|
||||||
self.comments += str(translate('SongsPlugin.EasyWorshipSongImport',
|
self.comments += str(translate('SongsPlugin.EasyWorshipSongImport',
|
||||||
|
260
openlp/plugins/songs/lib/importers/opspro.py
Normal file
260
openlp/plugins/songs/lib/importers/opspro.py
Normal file
@ -0,0 +1,260 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# OpenLP - Open Source Lyrics Projection #
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# Copyright (c) 2008-2016 OpenLP Developers #
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# 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 #
|
||||||
|
###############################################################################
|
||||||
|
"""
|
||||||
|
The :mod:`opspro` module provides the functionality for importing
|
||||||
|
a OPS Pro database into the OpenLP database.
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
import re
|
||||||
|
import pyodbc
|
||||||
|
import struct
|
||||||
|
|
||||||
|
from openlp.core.common import translate
|
||||||
|
from openlp.plugins.songs.lib.importers.songimport import SongImport
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class OPSProImport(SongImport):
|
||||||
|
"""
|
||||||
|
The :class:`OPSProImport` class provides the ability to import the
|
||||||
|
WorshipCenter Pro Access Database
|
||||||
|
"""
|
||||||
|
def __init__(self, manager, **kwargs):
|
||||||
|
"""
|
||||||
|
Initialise the WorshipCenter Pro importer.
|
||||||
|
"""
|
||||||
|
super(OPSProImport, self).__init__(manager, **kwargs)
|
||||||
|
|
||||||
|
def do_import(self):
|
||||||
|
"""
|
||||||
|
Receive a single file to import.
|
||||||
|
"""
|
||||||
|
password = self.extract_mdb_password()
|
||||||
|
try:
|
||||||
|
conn = pyodbc.connect('DRIVER={Microsoft Access Driver (*.mdb)};DBQ=%s;PWD=%s' % (self.import_source,
|
||||||
|
password))
|
||||||
|
except (pyodbc.DatabaseError, pyodbc.IntegrityError, pyodbc.InternalError, pyodbc.OperationalError) as e:
|
||||||
|
log.warning('Unable to connect the OPS Pro database %s. %s', self.import_source, str(e))
|
||||||
|
# Unfortunately no specific exception type
|
||||||
|
self.log_error(self.import_source, translate('SongsPlugin.OPSProImport',
|
||||||
|
'Unable to connect the OPS Pro database.'))
|
||||||
|
return
|
||||||
|
cursor = conn.cursor()
|
||||||
|
cursor.execute('SELECT Song.ID, SongNumber, SongBookName, Title, CopyrightText, Version, Origin FROM Song '
|
||||||
|
'LEFT JOIN SongBook ON Song.SongBookID = SongBook.ID ORDER BY Title')
|
||||||
|
songs = cursor.fetchall()
|
||||||
|
self.import_wizard.progress_bar.setMaximum(len(songs))
|
||||||
|
for song in songs:
|
||||||
|
if self.stop_import_flag:
|
||||||
|
break
|
||||||
|
# Type means: 0=Original, 1=Projection, 2=Own
|
||||||
|
cursor.execute('SELECT Lyrics, Type, IsDualLanguage FROM Lyrics WHERE SongID = %d AND Type < 2 '
|
||||||
|
'ORDER BY Type DESC' % song.ID)
|
||||||
|
lyrics = cursor.fetchone()
|
||||||
|
cursor.execute('SELECT CategoryName FROM Category INNER JOIN SongCategory '
|
||||||
|
'ON Category.ID = SongCategory.CategoryID WHERE SongCategory.SongID = %d '
|
||||||
|
'ORDER BY CategoryName' % song.ID)
|
||||||
|
topics = cursor.fetchall()
|
||||||
|
try:
|
||||||
|
self.process_song(song, lyrics, topics)
|
||||||
|
except Exception as e:
|
||||||
|
self.log_error(self.import_source,
|
||||||
|
translate('SongsPlugin.OPSProImport', '"%s" could not be imported. %s')
|
||||||
|
% (song.Title, e))
|
||||||
|
|
||||||
|
def process_song(self, song, lyrics, topics):
|
||||||
|
"""
|
||||||
|
Create the song, i.e. title, verse etc.
|
||||||
|
|
||||||
|
The OPS Pro format is a fairly simple text format using tags and anchors/labels. Linebreaks are \r\n.
|
||||||
|
Double linebreaks are slide dividers. OPS Pro support dual language using tags.
|
||||||
|
Tags are in [], see the liste below:
|
||||||
|
[join] are used to separate verses that should be keept on the same slide.
|
||||||
|
[split] or [splits] can be used to split a verse over several slides, while still being the same verse
|
||||||
|
Dual language tags:
|
||||||
|
[trans off] or [vertaal uit] turns dual language mode off for the following text
|
||||||
|
[trans on] or [vertaal aan] turns dual language mode on for the following text
|
||||||
|
[taal a] means the following lines are language a
|
||||||
|
[taal b] means the following lines are language b
|
||||||
|
"""
|
||||||
|
self.set_defaults()
|
||||||
|
self.title = song.Title
|
||||||
|
if song.CopyrightText:
|
||||||
|
for line in song.CopyrightText.splitlines():
|
||||||
|
if line.startswith('©') or line.lower().startswith('copyright'):
|
||||||
|
self.add_copyright(line)
|
||||||
|
else:
|
||||||
|
self.parse_author(line)
|
||||||
|
if song.Origin:
|
||||||
|
self.comments = song.Origin
|
||||||
|
if song.SongBookName:
|
||||||
|
self.song_book_name = song.SongBookName
|
||||||
|
if song.SongNumber:
|
||||||
|
self.song_number = song.SongNumber
|
||||||
|
for topic in topics:
|
||||||
|
self.topics.append(topic.CategoryName)
|
||||||
|
# Try to split lyrics based on various rules
|
||||||
|
if lyrics:
|
||||||
|
lyrics_text = lyrics.Lyrics
|
||||||
|
verses = re.split('\r\n\s*?\r\n', lyrics_text)
|
||||||
|
verse_tag_defs = {}
|
||||||
|
verse_tag_texts = {}
|
||||||
|
for verse_text in verses:
|
||||||
|
if verse_text.strip() == '':
|
||||||
|
continue
|
||||||
|
verse_def = 'v'
|
||||||
|
# Detect verse number
|
||||||
|
verse_number = re.match('^(\d+)\r\n', verse_text)
|
||||||
|
if verse_number:
|
||||||
|
verse_text = re.sub('^\d+\r\n', '', verse_text)
|
||||||
|
verse_def = 'v' + verse_number.group(1)
|
||||||
|
# Detect verse tags
|
||||||
|
elif re.match('^.+?\:\r\n', verse_text):
|
||||||
|
tag_match = re.match('^(.+?)\:\r\n(.*)', verse_text, flags=re.DOTALL)
|
||||||
|
tag = tag_match.group(1).lower()
|
||||||
|
tag = tag.split(' ')[0]
|
||||||
|
verse_text = tag_match.group(2)
|
||||||
|
if 'refrein' in tag or 'chorus' in tag:
|
||||||
|
verse_def = 'c'
|
||||||
|
elif 'bridge' in tag:
|
||||||
|
verse_def = 'b'
|
||||||
|
verse_tag_defs[tag] = verse_def
|
||||||
|
verse_tag_texts[tag] = verse_text
|
||||||
|
# Detect tag reference
|
||||||
|
elif re.match('^\(.*?\)$', verse_text):
|
||||||
|
tag_match = re.match('^\((.*?)\)$', verse_text)
|
||||||
|
tag = tag_match.group(1).lower()
|
||||||
|
if tag in verse_tag_defs:
|
||||||
|
verse_text = verse_tag_texts[tag]
|
||||||
|
verse_def = verse_tag_defs[tag]
|
||||||
|
# Detect end tag
|
||||||
|
elif re.match('^\[slot\]\r\n', verse_text, re.IGNORECASE):
|
||||||
|
verse_def = 'e'
|
||||||
|
verse_text = re.sub('^\[slot\]\r\n', '', verse_text, flags=re.IGNORECASE)
|
||||||
|
# Replace the join tag with line breaks
|
||||||
|
verse_text = verse_text.replace('[join]', '')
|
||||||
|
# Replace the split tag with line breaks and an optional split
|
||||||
|
verse_text = re.sub('\[splits?\]', '\r\n[---]', verse_text)
|
||||||
|
# Handle translations
|
||||||
|
if lyrics.IsDualLanguage:
|
||||||
|
verse_text = self.handle_translation(verse_text)
|
||||||
|
# Remove comments
|
||||||
|
verse_text = re.sub('\(.*?\)\r\n', '', verse_text, flags=re.IGNORECASE)
|
||||||
|
self.add_verse(verse_text, verse_def)
|
||||||
|
self.finish()
|
||||||
|
|
||||||
|
def handle_translation(self, verse_text):
|
||||||
|
"""
|
||||||
|
Replace OPS Pro translation tags with a {translation} tag
|
||||||
|
|
||||||
|
:param verse_text: the verse text
|
||||||
|
:return: the verse text with replaced tags
|
||||||
|
"""
|
||||||
|
language = None
|
||||||
|
translation = True
|
||||||
|
translation_verse_text = ''
|
||||||
|
start_tag = '{translation}'
|
||||||
|
end_tag = '{/translation}'
|
||||||
|
verse_text_lines = verse_text.splitlines()
|
||||||
|
idx = 0
|
||||||
|
while idx < len(verse_text_lines):
|
||||||
|
# Detect if translation is turned on or off
|
||||||
|
if verse_text_lines[idx] in ['[trans off]', '[vertaal uit]']:
|
||||||
|
translation = False
|
||||||
|
idx += 1
|
||||||
|
elif verse_text_lines[idx] in ['[trans on]', '[vertaal aan]']:
|
||||||
|
translation = True
|
||||||
|
idx += 1
|
||||||
|
elif verse_text_lines[idx] == '[taal a]':
|
||||||
|
language = 'a'
|
||||||
|
idx += 1
|
||||||
|
elif verse_text_lines[idx] == '[taal b]':
|
||||||
|
language = 'b'
|
||||||
|
idx += 1
|
||||||
|
if not idx < len(verse_text_lines):
|
||||||
|
break
|
||||||
|
# Handle the text based on whether translation is off or on
|
||||||
|
if language:
|
||||||
|
if language == 'b':
|
||||||
|
translation_verse_text += start_tag
|
||||||
|
while idx < len(verse_text_lines) and not verse_text_lines[idx].startswith('['):
|
||||||
|
translation_verse_text += verse_text_lines[idx] + '\r\n'
|
||||||
|
idx += 1
|
||||||
|
if language == 'b':
|
||||||
|
translation_verse_text += end_tag
|
||||||
|
language = None
|
||||||
|
elif translation:
|
||||||
|
translation_verse_text += verse_text_lines[idx] + '\r\n'
|
||||||
|
idx += 1
|
||||||
|
if idx < len(verse_text_lines) and not verse_text_lines[idx].startswith('['):
|
||||||
|
translation_verse_text += start_tag + verse_text_lines[idx] + end_tag + '\r\n'
|
||||||
|
idx += 1
|
||||||
|
else:
|
||||||
|
translation_verse_text += verse_text_lines[idx] + '\r\n'
|
||||||
|
idx += 1
|
||||||
|
while idx < len(verse_text_lines) and not verse_text_lines[idx].startswith('['):
|
||||||
|
translation_verse_text += verse_text_lines[idx] + '\r\n'
|
||||||
|
idx += 1
|
||||||
|
return translation_verse_text
|
||||||
|
|
||||||
|
def extract_mdb_password(self):
|
||||||
|
"""
|
||||||
|
Extract password from mdb. Based on code from
|
||||||
|
http://tutorialsto.com/database/access/crack-access-*.-mdb-all-current-versions-of-the-password.html
|
||||||
|
"""
|
||||||
|
# The definition of 13 bytes as the source XOR Access2000. Encrypted with the corresponding signs are 0x13
|
||||||
|
xor_pattern_2k = (0xa1, 0xec, 0x7a, 0x9c, 0xe1, 0x28, 0x34, 0x8a, 0x73, 0x7b, 0xd2, 0xdf, 0x50)
|
||||||
|
# Access97 XOR of the source
|
||||||
|
xor_pattern_97 = (0x86, 0xfb, 0xec, 0x37, 0x5d, 0x44, 0x9c, 0xfa, 0xc6, 0x5e, 0x28, 0xe6, 0x13)
|
||||||
|
mdb = open(self.import_source, 'rb')
|
||||||
|
mdb.seek(0x14)
|
||||||
|
version = struct.unpack('B', mdb.read(1))[0]
|
||||||
|
# Get encrypted logo
|
||||||
|
mdb.seek(0x62)
|
||||||
|
EncrypFlag = struct.unpack('B', mdb.read(1))[0]
|
||||||
|
# Get encrypted password
|
||||||
|
mdb.seek(0x42)
|
||||||
|
encrypted_password = mdb.read(26)
|
||||||
|
mdb.close()
|
||||||
|
# "Decrypt" the password based on the version
|
||||||
|
decrypted_password = ''
|
||||||
|
if version < 0x01:
|
||||||
|
# Access 97
|
||||||
|
if int(encrypted_password[0] ^ xor_pattern_97[0]) == 0:
|
||||||
|
# No password
|
||||||
|
decrypted_password = ''
|
||||||
|
else:
|
||||||
|
for j in range(0, 12):
|
||||||
|
decrypted_password = decrypted_password + chr(encrypted_password[j] ^ xor_pattern_97[j])
|
||||||
|
else:
|
||||||
|
# Access 2000 or 2002
|
||||||
|
for j in range(0, 12):
|
||||||
|
if j % 2 == 0:
|
||||||
|
# Every byte with a different sign or encrypt. Encryption signs here for the 0x13
|
||||||
|
t1 = chr(0x13 ^ EncrypFlag ^ encrypted_password[j * 2] ^ xor_pattern_2k[j])
|
||||||
|
else:
|
||||||
|
t1 = chr(encrypted_password[j * 2] ^ xor_pattern_2k[j])
|
||||||
|
decrypted_password = decrypted_password + t1
|
||||||
|
if ord(decrypted_password[1]) < 0x20 or ord(decrypted_password[1]) > 0x7e:
|
||||||
|
decrypted_password = ''
|
||||||
|
return decrypted_password
|
@ -371,7 +371,7 @@ class SongImport(QtCore.QObject):
|
|||||||
song_book = self.manager.get_object_filtered(Book, Book.name == self.song_book_name)
|
song_book = self.manager.get_object_filtered(Book, Book.name == self.song_book_name)
|
||||||
if song_book is None:
|
if song_book is None:
|
||||||
song_book = Book.populate(name=self.song_book_name, publisher=self.song_book_pub)
|
song_book = Book.populate(name=self.song_book_name, publisher=self.song_book_pub)
|
||||||
song.book = song_book
|
song.add_songbook_entry(song_book, song.song_number)
|
||||||
for topic_text in self.topics:
|
for topic_text in self.topics:
|
||||||
if not topic_text:
|
if not topic_text:
|
||||||
continue
|
continue
|
||||||
|
@ -700,7 +700,7 @@ class SongMediaItem(MediaManagerItem):
|
|||||||
:param s: A string value from the list we want to sort.
|
:param s: A string value from the list we want to sort.
|
||||||
"""
|
"""
|
||||||
return [int(text) if text.isdecimal() else text.lower()
|
return [int(text) if text.isdecimal() else text.lower()
|
||||||
for text in re.split('(\d+)', s)]
|
for text in re.split('(\d+)', s)]
|
||||||
|
|
||||||
def search(self, string, show_error):
|
def search(self, string, show_error):
|
||||||
"""
|
"""
|
||||||
|
@ -28,8 +28,8 @@ import logging
|
|||||||
from sqlalchemy import Table, Column, ForeignKey, types
|
from sqlalchemy import Table, Column, ForeignKey, types
|
||||||
from sqlalchemy.sql.expression import func, false, null, text
|
from sqlalchemy.sql.expression import func, false, null, text
|
||||||
|
|
||||||
|
from openlp.core.common.db import drop_columns
|
||||||
from openlp.core.lib.db import get_upgrade_op
|
from openlp.core.lib.db import get_upgrade_op
|
||||||
from openlp.core.utils.db import drop_columns
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
__version__ = 5
|
__version__ = 5
|
||||||
|
@ -26,27 +26,26 @@ for the Songs plugin.
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
from tempfile import gettempdir
|
|
||||||
import sqlite3
|
import sqlite3
|
||||||
|
from tempfile import gettempdir
|
||||||
|
|
||||||
from PyQt5 import QtCore, QtWidgets
|
from PyQt5 import QtCore, QtWidgets
|
||||||
|
|
||||||
from openlp.core.common import UiStrings, Registry, translate
|
from openlp.core.common import UiStrings, Registry, translate
|
||||||
|
from openlp.core.common.actions import ActionList
|
||||||
from openlp.core.lib import Plugin, StringContent, build_icon
|
from openlp.core.lib import Plugin, StringContent, build_icon
|
||||||
from openlp.core.lib.db import Manager
|
from openlp.core.lib.db import Manager
|
||||||
from openlp.core.lib.ui import create_action
|
from openlp.core.lib.ui import create_action
|
||||||
from openlp.core.utils.actions import ActionList
|
|
||||||
from openlp.plugins.songs.forms.duplicatesongremovalform import DuplicateSongRemovalForm
|
from openlp.plugins.songs.forms.duplicatesongremovalform import DuplicateSongRemovalForm
|
||||||
from openlp.plugins.songs.forms.songselectform import SongSelectForm
|
from openlp.plugins.songs.forms.songselectform import SongSelectForm
|
||||||
from openlp.plugins.songs.lib import clean_song, upgrade
|
from openlp.plugins.songs.lib import clean_song, upgrade
|
||||||
from openlp.plugins.songs.lib.db import init_schema, Song
|
from openlp.plugins.songs.lib.db import init_schema, Song
|
||||||
from openlp.plugins.songs.lib.mediaitem import SongSearch
|
|
||||||
from openlp.plugins.songs.lib.importer import SongFormat
|
from openlp.plugins.songs.lib.importer import SongFormat
|
||||||
from openlp.plugins.songs.lib.importers.openlp import OpenLPSongImport
|
from openlp.plugins.songs.lib.importers.openlp import OpenLPSongImport
|
||||||
from openlp.plugins.songs.lib.mediaitem import SongMediaItem
|
from openlp.plugins.songs.lib.mediaitem import SongMediaItem
|
||||||
|
from openlp.plugins.songs.lib.mediaitem import SongSearch
|
||||||
from openlp.plugins.songs.lib.songstab import SongsTab
|
from openlp.plugins.songs.lib.songstab import SongsTab
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
__default_settings__ = {
|
__default_settings__ = {
|
||||||
'songs/db type': 'sqlite',
|
'songs/db type': 'sqlite',
|
||||||
|
@ -26,10 +26,10 @@ from datetime import datetime
|
|||||||
from PyQt5 import QtCore, QtWidgets
|
from PyQt5 import QtCore, QtWidgets
|
||||||
|
|
||||||
from openlp.core.common import Registry, Settings, translate
|
from openlp.core.common import Registry, Settings, translate
|
||||||
|
from openlp.core.common.actions import ActionList
|
||||||
from openlp.core.lib import Plugin, StringContent, build_icon
|
from openlp.core.lib import Plugin, StringContent, build_icon
|
||||||
from openlp.core.lib.db import Manager
|
from openlp.core.lib.db import Manager
|
||||||
from openlp.core.lib.ui import create_action
|
from openlp.core.lib.ui import create_action
|
||||||
from openlp.core.utils.actions import ActionList
|
|
||||||
from openlp.plugins.songusage.forms import SongUsageDetailForm, SongUsageDeleteForm
|
from openlp.plugins.songusage.forms import SongUsageDetailForm, SongUsageDeleteForm
|
||||||
from openlp.plugins.songusage.lib import upgrade
|
from openlp.plugins.songusage.lib import upgrade
|
||||||
from openlp.plugins.songusage.lib.db import init_schema, SongUsageItem
|
from openlp.plugins.songusage.lib.db import init_schema, SongUsageItem
|
||||||
|
0
tests/functional/openlp_core/__init__.py
Normal file
0
tests/functional/openlp_core/__init__.py
Normal file
@ -37,7 +37,7 @@ class TestInitFunctions(TestMixin, TestCase):
|
|||||||
# GIVEN: a a set of system arguments.
|
# GIVEN: a a set of system arguments.
|
||||||
sys.argv[1:] = []
|
sys.argv[1:] = []
|
||||||
# WHEN: We we parse them to expand to options
|
# WHEN: We we parse them to expand to options
|
||||||
args = parse_options()
|
args = parse_options(None)
|
||||||
# THEN: the following fields will have been extracted.
|
# THEN: the following fields will have been extracted.
|
||||||
self.assertFalse(args.dev_version, 'The dev_version flag should be False')
|
self.assertFalse(args.dev_version, 'The dev_version flag should be False')
|
||||||
self.assertEquals(args.loglevel, 'warning', 'The log level should be set to warning')
|
self.assertEquals(args.loglevel, 'warning', 'The log level should be set to warning')
|
||||||
@ -54,7 +54,7 @@ class TestInitFunctions(TestMixin, TestCase):
|
|||||||
# GIVEN: a a set of system arguments.
|
# GIVEN: a a set of system arguments.
|
||||||
sys.argv[1:] = ['-l debug']
|
sys.argv[1:] = ['-l debug']
|
||||||
# WHEN: We we parse them to expand to options
|
# WHEN: We we parse them to expand to options
|
||||||
args = parse_options()
|
args = parse_options(None)
|
||||||
# THEN: the following fields will have been extracted.
|
# THEN: the following fields will have been extracted.
|
||||||
self.assertFalse(args.dev_version, 'The dev_version flag should be False')
|
self.assertFalse(args.dev_version, 'The dev_version flag should be False')
|
||||||
self.assertEquals(args.loglevel, ' debug', 'The log level should be set to debug')
|
self.assertEquals(args.loglevel, ' debug', 'The log level should be set to debug')
|
||||||
@ -71,7 +71,7 @@ class TestInitFunctions(TestMixin, TestCase):
|
|||||||
# GIVEN: a a set of system arguments.
|
# GIVEN: a a set of system arguments.
|
||||||
sys.argv[1:] = ['--portable']
|
sys.argv[1:] = ['--portable']
|
||||||
# WHEN: We we parse them to expand to options
|
# WHEN: We we parse them to expand to options
|
||||||
args = parse_options()
|
args = parse_options(None)
|
||||||
# THEN: the following fields will have been extracted.
|
# THEN: the following fields will have been extracted.
|
||||||
self.assertFalse(args.dev_version, 'The dev_version flag should be False')
|
self.assertFalse(args.dev_version, 'The dev_version flag should be False')
|
||||||
self.assertEquals(args.loglevel, 'warning', 'The log level should be set to warning')
|
self.assertEquals(args.loglevel, 'warning', 'The log level should be set to warning')
|
||||||
@ -88,7 +88,7 @@ class TestInitFunctions(TestMixin, TestCase):
|
|||||||
# GIVEN: a a set of system arguments.
|
# GIVEN: a a set of system arguments.
|
||||||
sys.argv[1:] = ['-l debug', '-d']
|
sys.argv[1:] = ['-l debug', '-d']
|
||||||
# WHEN: We we parse them to expand to options
|
# WHEN: We we parse them to expand to options
|
||||||
args = parse_options()
|
args = parse_options(None)
|
||||||
# THEN: the following fields will have been extracted.
|
# THEN: the following fields will have been extracted.
|
||||||
self.assertTrue(args.dev_version, 'The dev_version flag should be True')
|
self.assertTrue(args.dev_version, 'The dev_version flag should be True')
|
||||||
self.assertEquals(args.loglevel, ' debug', 'The log level should be set to debug')
|
self.assertEquals(args.loglevel, ' debug', 'The log level should be set to debug')
|
||||||
@ -105,7 +105,7 @@ class TestInitFunctions(TestMixin, TestCase):
|
|||||||
# GIVEN: a a set of system arguments.
|
# GIVEN: a a set of system arguments.
|
||||||
sys.argv[1:] = ['dummy_temp']
|
sys.argv[1:] = ['dummy_temp']
|
||||||
# WHEN: We we parse them to expand to options
|
# WHEN: We we parse them to expand to options
|
||||||
args = parse_options()
|
args = parse_options(None)
|
||||||
# THEN: the following fields will have been extracted.
|
# THEN: the following fields will have been extracted.
|
||||||
self.assertFalse(args.dev_version, 'The dev_version flag should be False')
|
self.assertFalse(args.dev_version, 'The dev_version flag should be False')
|
||||||
self.assertEquals(args.loglevel, 'warning', 'The log level should be set to warning')
|
self.assertEquals(args.loglevel, 'warning', 'The log level should be set to warning')
|
||||||
@ -122,7 +122,7 @@ class TestInitFunctions(TestMixin, TestCase):
|
|||||||
# GIVEN: a a set of system arguments.
|
# GIVEN: a a set of system arguments.
|
||||||
sys.argv[1:] = ['-l debug', 'dummy_temp']
|
sys.argv[1:] = ['-l debug', 'dummy_temp']
|
||||||
# WHEN: We we parse them to expand to options
|
# WHEN: We we parse them to expand to options
|
||||||
args = parse_options()
|
args = parse_options(None)
|
||||||
# THEN: the following fields will have been extracted.
|
# THEN: the following fields will have been extracted.
|
||||||
self.assertFalse(args.dev_version, 'The dev_version flag should be False')
|
self.assertFalse(args.dev_version, 'The dev_version flag should be False')
|
||||||
self.assertEquals(args.loglevel, ' debug', 'The log level should be set to debug')
|
self.assertEquals(args.loglevel, ' debug', 'The log level should be set to debug')
|
||||||
@ -130,15 +130,3 @@ class TestInitFunctions(TestMixin, TestCase):
|
|||||||
self.assertFalse(args.portable, 'The portable flag should be set to false')
|
self.assertFalse(args.portable, 'The portable flag should be set to false')
|
||||||
self.assertEquals(args.style, None, 'There are no style flags to be processed')
|
self.assertEquals(args.style, None, 'There are no style flags to be processed')
|
||||||
self.assertEquals(args.rargs, 'dummy_temp', 'The service file should not be blank')
|
self.assertEquals(args.rargs, 'dummy_temp', 'The service file should not be blank')
|
||||||
|
|
||||||
def parse_options_two_files_test(self):
|
|
||||||
"""
|
|
||||||
Test the parse options process works with a file
|
|
||||||
|
|
||||||
"""
|
|
||||||
# GIVEN: a a set of system arguments.
|
|
||||||
sys.argv[1:] = ['dummy_temp', 'dummy_temp2']
|
|
||||||
# WHEN: We we parse them to expand to options
|
|
||||||
args = parse_options()
|
|
||||||
# THEN: the following fields will have been extracted.
|
|
||||||
self.assertEquals(args, None, 'The args should be None')
|
|
||||||
|
@ -20,15 +20,14 @@
|
|||||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||||
###############################################################################
|
###############################################################################
|
||||||
"""
|
"""
|
||||||
Package to test the openlp.core.utils.actions package.
|
Package to test the openlp.core.common.actions package.
|
||||||
"""
|
"""
|
||||||
from unittest import TestCase
|
from unittest import TestCase
|
||||||
|
|
||||||
from PyQt5 import QtGui, QtCore, QtWidgets
|
from PyQt5 import QtGui, QtCore, QtWidgets
|
||||||
|
|
||||||
from openlp.core.common import Settings
|
from openlp.core.common import Settings
|
||||||
from openlp.core.utils import ActionList
|
from openlp.core.common.actions import CategoryActionList, ActionList
|
||||||
from openlp.core.utils.actions import CategoryActionList
|
|
||||||
from tests.functional import MagicMock
|
from tests.functional import MagicMock
|
||||||
from tests.helpers.testmixin import TestMixin
|
from tests.helpers.testmixin import TestMixin
|
||||||
|
|
@ -20,19 +20,19 @@
|
|||||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||||
###############################################################################
|
###############################################################################
|
||||||
"""
|
"""
|
||||||
Package to test the openlp.core.utils.db package.
|
Package to test the openlp.core.common.db package.
|
||||||
"""
|
"""
|
||||||
from tempfile import mkdtemp
|
|
||||||
from unittest import TestCase
|
|
||||||
import gc
|
import gc
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import sqlalchemy
|
|
||||||
import time
|
import time
|
||||||
|
from tempfile import mkdtemp
|
||||||
|
from unittest import TestCase
|
||||||
|
|
||||||
from openlp.core.utils.db import drop_column, drop_columns
|
import sqlalchemy
|
||||||
|
|
||||||
|
from openlp.core.common.db import drop_column, drop_columns
|
||||||
from openlp.core.lib.db import init_db, get_upgrade_op
|
from openlp.core.lib.db import init_db, get_upgrade_op
|
||||||
|
|
||||||
from tests.utils.constants import TEST_RESOURCES_PATH
|
from tests.utils.constants import TEST_RESOURCES_PATH
|
||||||
|
|
||||||
|
|
@ -26,6 +26,7 @@ Package to test the openlp.core.lib.projector.pjlink1 package.
|
|||||||
from unittest import TestCase
|
from unittest import TestCase
|
||||||
|
|
||||||
from openlp.core.lib.projector.pjlink1 import PJLink1
|
from openlp.core.lib.projector.pjlink1 import PJLink1
|
||||||
|
from openlp.core.lib.projector.constants import E_PARAMETER, ERROR_STRING
|
||||||
|
|
||||||
from tests.functional import patch
|
from tests.functional import patch
|
||||||
from tests.resources.projector.data import TEST_PIN, TEST_SALT, TEST_CONNECT_AUTHENTICATE
|
from tests.resources.projector.data import TEST_PIN, TEST_SALT, TEST_CONNECT_AUTHENTICATE
|
||||||
@ -74,3 +75,20 @@ class TestPJLink(TestCase):
|
|||||||
# THEN: Projector class should be set with proper value
|
# THEN: Projector class should be set with proper value
|
||||||
self.assertEquals(pjlink.pjlink_class, '1',
|
self.assertEquals(pjlink.pjlink_class, '1',
|
||||||
'Non-standard class reply should have set proper class')
|
'Non-standard class reply should have set proper class')
|
||||||
|
|
||||||
|
@patch.object(pjlink_test, 'change_status')
|
||||||
|
def status_change_test(self, mock_change_status):
|
||||||
|
"""
|
||||||
|
Test process_command call with ERR2 (Parameter) status
|
||||||
|
"""
|
||||||
|
# GIVEN: Test object
|
||||||
|
pjlink = pjlink_test
|
||||||
|
|
||||||
|
# WHEN: process_command is called with "ERR2" status from projector
|
||||||
|
pjlink.process_command('POWR', 'ERR2')
|
||||||
|
|
||||||
|
# THEN: change_status should have called change_status with E_UNDEFINED
|
||||||
|
# as first parameter
|
||||||
|
mock_change_status.called_with(E_PARAMETER,
|
||||||
|
'change_status should have been called with "{}"'.format(
|
||||||
|
ERROR_STRING[E_PARAMETER]))
|
||||||
|
@ -22,13 +22,14 @@
|
|||||||
"""
|
"""
|
||||||
Package to test the openlp.core.ui.slidecontroller package.
|
Package to test the openlp.core.ui.slidecontroller package.
|
||||||
"""
|
"""
|
||||||
|
import PyQt5
|
||||||
from unittest import TestCase
|
from unittest import TestCase
|
||||||
|
|
||||||
from openlp.core.common import Registry, ThemeLevel
|
from openlp.core.common import Registry, ThemeLevel, Settings
|
||||||
from openlp.core.lib import ServiceItem, ServiceItemType, ItemCapabilities
|
from openlp.core.lib import ServiceItem, ServiceItemType, ItemCapabilities
|
||||||
from openlp.core.ui import ServiceManager
|
from openlp.core.ui import ServiceManager
|
||||||
|
|
||||||
from tests.functional import MagicMock
|
from tests.functional import MagicMock, patch
|
||||||
|
|
||||||
|
|
||||||
class TestServiceManager(TestCase):
|
class TestServiceManager(TestCase):
|
||||||
@ -540,3 +541,80 @@ class TestServiceManager(TestCase):
|
|||||||
self.assertEquals(service_manager.timed_slide_interval.setChecked.call_count, 0, 'Should not be called')
|
self.assertEquals(service_manager.timed_slide_interval.setChecked.call_count, 0, 'Should not be called')
|
||||||
self.assertEquals(service_manager.theme_menu.menuAction().setVisible.call_count, 1,
|
self.assertEquals(service_manager.theme_menu.menuAction().setVisible.call_count, 1,
|
||||||
'Should have be called once')
|
'Should have be called once')
|
||||||
|
|
||||||
|
@patch(u'openlp.core.ui.servicemanager.Settings')
|
||||||
|
@patch(u'PyQt5.QtCore.QTimer.singleShot')
|
||||||
|
def single_click_preview_test_true(self, mocked_singleShot, MockedSettings):
|
||||||
|
"""
|
||||||
|
Test that when "Preview items when clicked in Service Manager" enabled the preview timer starts
|
||||||
|
"""
|
||||||
|
# GIVEN: A setting to enable "Preview items when clicked in Service Manager" and a service manager.
|
||||||
|
mocked_settings = MagicMock()
|
||||||
|
mocked_settings.value.return_value = True
|
||||||
|
MockedSettings.return_value = mocked_settings
|
||||||
|
service_manager = ServiceManager(None)
|
||||||
|
# WHEN: on_single_click_preview() is called
|
||||||
|
service_manager.on_single_click_preview()
|
||||||
|
# THEN: timer should have been started
|
||||||
|
mocked_singleShot.assert_called_with(PyQt5.QtWidgets.QApplication.instance().doubleClickInterval(),
|
||||||
|
service_manager.on_single_click_preview_timeout)
|
||||||
|
|
||||||
|
@patch(u'openlp.core.ui.servicemanager.Settings')
|
||||||
|
@patch(u'PyQt5.QtCore.QTimer.singleShot')
|
||||||
|
def single_click_preview_test_false(self, mocked_singleShot, MockedSettings):
|
||||||
|
"""
|
||||||
|
Test that when "Preview items when clicked in Service Manager" disabled the preview timer doesn't start
|
||||||
|
"""
|
||||||
|
# GIVEN: A setting to enable "Preview items when clicked in Service Manager" and a service manager.
|
||||||
|
mocked_settings = MagicMock()
|
||||||
|
mocked_settings.value.return_value = False
|
||||||
|
MockedSettings.return_value = mocked_settings
|
||||||
|
service_manager = ServiceManager(None)
|
||||||
|
# WHEN: on_single_click_preview() is called
|
||||||
|
service_manager.on_single_click_preview()
|
||||||
|
# THEN: timer should not be started
|
||||||
|
self.assertEquals(mocked_singleShot.call_count, 0, 'Should not be called')
|
||||||
|
|
||||||
|
@patch(u'openlp.core.ui.servicemanager.Settings')
|
||||||
|
@patch(u'PyQt5.QtCore.QTimer.singleShot')
|
||||||
|
@patch(u'openlp.core.ui.servicemanager.ServiceManager.make_live')
|
||||||
|
def single_click_preview_test_double(self, mocked_make_live, mocked_singleShot, MockedSettings):
|
||||||
|
"""
|
||||||
|
Test that when a double click has registered the preview timer doesn't start
|
||||||
|
"""
|
||||||
|
# GIVEN: A setting to enable "Preview items when clicked in Service Manager" and a service manager.
|
||||||
|
mocked_settings = MagicMock()
|
||||||
|
mocked_settings.value.return_value = True
|
||||||
|
MockedSettings.return_value = mocked_settings
|
||||||
|
service_manager = ServiceManager(None)
|
||||||
|
# WHEN: on_single_click_preview() is called following a double click
|
||||||
|
service_manager.on_double_click_live()
|
||||||
|
service_manager.on_single_click_preview()
|
||||||
|
# THEN: timer should not be started
|
||||||
|
self.assertEquals(mocked_singleShot.call_count, 0, 'Should not be called')
|
||||||
|
|
||||||
|
@patch(u'openlp.core.ui.servicemanager.ServiceManager.make_preview')
|
||||||
|
def single_click_timeout_test_single(self, mocked_make_preview):
|
||||||
|
"""
|
||||||
|
Test that when a single click has been registered, the item is sent to preview
|
||||||
|
"""
|
||||||
|
# GIVEN: A service manager.
|
||||||
|
service_manager = ServiceManager(None)
|
||||||
|
# WHEN: on_single_click_preview() is called
|
||||||
|
service_manager.on_single_click_preview_timeout()
|
||||||
|
# THEN: make_preview() should have been called
|
||||||
|
self.assertEquals(mocked_make_preview.call_count, 1, 'Should have been called once')
|
||||||
|
|
||||||
|
@patch(u'openlp.core.ui.servicemanager.ServiceManager.make_preview')
|
||||||
|
@patch(u'openlp.core.ui.servicemanager.ServiceManager.make_live')
|
||||||
|
def single_click_timeout_test_double(self, mocked_make_live, mocked_make_preview):
|
||||||
|
"""
|
||||||
|
Test that when a double click has been registered, the item does not goes to preview
|
||||||
|
"""
|
||||||
|
# GIVEN: A service manager.
|
||||||
|
service_manager = ServiceManager(None)
|
||||||
|
# WHEN: on_single_click_preview() is called after a double click
|
||||||
|
service_manager.on_double_click_live()
|
||||||
|
service_manager.on_single_click_preview_timeout()
|
||||||
|
# THEN: make_preview() should have been called
|
||||||
|
self.assertEquals(mocked_make_preview.call_count, 0, 'Should not be called')
|
||||||
|
@ -241,7 +241,7 @@ class TestUtils(TestCase):
|
|||||||
"""
|
"""
|
||||||
Test the get_locale_key(string) function
|
Test the get_locale_key(string) function
|
||||||
"""
|
"""
|
||||||
with patch('openlp.core.utils.languagemanager.LanguageManager.get_language') as mocked_get_language:
|
with patch('openlp.core.common.languagemanager.LanguageManager.get_language') as mocked_get_language:
|
||||||
# GIVEN: The language is German
|
# GIVEN: The language is German
|
||||||
# 0x00C3 (A with diaresis) should be sorted as "A". 0x00DF (sharp s) should be sorted as "ss".
|
# 0x00C3 (A with diaresis) should be sorted as "A". 0x00DF (sharp s) should be sorted as "ss".
|
||||||
mocked_get_language.return_value = 'de'
|
mocked_get_language.return_value = 'de'
|
||||||
@ -258,7 +258,7 @@ class TestUtils(TestCase):
|
|||||||
"""
|
"""
|
||||||
Test the get_natural_key(string) function
|
Test the get_natural_key(string) function
|
||||||
"""
|
"""
|
||||||
with patch('openlp.core.utils.languagemanager.LanguageManager.get_language') as mocked_get_language:
|
with patch('openlp.core.common.languagemanager.LanguageManager.get_language') as mocked_get_language:
|
||||||
# GIVEN: The language is English (a language, which sorts digits before letters)
|
# GIVEN: The language is English (a language, which sorts digits before letters)
|
||||||
mocked_get_language.return_value = 'en'
|
mocked_get_language.return_value = 'en'
|
||||||
unsorted_list = ['item 10a', 'item 3b', '1st item']
|
unsorted_list = ['item 10a', 'item 3b', '1st item']
|
||||||
|
163
tests/functional/openlp_plugins/songs/test_opsproimport.py
Normal file
163
tests/functional/openlp_plugins/songs/test_opsproimport.py
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# OpenLP - Open Source Lyrics Projection #
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# Copyright (c) 2008-2016 OpenLP Developers #
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# 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 WorshipCenter Pro song importer.
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
from unittest import TestCase, SkipTest
|
||||||
|
|
||||||
|
if os.name != 'nt':
|
||||||
|
raise SkipTest('Not Windows, skipping test')
|
||||||
|
|
||||||
|
from tests.functional import patch, MagicMock
|
||||||
|
|
||||||
|
from openlp.core.common import Registry
|
||||||
|
from openlp.plugins.songs.lib.importers.opspro import OPSProImport
|
||||||
|
|
||||||
|
TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..', 'resources', 'opsprosongs'))
|
||||||
|
|
||||||
|
|
||||||
|
class TestOpsProSongImport(TestCase):
|
||||||
|
"""
|
||||||
|
Test the functions in the :mod:`opsproimport` module.
|
||||||
|
"""
|
||||||
|
def setUp(self):
|
||||||
|
"""
|
||||||
|
Create the registry
|
||||||
|
"""
|
||||||
|
Registry.create()
|
||||||
|
|
||||||
|
@patch('openlp.plugins.songs.lib.importers.opspro.SongImport')
|
||||||
|
def create_importer_test(self, mocked_songimport):
|
||||||
|
"""
|
||||||
|
Test creating an instance of the OPS Pro file importer
|
||||||
|
"""
|
||||||
|
# GIVEN: A mocked out SongImport class, and a mocked out "manager"
|
||||||
|
mocked_manager = MagicMock()
|
||||||
|
|
||||||
|
# WHEN: An importer object is created
|
||||||
|
importer = OPSProImport(mocked_manager, filenames=[])
|
||||||
|
|
||||||
|
# THEN: The importer object should not be None
|
||||||
|
self.assertIsNotNone(importer, 'Import should not be none')
|
||||||
|
|
||||||
|
@patch('openlp.plugins.songs.lib.importers.opspro.SongImport')
|
||||||
|
def detect_chorus_test(self, mocked_songimport):
|
||||||
|
"""
|
||||||
|
Test importing lyrics with a chorus in OPS Pro
|
||||||
|
"""
|
||||||
|
# GIVEN: A mocked out SongImport class, a mocked out "manager" and a mocked song and lyrics entry
|
||||||
|
mocked_manager = MagicMock()
|
||||||
|
importer = OPSProImport(mocked_manager, filenames=[])
|
||||||
|
importer.finish = MagicMock()
|
||||||
|
song, lyrics = self._build_test_data('you are so faithfull.txt', False)
|
||||||
|
|
||||||
|
# WHEN: An importer object is created
|
||||||
|
importer.process_song(song, lyrics, [])
|
||||||
|
|
||||||
|
# THEN: The imported data should look like expected
|
||||||
|
result_file = open(os.path.join(TEST_PATH, 'You are so faithful.json'), 'rb')
|
||||||
|
result_data = json.loads(result_file.read().decode())
|
||||||
|
self.assertListEqual(importer.verses, self._get_data(result_data, 'verses'))
|
||||||
|
self.assertListEqual(importer.verse_order_list_generated, self._get_data(result_data, 'verse_order_list'))
|
||||||
|
|
||||||
|
@patch('openlp.plugins.songs.lib.importers.opspro.SongImport')
|
||||||
|
def join_and_split_test(self, mocked_songimport):
|
||||||
|
"""
|
||||||
|
Test importing lyrics with a split and join tags works in OPS Pro
|
||||||
|
"""
|
||||||
|
# GIVEN: A mocked out SongImport class, a mocked out "manager" and a mocked song and lyrics entry
|
||||||
|
mocked_manager = MagicMock()
|
||||||
|
importer = OPSProImport(mocked_manager, filenames=[])
|
||||||
|
importer.finish = MagicMock()
|
||||||
|
song, lyrics = self._build_test_data('amazing grace.txt', False)
|
||||||
|
|
||||||
|
# WHEN: An importer object is created
|
||||||
|
importer.process_song(song, lyrics, [])
|
||||||
|
|
||||||
|
# THEN: The imported data should look like expected
|
||||||
|
result_file = open(os.path.join(TEST_PATH, 'Amazing Grace.json'), 'rb')
|
||||||
|
result_data = json.loads(result_file.read().decode())
|
||||||
|
self.assertListEqual(importer.verses, self._get_data(result_data, 'verses'))
|
||||||
|
self.assertListEqual(importer.verse_order_list_generated, self._get_data(result_data, 'verse_order_list'))
|
||||||
|
|
||||||
|
@patch('openlp.plugins.songs.lib.importers.opspro.SongImport')
|
||||||
|
def trans_off_tag_test(self, mocked_songimport):
|
||||||
|
"""
|
||||||
|
Test importing lyrics with a split and join and translations tags works in OPS Pro
|
||||||
|
"""
|
||||||
|
# GIVEN: A mocked out SongImport class, a mocked out "manager" and a mocked song and lyrics entry
|
||||||
|
mocked_manager = MagicMock()
|
||||||
|
importer = OPSProImport(mocked_manager, filenames=[])
|
||||||
|
importer.finish = MagicMock()
|
||||||
|
song, lyrics = self._build_test_data('amazing grace2.txt', True)
|
||||||
|
|
||||||
|
# WHEN: An importer object is created
|
||||||
|
importer.process_song(song, lyrics, [])
|
||||||
|
|
||||||
|
# THEN: The imported data should look like expected
|
||||||
|
result_file = open(os.path.join(TEST_PATH, 'Amazing Grace.json'), 'rb')
|
||||||
|
result_data = json.loads(result_file.read().decode())
|
||||||
|
self.assertListEqual(importer.verses, self._get_data(result_data, 'verses'))
|
||||||
|
self.assertListEqual(importer.verse_order_list_generated, self._get_data(result_data, 'verse_order_list'))
|
||||||
|
|
||||||
|
@patch('openlp.plugins.songs.lib.importers.opspro.SongImport')
|
||||||
|
def trans_tag_test(self, mocked_songimport):
|
||||||
|
"""
|
||||||
|
Test importing lyrics with various translations tags works in OPS Pro
|
||||||
|
"""
|
||||||
|
# GIVEN: A mocked out SongImport class, a mocked out "manager" and a mocked song and lyrics entry
|
||||||
|
mocked_manager = MagicMock()
|
||||||
|
importer = OPSProImport(mocked_manager, filenames=[])
|
||||||
|
importer.finish = MagicMock()
|
||||||
|
song, lyrics = self._build_test_data('amazing grace3.txt', True)
|
||||||
|
|
||||||
|
# WHEN: An importer object is created
|
||||||
|
importer.process_song(song, lyrics, [])
|
||||||
|
|
||||||
|
# THEN: The imported data should look like expected
|
||||||
|
result_file = open(os.path.join(TEST_PATH, 'Amazing Grace3.json'), 'rb')
|
||||||
|
result_data = json.loads(result_file.read().decode())
|
||||||
|
self.assertListEqual(importer.verses, self._get_data(result_data, 'verses'))
|
||||||
|
self.assertListEqual(importer.verse_order_list_generated, self._get_data(result_data, 'verse_order_list'))
|
||||||
|
|
||||||
|
def _get_data(self, data, key):
|
||||||
|
if key in data:
|
||||||
|
return data[key]
|
||||||
|
return ''
|
||||||
|
|
||||||
|
def _build_test_data(self, test_file, dual_language):
|
||||||
|
song = MagicMock()
|
||||||
|
song.ID = 100
|
||||||
|
song.SongNumber = 123
|
||||||
|
song.SongBookName = 'The Song Book'
|
||||||
|
song.Title = 'Song Title'
|
||||||
|
song.CopyrightText = 'Music and text by me'
|
||||||
|
song.Version = '1'
|
||||||
|
song.Origin = '...'
|
||||||
|
lyrics = MagicMock()
|
||||||
|
test_file = open(os.path.join(TEST_PATH, test_file), 'rb')
|
||||||
|
lyrics.Lyrics = test_file.read().decode()
|
||||||
|
lyrics.Type = 1
|
||||||
|
lyrics.IsDualLanguage = dual_language
|
||||||
|
return song, lyrics
|
@ -23,11 +23,8 @@ This module contains tests for the VideoPsalm song importer.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from unittest import TestCase
|
|
||||||
|
|
||||||
from tests.helpers.songfileimport import SongImportTestHelper
|
from tests.helpers.songfileimport import SongImportTestHelper
|
||||||
from openlp.core.common import Registry
|
|
||||||
from tests.functional import patch, MagicMock
|
|
||||||
|
|
||||||
TEST_PATH = os.path.abspath(
|
TEST_PATH = os.path.abspath(
|
||||||
os.path.join(os.path.dirname(__file__), '..', '..', '..', 'resources', 'videopsalmsongs'))
|
os.path.join(os.path.dirname(__file__), '..', '..', '..', 'resources', 'videopsalmsongs'))
|
||||||
|
@ -32,7 +32,7 @@ from PyQt5 import QtWidgets
|
|||||||
|
|
||||||
from openlp.core.common import Registry, Settings
|
from openlp.core.common import Registry, Settings
|
||||||
from openlp.core.lib.pluginmanager import PluginManager
|
from openlp.core.lib.pluginmanager import PluginManager
|
||||||
from tests.interfaces import MagicMock
|
from tests.interfaces import MagicMock, patch
|
||||||
from tests.helpers.testmixin import TestMixin
|
from tests.helpers.testmixin import TestMixin
|
||||||
|
|
||||||
|
|
||||||
@ -45,13 +45,12 @@ class TestPluginManager(TestCase, TestMixin):
|
|||||||
"""
|
"""
|
||||||
Some pre-test setup required.
|
Some pre-test setup required.
|
||||||
"""
|
"""
|
||||||
Settings.setDefaultFormat(Settings.IniFormat)
|
self.setup_application()
|
||||||
self.build_settings()
|
self.build_settings()
|
||||||
self.temp_dir = mkdtemp('openlp')
|
self.temp_dir = mkdtemp('openlp')
|
||||||
Settings().setValue('advanced/data path', self.temp_dir)
|
Settings().setValue('advanced/data path', self.temp_dir)
|
||||||
Registry.create()
|
Registry.create()
|
||||||
Registry().register('service_list', MagicMock())
|
Registry().register('service_list', MagicMock())
|
||||||
self.setup_application()
|
|
||||||
self.main_window = QtWidgets.QMainWindow()
|
self.main_window = QtWidgets.QMainWindow()
|
||||||
Registry().register('main_window', self.main_window)
|
Registry().register('main_window', self.main_window)
|
||||||
|
|
||||||
@ -64,7 +63,13 @@ class TestPluginManager(TestCase, TestMixin):
|
|||||||
gc.collect()
|
gc.collect()
|
||||||
shutil.rmtree(self.temp_dir)
|
shutil.rmtree(self.temp_dir)
|
||||||
|
|
||||||
def find_plugins_test(self):
|
@patch('openlp.plugins.songusage.lib.db.init_schema')
|
||||||
|
@patch('openlp.plugins.songs.lib.db.init_schema')
|
||||||
|
@patch('openlp.plugins.images.lib.db.init_schema')
|
||||||
|
@patch('openlp.plugins.custom.lib.db.init_schema')
|
||||||
|
@patch('openlp.plugins.alerts.lib.db.init_schema')
|
||||||
|
@patch('openlp.plugins.bibles.lib.db.init_schema')
|
||||||
|
def find_plugins_test(self, mocked_is1, mocked_is2, mocked_is3, mocked_is4, mocked_is5, mocked_is6):
|
||||||
"""
|
"""
|
||||||
Test the find_plugins() method to ensure it imports the correct plugins
|
Test the find_plugins() method to ensure it imports the correct plugins
|
||||||
"""
|
"""
|
||||||
|
@ -128,3 +128,19 @@ class TestEditCustomForm(TestCase, TestMixin):
|
|||||||
# THEN: The validate method should have returned False.
|
# THEN: The validate method should have returned False.
|
||||||
assert not result, 'The _validate() method should have retured False'
|
assert not result, 'The _validate() method should have retured False'
|
||||||
mocked_critical_error_message_box.assert_called_with(message='You need to add at least one slide.')
|
mocked_critical_error_message_box.assert_called_with(message='You need to add at least one slide.')
|
||||||
|
|
||||||
|
def update_slide_list_test(self):
|
||||||
|
"""
|
||||||
|
Test the update_slide_list() method
|
||||||
|
"""
|
||||||
|
# GIVEN: Mocked slide_list_view with a slide with 3 lines
|
||||||
|
self.form.slide_list_view = MagicMock()
|
||||||
|
self.form.slide_list_view.count.return_value = 1
|
||||||
|
self.form.slide_list_view.currentRow.return_value = 0
|
||||||
|
self.form.slide_list_view.item.return_value = MagicMock(return_value='1st Slide\n2nd Slide\n3rd Slide')
|
||||||
|
|
||||||
|
# WHEN: updating the slide by splitting the lines into slides
|
||||||
|
self.form.update_slide_list(['1st Slide', '2nd Slide', '3rd Slide'])
|
||||||
|
|
||||||
|
# THEN: The slides should be created in correct order
|
||||||
|
self.form.slide_list_view.addItems.assert_called_with(['1st Slide', '2nd Slide', '3rd Slide'])
|
||||||
|
21
tests/resources/opsprosongs/Amazing Grace.json
Normal file
21
tests/resources/opsprosongs/Amazing Grace.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"title": "Amazing Grace",
|
||||||
|
"verse_order_list": ["v1", "v2", "v3"],
|
||||||
|
"verses": [
|
||||||
|
[
|
||||||
|
"v1",
|
||||||
|
"Amazing grace! How sweet the sound!\r\nThat saved a wretch like me!\r\nI once was lost, but now am found;\r\nWas blind, but now I see.\r\n\r\n'Twas grace that taught my heart to fear,\r\nAnd grace my fears relieved.\r\nHow precious did that grace appear,\r\nThe hour I first believed.",
|
||||||
|
null
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"v2",
|
||||||
|
"The Lord has promised good to me,\r\nHis Word my hope secures.\r\nHe will my shield and portion be\r\nAs long as life endures.",
|
||||||
|
null
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"v3",
|
||||||
|
"Thro' many dangers, toils and snares\r\nI have already come.\r\n'Tis grace that brought me safe thus far,\r\nAnd grace will lead me home.\r\n\r\n[---]\r\nWhen we've been there ten thousand years,\r\nBright shining as the sun,\r\nWe've no less days to sing God's praise,\r\nThan when we first begun.",
|
||||||
|
null
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
31
tests/resources/opsprosongs/Amazing Grace3.json
Normal file
31
tests/resources/opsprosongs/Amazing Grace3.json
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"title": "Amazing Grace",
|
||||||
|
"verse_order_list": ["v1", "v2", "v3", "v4", "v5"],
|
||||||
|
"verses": [
|
||||||
|
[
|
||||||
|
"v1",
|
||||||
|
"Amazing grace! How sweet the sound!\r\n{translation}That saved a wretch like me!{/translation}\r\nI once was lost, but now am found;\r\n{translation}Was blind, but now I see.{/translation}",
|
||||||
|
null
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"v2",
|
||||||
|
"'Twas grace that taught my heart to fear,\r\nAnd grace my fears relieved.\r\n{translation}How precious did that grace appear,\r\nThe hour I first believed.\r\n{/translation}",
|
||||||
|
null
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"v3",
|
||||||
|
"The Lord has promised good to me,\r\nHis Word my hope secures.\r\nHe will my shield and portion be\r\n{translation}As long as life endures.{/translation}",
|
||||||
|
null
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"v4",
|
||||||
|
"Thro' many dangers, toils and snares\r\nI have already come.\r\n'Tis grace that brought me safe thus far,\r\n{translation}And grace will lead me home.{/translation}",
|
||||||
|
null
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"v5",
|
||||||
|
"[end]\r\n{translation}When we've been there ten thousand years,{/translation}\r\nBright shining as the sun,\r\n{translation}We've no less days to sing God's praise,{/translation}\r\nThan when we first begun.",
|
||||||
|
null
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
31
tests/resources/opsprosongs/You are so faithful.json
Normal file
31
tests/resources/opsprosongs/You are so faithful.json
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"title": "You are so faithful",
|
||||||
|
"verse_order_list": ["v1", "c1", "v2", "c1", "v3", "c1", "v4"],
|
||||||
|
"verses": [
|
||||||
|
[
|
||||||
|
"v1",
|
||||||
|
"You are so faithful\r\nso faithful, so faithful.\r\nYou are so faithful\r\nso faithful, so faithful.",
|
||||||
|
null
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"c1",
|
||||||
|
"That's why I praise you\r\nin the morning\r\nThat's why I praise you\r\nin the noontime.\r\nThat's why I praise you\r\nin the evening\r\nThat's why I praise you\r\nall the time.",
|
||||||
|
null
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"v2",
|
||||||
|
"You are so loving\r\nso loving, so loving.\r\nYou are so loving\r\nso loving, so loving.",
|
||||||
|
null
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"v3",
|
||||||
|
"You are so caring\r\nso caring, so caring.\r\nYou are so caring\r\nso caring, so caring.",
|
||||||
|
null
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"v4",
|
||||||
|
"You are so mighty\r\nso mighty, so mighty.\r\nYou are so mighty\r\nso mighty, so mighty.",
|
||||||
|
null
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
24
tests/resources/opsprosongs/amazing grace.txt
Normal file
24
tests/resources/opsprosongs/amazing grace.txt
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
Amazing grace! How sweet the sound!
|
||||||
|
That saved a wretch like me!
|
||||||
|
I once was lost, but now am found;
|
||||||
|
Was blind, but now I see.
|
||||||
|
[join]
|
||||||
|
'Twas grace that taught my heart to fear,
|
||||||
|
And grace my fears relieved.
|
||||||
|
How precious did that grace appear,
|
||||||
|
The hour I first believed.
|
||||||
|
|
||||||
|
The Lord has promised good to me,
|
||||||
|
His Word my hope secures.
|
||||||
|
He will my shield and portion be
|
||||||
|
As long as life endures.
|
||||||
|
|
||||||
|
Thro' many dangers, toils and snares
|
||||||
|
I have already come.
|
||||||
|
'Tis grace that brought me safe thus far,
|
||||||
|
And grace will lead me home.
|
||||||
|
[split]
|
||||||
|
When we've been there ten thousand years,
|
||||||
|
Bright shining as the sun,
|
||||||
|
We've no less days to sing God's praise,
|
||||||
|
Than when we first begun.
|
29
tests/resources/opsprosongs/amazing grace2.txt
Normal file
29
tests/resources/opsprosongs/amazing grace2.txt
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
[trans off]
|
||||||
|
Amazing grace! How sweet the sound!
|
||||||
|
That saved a wretch like me!
|
||||||
|
I once was lost, but now am found;
|
||||||
|
Was blind, but now I see.
|
||||||
|
[join]
|
||||||
|
[trans off]
|
||||||
|
'Twas grace that taught my heart to fear,
|
||||||
|
And grace my fears relieved.
|
||||||
|
How precious did that grace appear,
|
||||||
|
The hour I first believed.
|
||||||
|
|
||||||
|
[trans off]
|
||||||
|
The Lord has promised good to me,
|
||||||
|
His Word my hope secures.
|
||||||
|
He will my shield and portion be
|
||||||
|
As long as life endures.
|
||||||
|
|
||||||
|
[trans off]
|
||||||
|
Thro' many dangers, toils and snares
|
||||||
|
I have already come.
|
||||||
|
'Tis grace that brought me safe thus far,
|
||||||
|
And grace will lead me home.
|
||||||
|
[trans off]
|
||||||
|
[split]
|
||||||
|
When we've been there ten thousand years,
|
||||||
|
Bright shining as the sun,
|
||||||
|
We've no less days to sing God's praise,
|
||||||
|
Than when we first begun.
|
31
tests/resources/opsprosongs/amazing grace3.txt
Normal file
31
tests/resources/opsprosongs/amazing grace3.txt
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
Amazing grace! How sweet the sound!
|
||||||
|
That saved a wretch like me!
|
||||||
|
I once was lost, but now am found;
|
||||||
|
Was blind, but now I see.
|
||||||
|
|
||||||
|
[taal a]
|
||||||
|
'Twas grace that taught my heart to fear,
|
||||||
|
And grace my fears relieved.
|
||||||
|
[taal b]
|
||||||
|
How precious did that grace appear,
|
||||||
|
The hour I first believed.
|
||||||
|
|
||||||
|
[trans off]
|
||||||
|
The Lord has promised good to me,
|
||||||
|
His Word my hope secures.
|
||||||
|
[trans on]
|
||||||
|
He will my shield and portion be
|
||||||
|
As long as life endures.
|
||||||
|
|
||||||
|
[vertaal uit]
|
||||||
|
Thro' many dangers, toils and snares
|
||||||
|
I have already come.
|
||||||
|
[vertaal aan]
|
||||||
|
'Tis grace that brought me safe thus far,
|
||||||
|
And grace will lead me home.
|
||||||
|
|
||||||
|
[end]
|
||||||
|
When we've been there ten thousand years,
|
||||||
|
Bright shining as the sun,
|
||||||
|
We've no less days to sing God's praise,
|
||||||
|
Than when we first begun.
|
37
tests/resources/opsprosongs/you are so faithfull.txt
Normal file
37
tests/resources/opsprosongs/you are so faithfull.txt
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
1
|
||||||
|
You are so faithful
|
||||||
|
so faithful, so faithful.
|
||||||
|
You are so faithful
|
||||||
|
so faithful, so faithful.
|
||||||
|
|
||||||
|
Refrein:
|
||||||
|
That's why I praise you
|
||||||
|
in the morning
|
||||||
|
That's why I praise you
|
||||||
|
in the noontime.
|
||||||
|
That's why I praise you
|
||||||
|
in the evening
|
||||||
|
That's why I praise you
|
||||||
|
all the time.
|
||||||
|
|
||||||
|
2
|
||||||
|
You are so loving
|
||||||
|
so loving, so loving.
|
||||||
|
You are so loving
|
||||||
|
so loving, so loving.
|
||||||
|
|
||||||
|
(refrein)
|
||||||
|
|
||||||
|
3
|
||||||
|
You are so caring
|
||||||
|
so caring, so caring.
|
||||||
|
You are so caring
|
||||||
|
so caring, so caring.
|
||||||
|
|
||||||
|
(refrein)
|
||||||
|
|
||||||
|
4
|
||||||
|
You are so mighty
|
||||||
|
so mighty, so mighty.
|
||||||
|
You are so mighty
|
||||||
|
so mighty, so mighty.
|
Loading…
Reference in New Issue
Block a user