Merging to trunk on 3.4.16

This commit is contained in:
suutari-olli 2016-04-03 23:58:06 +03:00
commit 447f7a9ead
63 changed files with 984 additions and 190 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -289,6 +289,7 @@ 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
try:
raw_record = db_file.read(record_size) raw_record = db_file.read(record_size)
self.fields = self.record_structure.unpack(raw_record) self.fields = self.record_structure.unpack(raw_record)
self.set_defaults() self.set_defaults()
@ -323,6 +324,10 @@ class EasyWorshipSongImport(SongImport):
self.entry_error_log = '' self.entry_error_log = ''
elif not self.finish(): elif not self.finish():
self.log_error(self.import_source) self.log_error(self.import_source)
except Exception as e:
self.log_error(self.import_source,
translate('SongsPlugin.EasyWorshipSongImport', '"%s" could not be imported. %s')
% (self.title, e))
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',

View 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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

View File

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

View File

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

View File

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

View 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
]
]
}

View 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
]
]
}

View 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
]
]
}

View 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.

View 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.

View 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.

View 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.