Merge trunk

This commit is contained in:
Samuel Mehrbrodt 2015-12-11 09:33:24 +01:00
commit 0d4541b8dc
125 changed files with 13430 additions and 11728 deletions

View File

@ -1 +1 @@
2.1.6
2.2

View File

@ -30,7 +30,7 @@ logging and a plugin framework are contained within the openlp.core module.
import os
import sys
import logging
from optparse import OptionParser
import argparse
from traceback import format_exception
import shutil
import time
@ -282,17 +282,18 @@ def parse_options(args):
:return: a tuple of parsed options of type optparse.Value and a list of remaining argsZ
"""
# Set up command line options.
usage = 'Usage: %prog [options] [qt-options]'
parser = OptionParser(usage=usage)
parser.add_option('-e', '--no-error-form', dest='no_error_form', action='store_true',
parser = argparse.ArgumentParser(prog='openlp.py')
parser.add_argument('-e', '--no-error-form', dest='no_error_form', action='store_true',
help='Disable the error notification form.')
parser.add_option('-l', '--log-level', dest='loglevel', default='warning', metavar='LEVEL',
parser.add_argument('-l', '--log-level', dest='loglevel', default='warning', metavar='LEVEL',
help='Set logging to LEVEL level. Valid values are "debug", "info", "warning".')
parser.add_option('-p', '--portable', dest='portable', action='store_true',
help='Specify if this should be run as a portable app, off a USB flash drive (not implemented).')
parser.add_option('-d', '--dev-version', dest='dev_version', action='store_true',
parser.add_argument('-p', '--portable', dest='portable', action='store_true',
help='Specify if this should be run as a portable app, '
'off a USB flash drive (not implemented).')
parser.add_argument('-d', '--dev-version', dest='dev_version', action='store_true',
help='Ignore the version file and pull the version directly from Bazaar')
parser.add_option('-s', '--style', dest='style', help='Set the Qt4 style (passed directly to Qt4).')
parser.add_argument('-s', '--style', dest='style', help='Set the Qt4 style (passed directly to Qt4).')
parser.add_argument('rargs', nargs='?', default=[])
# Parse command line options and deal with them. Use args supplied pragmatically if possible.
return parser.parse_args(args) if args else parser.parse_args()
@ -318,18 +319,18 @@ def main(args=None):
:param args: Some args
"""
(options, args) = parse_options(args)
args = parse_options(args)
qt_args = []
if options.loglevel.lower() in ['d', 'debug']:
if args and args.loglevel.lower() in ['d', 'debug']:
log.setLevel(logging.DEBUG)
elif options.loglevel.lower() in ['w', 'warning']:
elif args and args.loglevel.lower() in ['w', 'warning']:
log.setLevel(logging.WARNING)
else:
log.setLevel(logging.INFO)
if options.style:
qt_args.extend(['-style', options.style])
if args and args.style:
qt_args.extend(['-style', args.style])
# Throw the rest of the arguments at Qt, just in case.
qt_args.extend(args)
qt_args.extend(args.rargs)
# Bug #1018855: Set the WM_CLASS property in X11
if not is_win() and not is_macosx():
qt_args.append('OpenLP')
@ -339,7 +340,7 @@ def main(args=None):
application = OpenLP(qt_args)
application.setOrganizationName('OpenLP')
application.setOrganizationDomain('openlp.org')
if options.portable:
if args and args.portable:
application.setApplicationName('OpenLPPortable')
Settings.setDefaultFormat(Settings.IniFormat)
# Get location OpenLPPortable.ini
@ -383,6 +384,6 @@ def main(args=None):
application.installTranslator(default_translator)
else:
log.debug('Could not find default_translator.')
if not options.no_error_form:
if args and not args.no_error_form:
sys.excepthook = application.hook_exception
sys.exit(application.run(qt_args))

View File

@ -118,6 +118,7 @@ class Settings(QtCore.QSettings):
'advanced/slide limits': SlideLimits.End,
'advanced/single click preview': False,
'advanced/x11 bypass wm': X11_BYPASS_DEFAULT,
'advanced/search as type': True,
'crashreport/last directory': '',
'formattingTags/html_tags': '',
'core/audio repeat list': False,
@ -321,48 +322,10 @@ class Settings(QtCore.QSettings):
}
__file_path__ = ''
__obsolete_settings__ = [
# Changed during 1.9.x development.
('bibles/bookname language', 'bibles/book name language', []),
('general/enable slide loop', 'advanced/slide limits', [(SlideLimits.Wrap, True), (SlideLimits.End, False)]),
('songs/ccli number', 'core/ccli number', []),
('media/use phonon', '', []),
# Changed during 2.1.x development.
('advanced/stylesheet fix', '', []),
('bibles/last directory 1', 'bibles/last directory import', []),
('media/background color', 'players/background color', []),
('themes/last directory', 'themes/last directory import', []),
('themes/last directory 1', 'themes/last directory export', []),
('songs/last directory 1', 'songs/last directory import', []),
('songusage/last directory 1', 'songusage/last directory export', []),
('user interface/mainwindow splitter geometry', 'user interface/main window splitter geometry', []),
('shortcuts/makeLive', 'shortcuts/make_live', []),
('general/audio repeat list', 'core/audio repeat list', []),
('general/auto open', 'core/auto open', []),
('general/auto preview', 'core/auto preview', []),
('general/audio start paused', 'core/audio start paused', []),
('general/auto unblank', 'core/auto unblank', []),
('general/blank warning', 'core/blank warning', []),
('general/ccli number', 'core/ccli number', []),
('general/has run wizard', 'core/has run wizard', []),
('general/language', 'core/language', []),
('general/last version test', 'core/last version test', []),
('general/loop delay', 'core/loop delay', []),
('general/recent files', 'core/recent files', [(recent_files_conv, None)]),
('general/save prompt', 'core/save prompt', []),
('general/screen blank', 'core/screen blank', []),
('general/show splash', 'core/show splash', []),
('general/songselect password', 'core/songselect password', []),
('general/songselect username', 'core/songselect username', []),
('general/update check', 'core/update check', []),
('general/view mode', 'core/view mode', []),
('general/display on monitor', 'core/display on monitor', []),
('general/override position', 'core/override position', []),
('general/x position', 'core/x position', []),
('general/y position', 'core/y position', []),
('general/monitor', 'core/monitor', []),
('general/height', 'core/height', []),
('general/monitor', 'core/monitor', []),
('general/width', 'core/width', [])
# Changed during 2.2.x development.
# ('advanced/stylesheet fix', '', []),
# ('general/recent files', 'core/recent files', [(recent_files_conv, None)]),
('songs/search as type', 'advanced/search as type', [])
]
@staticmethod

View File

@ -108,8 +108,9 @@ class UiStrings(object):
self.NFSp = translate('OpenLP.Ui', 'No Files Selected', 'Plural')
self.NISs = translate('OpenLP.Ui', 'No Item Selected', 'Singular')
self.NISp = translate('OpenLP.Ui', 'No Items Selected', 'Plural')
self.OLPV2 = translate('OpenLP.Ui', 'OpenLP 2')
self.OLPV2x = translate('OpenLP.Ui', 'OpenLP 2.2')
self.OLP = translate('OpenLP.Ui', 'OpenLP')
self.OLPV2 = "%s %s" % (self.OLP, "2")
self.OLPV2x = "%s %s" % (self.OLP, "2.4")
self.OpenLPStart = translate('OpenLP.Ui', 'OpenLP is already running. Do you wish to continue?')
self.OpenService = translate('OpenLP.Ui', 'Open service.')
self.PlaySlidesInLoop = translate('OpenLP.Ui', 'Play Slides in Loop')

View File

@ -83,7 +83,7 @@ def get_text_file_string(text_file):
None.
:param text_file: The name of the file.
:return The file as a single string
:return: The file as a single string
"""
if not os.path.isfile(text_file):
return False
@ -108,7 +108,7 @@ def str_to_bool(string_value):
Convert a string version of a boolean into a real boolean.
:param string_value: The string value to examine and convert to a boolean type.
:return The correct boolean value
:return: The correct boolean value
"""
if isinstance(string_value, bool):
return string_value
@ -123,7 +123,7 @@ def build_icon(icon):
:param icon:
The icon to build. This can be a QIcon, a resource string in the form ``:/resource/file.png``, or a file
location like ``/path/to/file.png``. However, the **recommended** way is to specify a resource string.
:return The build icon.
:return: The build icon.
"""
button_icon = QtGui.QIcon()
if isinstance(icon, QtGui.QIcon):
@ -168,7 +168,7 @@ def create_thumb(image_path, thumb_path, return_icon=True, size=None):
:param return_icon: States if an icon should be build and returned from the thumb. Defaults to ``True``.
:param size: Allows to state a own size (QtCore.QSize) to use. Defaults to ``None``, which means that a default
height of 88 is used.
:return The final icon.
:return: The final icon.
"""
ext = os.path.splitext(thumb_path)[1].lower()
reader = QtGui.QImageReader(image_path)
@ -194,7 +194,7 @@ def validate_thumb(file_path, thumb_path):
:param file_path: The path to the file. The file **must** exist!
:param thumb_path: The path to the thumb.
:return True, False if the image has changed since the thumb was created.
:return: True, False if the image has changed since the thumb was created.
"""
if not os.path.exists(thumb_path):
return False

View File

@ -185,8 +185,8 @@ class FormattingTags(object):
A boolean stating whether this is a build-in tag or not. Should be ``True`` in most cases.
* temporary
A temporary tag will not be saved, but is also considered when displaying text containing the tag. It has
to be a ``boolean``.
A temporary tag will not be saved, but is also considered when displaying text containing the tag. It
has to be a ``boolean``.
"""
FormattingTags.html_expands.extend(tags)

View File

@ -288,13 +288,7 @@ class Plugin(QtCore.QObject, RegistryProperties):
"""
Perform tasks on application startup
"""
# FIXME: Remove after 2.2 release.
# This is needed to load the list of media/presentation from the config saved before the settings rewrite.
if self.media_item_class is not None and self.name != 'images':
loaded_list = Settings().get_files_from_config(self)
# Now save the list to the config using our Settings class.
if loaded_list:
Settings().setValue('%s/%s files' % (self.settings_section, self.name), loaded_list)
pass
def uses_theme(self, theme):
"""

View File

@ -25,7 +25,9 @@
See PJLink Class 1 Specifications for details.
http://pjlink.jbmia.or.jp/english/dl.html
Section 5-1 PJLink Specifications
Section 5-5 Guidelines for Input Terminals
NOTE:

View File

@ -142,7 +142,9 @@ class ScreenList(object):
"""
Add a screen to the list of known screens.
:param screen: A dict with the screen properties::
:param screen: A dict with the screen properties:
::
{
'primary': True,

View File

@ -129,7 +129,7 @@ class ItemCapabilities(object):
OnLoadUpdate = 8
AddIfNewItem = 9
ProvidesOwnDisplay = 10
HasDetailedTitleDisplay = 11
# HasDetailedTitleDisplay = 11
HasVariableStartTime = 12
CanSoftBreak = 13
CanWordSplit = 14
@ -415,11 +415,6 @@ class ServiceItem(RegistryProperties):
self.will_auto_start = header.get('will_auto_start', False)
self.processor = header.get('processor', None)
self.has_original_files = True
# TODO: Remove me in 2,3 build phase
if self.is_capable(ItemCapabilities.HasDetailedTitleDisplay):
self.capabilities.remove(ItemCapabilities.HasDetailedTitleDisplay)
self.processor = self.title
self.title = None
if 'background_audio' in header:
self.background_audio = []
for filename in header['background_audio']:

View File

@ -154,80 +154,42 @@ class UiAboutDialog(object):
'zh_CN': [' "executor" ']
}
documentors = ['Wesley "wrst" Stout', 'John "jseagull1" Cegalis (lead)']
self.credits_text_edit.setPlainText(
translate('OpenLP.AboutForm',
'Project Lead\n'
' %s\n'
'\n'
'Developers\n'
' %s\n'
'\n'
'Contributors\n'
' %s\n'
'\n'
'Testers\n'
' %s\n'
'\n'
'Packagers\n'
' %s\n'
'\n'
'Translators\n'
' Afrikaans (af)\n'
' %s\n'
' Czech (cs)\n'
' %s\n'
' Danish (da)\n'
' %s\n'
' German (de)\n'
' %s\n'
' Greek (el)\n'
' %s\n'
' English, United Kingdom (en_GB)\n'
' %s\n'
' English, South Africa (en_ZA)\n'
' %s\n'
' Spanish (es)\n'
' %s\n'
' Estonian (et)\n'
' %s\n'
' Finnish (fi)\n'
' %s\n'
' French (fr)\n'
' %s\n'
' Hungarian (hu)\n'
' %s\n'
' Indonesian (id)\n'
' %s\n'
' Japanese (ja)\n'
' %s\n'
' Norwegian Bokm\xe5l (nb)\n'
' %s\n'
' Dutch (nl)\n'
' %s\n'
' Polish (pl)\n'
' %s\n'
' Portuguese, Brazil (pt_BR)\n'
' %s\n'
' Russian (ru)\n'
' %s\n'
' Swedish (sv)\n'
' %s\n'
' Tamil(Sri-Lanka) (ta_LK)\n'
' %s\n'
' Chinese(China) (zh_CN)\n'
' %s\n'
'\n'
'Documentation\n'
' %s\n'
'\n'
'Built With\n'
project_lead = translate('OpenLP.AboutForm', 'Project Lead')
devs = translate('OpenLP.AboutForm', 'Developers')
cons = translate('OpenLP.AboutForm', 'Contributors')
packs = translate('OpenLP.AboutForm', 'Packagers')
tests = translate('OpenLP.AboutForm', 'Testers')
laters = translate('OpenLP.AboutForm', 'Translators')
af = translate('OpenLP.AboutForm', 'Afrikaans (af)')
cs = translate('OpenLP.AboutForm', 'Czech (cs)')
da = translate('OpenLP.AboutForm', 'Danish (da)')
de = translate('OpenLP.AboutForm', 'German (de)')
el = translate('OpenLP.AboutForm', 'Greek (el)')
gb = translate('OpenLP.AboutForm', 'English, United Kingdom (en_GB)')
enza = translate('OpenLP.AboutForm', 'English, South Africa (en_ZA)')
es = translate('OpenLP.AboutForm', 'Spanish (es)')
et = translate('OpenLP.AboutForm', 'Estonian (et)')
fi = translate('OpenLP.AboutForm', 'Finnish (fi)')
fr = translate('OpenLP.AboutForm', 'French (fr)')
hu = translate('OpenLP.AboutForm', 'Hungarian (hu)')
ind = translate('OpenLP.AboutForm', 'Indonesian (id)')
ja = translate('OpenLP.AboutForm', 'Japanese (ja)')
nb = translate('OpenLP.AboutForm', 'Norwegian Bokm\xe5l (nb)')
nl = translate('OpenLP.AboutForm', 'Dutch (nl)')
pl = translate('OpenLP.AboutForm', 'Polish (pl)')
ptbr = translate('OpenLP.AboutForm', 'Portuguese, Brazil (pt_BR)')
ru = translate('OpenLP.AboutForm', 'Russian (ru)')
sv = translate('OpenLP.AboutForm', 'Swedish (sv)')
talk = translate('OpenLP.AboutForm', 'Tamil(Sri-Lanka) (ta_LK)')
zhcn = translate('OpenLP.AboutForm', 'Chinese(China) (zh_CN)')
documentation = translate('OpenLP.AboutForm', 'Documentation')
built_with = translate('OpenLP.AboutForm', 'Built With\n'
' Python: http://www.python.org/\n'
' Qt4: http://qt.io\n'
' PyQt4: http://www.riverbankcomputing.co.uk/software/pyqt/intro\n'
' Oxygen Icons: http://techbase.kde.org/Projects/Oxygen/\n'
' MuPDF: http://www.mupdf.com/\n'
'\n'
'Final Credit\n'
' MuPDF: http://www.mupdf.com/\n')
final_credit = translate('OpenLP.AboutForm', 'Final Credit\n'
' "For God so loved the world that He gave\n'
' His one and only Son, so that whoever\n'
' believes in Him will not perish but inherit\n'
@ -236,32 +198,103 @@ class UiAboutDialog(object):
' God our Father, for sending His Son to die\n'
' on the cross, setting us free from sin. We\n'
' bring this software to you for free because\n'
' He has set us free.') %
(lead, '\n '.join(developers),
'\n '.join(contributors), '\n '.join(testers),
'\n '.join(packagers), '\n '.join(translators['af']),
'\n '.join(translators['cs']),
'\n '.join(translators['da']),
'\n '.join(translators['de']),
'\n '.join(translators['el']),
'\n '.join(translators['en_GB']),
'\n '.join(translators['en_ZA']),
'\n '.join(translators['es']),
'\n '.join(translators['et']),
'\n '.join(translators['fi']),
'\n '.join(translators['fr']),
'\n '.join(translators['hu']),
'\n '.join(translators['id']),
'\n '.join(translators['ja']),
'\n '.join(translators['nb']),
'\n '.join(translators['nl']),
'\n '.join(translators['pl']),
'\n '.join(translators['pt_BR']),
'\n '.join(translators['ru']),
'\n '.join(translators['sv']),
'\n '.join(translators['ta_LK']),
'\n '.join(translators['zh_CN']),
'\n '.join(documentors)))
' He has set us free.')
self.credits_text_edit.setPlainText(
'%s\n'
' %s\n'
'\n'
'%s\n'
' %s\n'
'\n'
'%s\n'
' %s\n'
'\n'
'%s\n'
' %s\n'
'\n'
'%s\n'
' %s\n'
'\n'
'%s\n'
' %s\n'
' %s\n'
' %s\n'
' %s\n'
' %s\n'
' %s\n'
' %s\n'
' %s\n'
' %s\n'
' %s\n'
' %s\n'
' %s\n'
' %s\n'
' %s\n'
' %s\n'
' %s\n'
' %s\n'
' %s\n'
' %s\n'
' %s\n'
' %s\n'
' %s\n'
' %s\n'
' %s\n'
' %s\n'
' %s\n'
' %s\n'
' %s\n'
' %s\n'
' %s\n'
' %s\n'
' %s\n'
' %s\n'
' %s\n'
' %s\n'
' %s\n'
' %s\n'
' %s\n'
' %s\n'
' %s\n'
' %s\n'
' %s\n'
' %s\n'
' %s\n'
'\n'
'%s\n'
' %s\n'
'\n'
'%s\n%s' %
(project_lead, lead,
devs, '\n '.join(developers),
cons, '\n '.join(contributors),
tests, '\n '.join(testers),
packs, '\n '.join(packagers),
laters,
af, '\n '.join(translators['af']),
cs, '\n '.join(translators['cs']),
da, '\n '.join(translators['da']),
de, '\n '.join(translators['de']),
el, '\n '.join(translators['el']),
gb, '\n '.join(translators['en_GB']),
enza, '\n '.join(translators['en_ZA']),
es, '\n '.join(translators['es']),
et, '\n '.join(translators['et']),
fi, '\n '.join(translators['fi']),
fr, '\n '.join(translators['fr']),
hu, '\n '.join(translators['hu']),
ind, '\n '.join(translators['id']),
ja, '\n '.join(translators['ja']),
nb, '\n '.join(translators['nb']),
nl, '\n '.join(translators['nl']),
pl, '\n '.join(translators['pl']),
ptbr, '\n '.join(translators['pt_BR']),
ru, '\n '.join(translators['ru']),
sv, '\n '.join(translators['sv']),
talk, '\n '.join(translators['ta_LK']),
zhcn, '\n '.join(translators['zh_CN']),
documentation, '\n '.join(documentors),
built_with, final_credit))
self.about_notebook.setTabText(self.about_notebook.indexOf(self.credits_tab),
translate('OpenLP.AboutForm', 'Credits'))
copyright_note = translate('OpenLP.AboutForm',

View File

@ -80,6 +80,9 @@ class AdvancedTab(SettingsTab):
self.expand_service_item_check_box = QtGui.QCheckBox(self.ui_group_box)
self.expand_service_item_check_box.setObjectName('expand_service_item_check_box')
self.ui_layout.addRow(self.expand_service_item_check_box)
self.search_as_type_check_box = QtGui.QCheckBox(self.ui_group_box)
self.search_as_type_check_box.setObjectName('SearchAsType_check_box')
self.ui_layout.addRow(self.search_as_type_check_box)
self.enable_auto_close_check_box = QtGui.QCheckBox(self.ui_group_box)
self.enable_auto_close_check_box.setObjectName('enable_auto_close_check_box')
self.ui_layout.addRow(self.enable_auto_close_check_box)
@ -251,6 +254,7 @@ class AdvancedTab(SettingsTab):
self.end_slide_radio_button.clicked.connect(self.on_end_slide_button_clicked)
self.wrap_slide_radio_button.clicked.connect(self.on_wrap_slide_button_clicked)
self.next_item_radio_button.clicked.connect(self.on_next_item_button_clicked)
self.search_as_type_check_box.stateChanged.connect(self.on_search_as_type_check_box_changed)
def retranslateUi(self):
"""
@ -319,6 +323,7 @@ class AdvancedTab(SettingsTab):
self.end_slide_radio_button.setText(translate('OpenLP.GeneralTab', '&Remain on Slide'))
self.wrap_slide_radio_button.setText(translate('OpenLP.GeneralTab', '&Wrap around'))
self.next_item_radio_button.setText(translate('OpenLP.GeneralTab', '&Move to next/previous service item'))
self.search_as_type_check_box.setText(translate('SongsPlugin.GeneralTab', 'Enable search as you type'))
def load(self):
"""
@ -349,6 +354,8 @@ class AdvancedTab(SettingsTab):
self.default_color = settings.value('default color')
self.default_file_edit.setText(settings.value('default image'))
self.slide_limits = settings.value('slide limits')
self.is_search_as_you_type_enabled = settings.value('search as type')
self.search_as_type_check_box.setChecked(self.is_search_as_you_type_enabled)
# Prevent the dialog displayed by the alternate_rows_check_box to display.
self.alternate_rows_check_box.blockSignals(True)
self.alternate_rows_check_box.setChecked(settings.value('alternate rows'))
@ -424,8 +431,14 @@ class AdvancedTab(SettingsTab):
settings.setValue('x11 bypass wm', self.x11_bypass_check_box.isChecked())
self.settings_form.register_post_process('config_screen_changed')
self.settings_form.register_post_process('slidecontroller_update_slide_limits')
settings.setValue('search as type', self.is_search_as_you_type_enabled)
settings.endGroup()
def on_search_as_type_check_box_changed(self, check_state):
self.is_search_as_you_type_enabled = (check_state == QtCore.Qt.Checked)
self.settings_form.register_post_process('songs_config_updated')
self.settings_form.register_post_process('custom_config_updated')
def cancel(self):
"""
Dialogue was cancelled, remove any pending data path change.

View File

@ -173,6 +173,19 @@ class MainDisplay(OpenLPMixin, Display, RegistryProperties):
Registry().register_function('live_display_hide', self.hide_display)
Registry().register_function('live_display_show', self.show_display)
Registry().register_function('update_display_css', self.css_changed)
self.close_display = False
def closeEvent(self, event):
"""
Catch the close event, and check that the close event is triggered by OpenLP closing the display.
On Windows this event can be triggered by pressing ALT+F4, which we want to ignore.
:param event: The triggered event
"""
if self.close_display:
super().closeEvent(event)
else:
event.ignore()
def close(self):
"""
@ -182,6 +195,7 @@ class MainDisplay(OpenLPMixin, Display, RegistryProperties):
Registry().remove_function('live_display_hide', self.hide_display)
Registry().remove_function('live_display_show', self.show_display)
Registry().remove_function('update_display_css', self.css_changed)
self.close_display = True
super().close()
def set_transparency(self, enabled):

View File

@ -312,6 +312,13 @@ class Ui_MainWindow(object):
icon=':/system/system_help_contents.png',
can_shortcuts=True,
category=UiStrings().Help, triggers=self.on_offline_help_clicked)
elif is_macosx():
self.local_help_file = os.path.join(AppLocation.get_directory(AppLocation.AppDir),
'..', 'Resources', 'OpenLP.help')
self.offline_help_item = create_action(main_window, 'offlineHelpItem',
icon=':/system/system_help_contents.png',
can_shortcuts=True,
category=UiStrings().Help, triggers=self.on_offline_help_clicked)
self.on_line_help_item = create_action(main_window, 'onlineHelpItem',
icon=':/system/system_online_help.png',
can_shortcuts=True,
@ -354,7 +361,7 @@ class Ui_MainWindow(object):
add_actions(self.tools_menu, (self.tools_open_data_folder, None))
add_actions(self.tools_menu, (self.tools_first_time_wizard, None))
add_actions(self.tools_menu, [self.update_theme_images])
if is_win():
if (is_win() or is_macosx()) and (hasattr(sys, 'frozen') and sys.frozen == 1):
add_actions(self.help_menu, (self.offline_help_item, self.on_line_help_item, None, self.web_site_item,
self.about_item))
else:
@ -382,7 +389,7 @@ class Ui_MainWindow(object):
self.file_menu.setTitle(translate('OpenLP.MainWindow', '&File'))
self.file_import_menu.setTitle(translate('OpenLP.MainWindow', '&Import'))
self.file_export_menu.setTitle(translate('OpenLP.MainWindow', '&Export'))
self.recent_files_menu.setTitle(translate('OpenLP.MainWindow', '&Recent Files'))
self.recent_files_menu.setTitle(translate('OpenLP.MainWindow', '&Recent Services'))
self.view_menu.setTitle(translate('OpenLP.MainWindow', '&View'))
self.view_mode_menu.setTitle(translate('OpenLP.MainWindow', 'M&ode'))
self.tools_menu.setTitle(translate('OpenLP.MainWindow', '&Tools'))
@ -393,16 +400,16 @@ class Ui_MainWindow(object):
self.service_manager_dock.setWindowTitle(translate('OpenLP.MainWindow', 'Service Manager'))
self.theme_manager_dock.setWindowTitle(translate('OpenLP.MainWindow', 'Theme Manager'))
self.projector_manager_dock.setWindowTitle(translate('OpenLP.MainWindow', 'Projector Manager'))
self.file_new_item.setText(translate('OpenLP.MainWindow', '&New'))
self.file_new_item.setText(translate('OpenLP.MainWindow', '&New Service'))
self.file_new_item.setToolTip(UiStrings().NewService)
self.file_new_item.setStatusTip(UiStrings().CreateService)
self.file_open_item.setText(translate('OpenLP.MainWindow', '&Open'))
self.file_open_item.setText(translate('OpenLP.MainWindow', '&Open Service'))
self.file_open_item.setToolTip(UiStrings().OpenService)
self.file_open_item.setStatusTip(translate('OpenLP.MainWindow', 'Open an existing service.'))
self.file_save_item.setText(translate('OpenLP.MainWindow', '&Save'))
self.file_save_item.setText(translate('OpenLP.MainWindow', '&Save Service'))
self.file_save_item.setToolTip(UiStrings().SaveService)
self.file_save_item.setStatusTip(translate('OpenLP.MainWindow', 'Save the current service to disk.'))
self.file_save_as_item.setText(translate('OpenLP.MainWindow', 'Save &As...'))
self.file_save_as_item.setText(translate('OpenLP.MainWindow', 'Save Service &As...'))
self.file_save_as_item.setToolTip(translate('OpenLP.MainWindow', 'Save Service As'))
self.file_save_as_item.setStatusTip(translate('OpenLP.MainWindow',
'Save the current service under a new name.'))
@ -449,11 +456,11 @@ class Ui_MainWindow(object):
self.lock_panel.setText(translate('OpenLP.MainWindow', 'L&ock Panels'))
self.lock_panel.setStatusTip(translate('OpenLP.MainWindow', 'Prevent the panels being moved.'))
self.view_live_panel.setStatusTip(translate('OpenLP.MainWindow', 'Toggle the visibility of the live panel.'))
self.settings_plugin_list_item.setText(translate('OpenLP.MainWindow', '&Plugin List'))
self.settings_plugin_list_item.setText(translate('OpenLP.MainWindow', '&Manage Plugins'))
self.settings_plugin_list_item.setStatusTip(translate('OpenLP.MainWindow', 'List the Plugins'))
self.about_item.setText(translate('OpenLP.MainWindow', '&About'))
self.about_item.setStatusTip(translate('OpenLP.MainWindow', 'More information about OpenLP'))
if is_win():
if is_win() or is_macosx():
self.offline_help_item.setText(translate('OpenLP.MainWindow', '&User Guide'))
self.on_line_help_item.setText(translate('OpenLP.MainWindow', '&Online Help'))
self.search_shortcut_action.setText(UiStrings().Search)
@ -498,7 +505,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow, RegistryProperties):
super(MainWindow, self).__init__()
Registry().register('main_window', self)
self.clipboard = self.application.clipboard()
self.arguments = self.application.args
self.arguments = ''.join(self.application.args)
# Set up settings sections for the main application (not for use by plugins).
self.ui_settings_section = 'user interface'
self.general_settings_section = 'core'
@ -627,7 +634,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow, RegistryProperties):
self.live_controller.display.setFocus()
self.activateWindow()
if self.arguments:
self.open_cmd_line_files()
self.open_cmd_line_files(self.arguments)
elif Settings().value(self.general_settings_section + '/auto open'):
self.service_manager_contents.load_last_file()
view_mode = Settings().value('%s/view mode' % self.general_settings_section)
@ -762,7 +769,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow, RegistryProperties):
"""
Load the local OpenLP help file
"""
os.startfile(self.local_help_file)
QtGui.QDesktopServices.openUrl(QtCore.QUrl("file:///" + self.local_help_file))
def on_online_help_clicked(self):
"""
@ -1409,14 +1416,10 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow, RegistryProperties):
settings.remove('advanced/data path')
self.application.set_normal_cursor()
def open_cmd_line_files(self):
def open_cmd_line_files(self, filename):
"""
Open files passed in through command line arguments
"""
args = []
for a in self.arguments:
args.extend([a])
for filename in args:
if not isinstance(filename, str):
filename = str(filename, sys.getfilesystemencoding())
if filename.endswith(('.osz', '.oszl')):

View File

@ -514,8 +514,13 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties):
:param display: Which display to use
:param service_item: The ServiceItem containing the details to be played.
"""
used_players = get_media_players()[0]
used_players = get_media_players()
default_player = used_players[0]
if service_item.processor and service_item.processor != UiStrings().Automatic:
# check to see if the player is usable else use the default one.
if not service_item.processor.lower() in used_players:
used_players = default_player
else:
used_players = [service_item.processor.lower()]
# If no player, we can't play
if not used_players:

View File

@ -60,7 +60,7 @@ if sys.version_info[0] > 2:
"""Translate string or bytes to bytes.
"""
if isinstance(s, str):
return bytes(s, sys.getfilesystemencoding())
return s.encode()
else:
return s

View File

@ -80,10 +80,8 @@ def get_vlc():
if is_win():
if not isinstance(e, WindowsError) and e.winerror != 126:
raise
elif is_macosx():
pass
else:
raise
pass
if is_vlc_available:
try:
VERSION = vlc.libvlc_get_version().decode('UTF-8')
@ -103,9 +101,13 @@ def get_vlc():
# On linux we need to initialise X threads, but not when running tests.
# This needs to happen on module load and not in get_vlc(), otherwise it can cause crashes on some DE on some setups
# (reported on Gnome3, Unity, Cinnamon, all GTK+ based) when using native filedialogs...
if get_vlc() and is_linux() and 'nose' not in sys.argv[0]:
if is_linux() and 'nose' not in sys.argv[0] and get_vlc():
import ctypes
try:
try:
x11 = ctypes.cdll.LoadLibrary('libX11.so.6')
except OSError:
# If libx11.so.6 was not found, fallback to more generic libx11.so
x11 = ctypes.cdll.LoadLibrary('libX11.so')
x11.XInitThreads()
except:

View File

@ -33,21 +33,21 @@ class Ui_PluginViewDialog(object):
"""
The UI of the plugin view dialog
"""
def setupUi(self, pluginViewDialog):
def setupUi(self, plugin_view_dialog):
"""
Set up the UI
"""
pluginViewDialog.setObjectName('pluginViewDialog')
pluginViewDialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg'))
pluginViewDialog.setWindowModality(QtCore.Qt.ApplicationModal)
self.plugin_layout = QtGui.QVBoxLayout(pluginViewDialog)
plugin_view_dialog.setObjectName('plugin_view_dialog')
plugin_view_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo.svg'))
plugin_view_dialog.setWindowModality(QtCore.Qt.ApplicationModal)
self.plugin_layout = QtGui.QVBoxLayout(plugin_view_dialog)
self.plugin_layout.setObjectName('plugin_layout')
self.list_layout = QtGui.QHBoxLayout()
self.list_layout.setObjectName('list_layout')
self.plugin_list_widget = QtGui.QListWidget(pluginViewDialog)
self.plugin_list_widget = QtGui.QListWidget(plugin_view_dialog)
self.plugin_list_widget.setObjectName('plugin_list_widget')
self.list_layout.addWidget(self.plugin_list_widget)
self.plugin_info_group_box = QtGui.QGroupBox(pluginViewDialog)
self.plugin_info_group_box = QtGui.QGroupBox(plugin_view_dialog)
self.plugin_info_group_box.setObjectName('plugin_info_group_box')
self.plugin_info_layout = QtGui.QFormLayout(self.plugin_info_group_box)
self.plugin_info_layout.setObjectName('plugin_info_layout')
@ -70,15 +70,15 @@ class Ui_PluginViewDialog(object):
self.plugin_info_layout.addRow(self.about_label, self.about_text_browser)
self.list_layout.addWidget(self.plugin_info_group_box)
self.plugin_layout.addLayout(self.list_layout)
self.button_box = create_button_box(pluginViewDialog, 'button_box', ['ok'])
self.button_box = create_button_box(plugin_view_dialog, 'button_box', ['ok'])
self.plugin_layout.addWidget(self.button_box)
self.retranslateUi(pluginViewDialog)
self.retranslateUi(plugin_view_dialog)
def retranslateUi(self, pluginViewDialog):
def retranslateUi(self, plugin_view_dialog):
"""
Translate the UI on the fly
"""
pluginViewDialog.setWindowTitle(translate('OpenLP.PluginForm', 'Plugin List'))
plugin_view_dialog.setWindowTitle(translate('OpenLP.PluginForm', 'Manage Plugins'))
self.plugin_info_group_box.setTitle(translate('OpenLP.PluginForm', 'Plugin Details'))
self.version_label.setText('%s:' % UiStrings().Version)
self.about_label.setText('%s:' % UiStrings().About)

View File

@ -162,7 +162,7 @@ class PrintServiceForm(QtGui.QDialog, Ui_PrintServiceDialog, RegistryProperties)
html_data = self._add_element('html')
self._add_element('head', parent=html_data)
self._add_element('title', self.title_line_edit.text(), html_data.head)
css_path = os.path.join(AppLocation.get_data_path(), 'service_print.css')
css_path = os.path.join(AppLocation.get_data_path(), 'serviceprint', 'service_print.css')
custom_css = get_text_file_string(css_path)
if not custom_css:
custom_css = DEFAULT_CSS

View File

@ -473,8 +473,9 @@ class ProjectorManager(OpenLPMixin, RegistryMixin, QWidget, Ui_ProjectorManager,
return
projector = list_item.data(QtCore.Qt.UserRole)
msg = QtGui.QMessageBox()
msg.setText('Delete projector (%s) %s?' % (projector.link.ip, projector.link.name))
msg.setInformativeText('Are you sure you want to delete this projector?')
msg.setText(translate('OpenLP.ProjectorManager', 'Delete projector (%s) %s?') % (projector.link.ip,
projector.link.name))
msg.setInformativeText(translate('OpenLP.ProjectorManager', 'Are you sure you want to delete this projector?'))
msg.setStandardButtons(msg.Cancel | msg.Ok)
msg.setDefaultButton(msg.Cancel)
ans = msg.exec_()

View File

@ -44,6 +44,8 @@ def source_group(inputs, source_text):
Return a dictionary where key is source[0] and values are inputs
grouped by source[0].
::
source_text = dict{"key1": "key1-text",
"key2": "key2-text",
...}
@ -81,13 +83,15 @@ def Build_Tab(group, source_key, default, projector, projectordb, edit=False):
Create the radio button page for a tab.
Dictionary will be a 1-key entry where key=tab to setup, val=list of inputs.
::
source_key: {"groupkey1": {"key11": "key11-text",
"key12": "key12-text",
...
},
"groupkey2": {"key21": "key21-text",
"key22": "key22-text",
....
...
},
...
}

View File

@ -480,7 +480,7 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtGui.QWidget, Ui_ServiceManage
"""
Create the initial service array with the base items to be saved.
:return service array
:return: service array
"""
service = []
core = {'lite-service': self._save_lite,

View File

@ -902,7 +902,8 @@ class SlideController(DisplayController, RegistryProperties):
# This avoids the service theme/desktop flashing on screen
# However opening a new item of the same type will automatically
# close the previous, so make sure we don't close the new one.
if old_item.is_command() and not service_item.is_command():
if old_item.is_command() and not service_item.is_command() or \
old_item.is_command() and not old_item.is_media() and service_item.is_media():
Registry().execute('%s_stop' % old_item.name.lower(), [old_item, self.is_live])
if old_item.is_media() and not service_item.is_media():
self.on_media_close()

View File

@ -503,7 +503,7 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtGui.QWidget, Ui_ThemeManager, R
Returns a theme object from an XML file
:param theme_name: Name of the theme to load from file
:return The theme object.
:return: The theme object.
"""
self.log_debug('get theme data for theme %s' % theme_name)
xml_file = os.path.join(self.path, str(theme_name), str(theme_name) + '.xml')
@ -519,7 +519,7 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtGui.QWidget, Ui_ThemeManager, R
Display a warning box to the user that a theme already exists
:param theme_name: Name of the theme.
:return Confirm if the theme is to be overwritten.
:return: Confirm if the theme is to be overwritten.
"""
ret = QtGui.QMessageBox.question(self, translate('OpenLP.ThemeManager', 'Theme Already Exists'),
translate('OpenLP.ThemeManager',
@ -607,7 +607,7 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtGui.QWidget, Ui_ThemeManager, R
Check if theme already exists and displays error message
:param theme_name: Name of the Theme to test
:return True or False if theme exists
:return: True or False if theme exists
"""
theme_dir = os.path.join(self.path, theme_name)
if os.path.exists(theme_dir):
@ -718,7 +718,7 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtGui.QWidget, Ui_ThemeManager, R
:param theme_xml: The Theme data object.
:param image_path: Where the theme image is stored
:return Theme data.
:return: Theme data.
"""
theme = ThemeXML()
theme.parse(theme_xml)
@ -734,7 +734,7 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtGui.QWidget, Ui_ThemeManager, R
:param confirm_text: Confirm message text to be displayed.
:param test_plugin: Do we check the plugins for theme usage.
:param confirm: Do we display a confirm box before run checks.
:return True or False depending on the validity.
:return: True or False depending on the validity.
"""
self.global_theme = Settings().value(self.settings_section + '/global theme')
if check_item_selected(self.theme_list_widget, select_text):
@ -755,12 +755,19 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtGui.QWidget, Ui_ThemeManager, R
return False
# check for use in the system else where.
if test_plugin:
plugin_usage = ""
for plugin in self.plugin_manager.plugins:
if plugin.uses_theme(theme):
critical_error_message_box(translate('OpenLP.ThemeManager', 'Validation Error'),
translate('OpenLP.ThemeManager',
'Theme %s is used in the %s plugin.')
% (theme, plugin.name))
used_count = plugin.uses_theme(theme)
if used_count:
plugin_usage = "%s%s" % (plugin_usage, (translate('OpenLP.ThemeManager',
'%s time(s) by %s') %
(used_count, plugin.name)))
plugin_usage = "%s\n" % plugin_usage
if plugin_usage:
critical_error_message_box(translate('OpenLP.ThemeManager', 'Unable to delete theme'),
translate('OpenLP.ThemeManager', 'Theme is currently used \n\n%s') %
plugin_usage)
return False
return True
return False

View File

@ -404,6 +404,7 @@ def get_web_page(url, header=None, update_openlp=False):
try:
page = urllib.request.urlopen(req, timeout=CONNECTION_TIMEOUT)
log.debug('Downloaded page {}'.format(page.geturl()))
break
except urllib.error.URLError as err:
log.exception('URLError on {}'.format(url))
log.exception('URLError: {}'.format(err.reason))

View File

@ -48,7 +48,11 @@ class AlertsManager(OpenLPMixin, RegistryMixin, QtCore.QObject, RegistryProperti
:param message: The message text to be displayed
"""
if message:
self.display_alert(message[0])
text = message[0]
# remove line breaks as these crash javascript code on display
while '\n' in text:
text = text.replace('\n', ' ')
self.display_alert(text)
def display_alert(self, text=''):
"""

View File

@ -178,12 +178,14 @@ class BiblePlugin(Plugin):
def uses_theme(self, theme):
"""
Called to find out if the bible plugin is currently using a theme. Returns ``True`` if the theme is being used,
otherwise returns ``False``.
Called to find out if the bible plugin is currently using a theme. Returns ``1`` if the theme is being used,
otherwise returns ``0``.
:param theme: The theme
"""
return str(self.settings_tab.bible_theme) == theme
if str(self.settings_tab.bible_theme) == theme:
return 1
return 0
def rename_theme(self, old_theme, new_theme):
"""

View File

@ -517,6 +517,8 @@ class BibleImportForm(OpenLPWizard):
critical_error_message_box(translate('BiblesPlugin.ImportWizardForm', 'Error during download'),
translate('BiblesPlugin.ImportWizardForm',
'An error occurred while downloading the list of bibles from %s.'))
bibles = None
if bibles:
self.web_bible_list[download_type] = {}
for (bible_name, bible_key, language_code) in bibles:
self.web_bible_list[download_type][bible_name] = (bible_key, language_code)

View File

@ -37,7 +37,7 @@ from openlp.core.common import Registry, RegistryProperties, AppLocation, transl
from openlp.core.lib.db import BaseModel, init_db, Manager
from openlp.core.lib.ui import critical_error_message_box
from openlp.core.utils import clean_filename
from . import upgrade
from openlp.plugins.bibles.lib import upgrade
log = logging.getLogger(__name__)
@ -476,16 +476,6 @@ class BibleDB(QtCore.QObject, Manager, RegistryProperties):
self.save_meta('language_id', language_id)
return language_id
def is_old_database(self):
"""
Returns ``True`` if it is a bible database, which has been created prior to 1.9.6.
"""
try:
self.session.query(Book).all()
except:
return True
return False
def dump_bible(self):
"""
Utility debugging method to dump the contents of a bible.

View File

@ -27,7 +27,6 @@ import re
import socket
import urllib.parse
import urllib.error
from html.parser import HTMLParseError
from bs4 import BeautifulSoup, NavigableString, Tag
@ -290,7 +289,7 @@ class BGExtract(RegistryProperties):
page_source = str(page_source, 'cp1251')
try:
soup = BeautifulSoup(page_source)
except HTMLParseError:
except Exception:
log.error('BeautifulSoup could not parse the Bible page.')
send_error_message('parse')
return None
@ -762,7 +761,7 @@ def get_soup_for_bible_ref(reference_url, header=None, pre_parse_regex=None, pre
try:
soup = BeautifulSoup(page_source)
CLEANER_REGEX.sub('', str(soup))
except HTMLParseError:
except Exception:
log.exception('BeautifulSoup could not parse the bible page.')
if not soup:
send_error_message('parse')

View File

@ -129,11 +129,6 @@ class BibleManager(RegistryProperties):
bible.session.close()
delete_file(os.path.join(self.path, filename))
continue
# Find old database versions.
if bible.is_old_database():
self.old_bible_databases.append([filename, name])
bible.session.close()
continue
log.debug('Bible Name: "%s"', name)
self.db_cache[name] = bible
# Look to see if lazy load bible exists and get create getter.

View File

@ -30,7 +30,8 @@ from openlp.core.lib.searchedit import SearchEdit
from openlp.core.lib.ui import set_case_insensitive_completer, create_horizontal_adjusting_combo_box, \
critical_error_message_box, find_and_set_in_combo_box, build_icon
from openlp.core.utils import get_locale_key
from openlp.plugins.bibles.forms import BibleImportForm, EditBibleForm
from openlp.plugins.bibles.forms.bibleimportform import BibleImportForm
from openlp.plugins.bibles.forms.editbibleform import EditBibleForm
from openlp.plugins.bibles.lib import LayoutStyle, DisplayStyle, VerseReferenceList, get_reference_separator, \
LanguageSelection, BibleStrings
from openlp.plugins.bibles.lib.db import BiblesResourcesDB

View File

@ -72,11 +72,9 @@ class CustomPlugin(Plugin):
"""
Called to find out if the custom plugin is currently using a theme.
Returns True if the theme is being used, otherwise returns False.
Returns count of the times the theme is used.
"""
if self.db_manager.get_all_objects(CustomSlide, CustomSlide.theme_name == theme):
return True
return False
return len(self.db_manager.get_all_objects(CustomSlide, CustomSlide.theme_name == theme))
def rename_theme(self, old_theme, new_theme):
"""

View File

@ -85,6 +85,7 @@ class CustomMediaItem(MediaManagerItem):
"""
log.debug('Config loaded')
self.add_custom_from_service = Settings().value(self.settings_section + '/add custom from service')
self.is_search_as_you_type_enabled = Settings().value('advanced/search as type')
def retranslateUi(self):
"""
@ -269,6 +270,7 @@ class CustomMediaItem(MediaManagerItem):
:param text: The search text
"""
if self.is_search_as_you_type_enabled:
search_length = 2
if len(text) > search_length:
self.on_search_text_button_clicked()

View File

@ -67,36 +67,13 @@ class ImagePlugin(Plugin):
'provided by the theme.')
return about_text
def app_startup(self):
"""
Perform tasks on application startup.
"""
# TODO: Can be removed when the upgrade path from 2.0.x to 2.2.x is no longer needed
Plugin.app_startup(self)
# Convert old settings-based image list to the database.
files_from_config = Settings().get_files_from_config(self)
if files_from_config:
for file in files_from_config:
filename = os.path.split(file)[1]
thumb = os.path.join(self.media_item.service_path, filename)
try:
os.remove(thumb)
except:
pass
log.debug('Importing images list from old config: %s' % files_from_config)
self.media_item.save_new_images_list(files_from_config)
def upgrade_settings(self, settings):
"""
Upgrade the settings of this plugin.
:param settings: The Settings object containing the old settings.
"""
# TODO: Can be removed when the upgrade path from 2.0.x to 2.2.x is no longer needed
files_from_config = settings.get_files_from_config(self)
if files_from_config:
log.debug('Importing images list from old config: %s' % files_from_config)
self.media_item.save_new_images_list(files_from_config)
pass
def set_plugin_text_strings(self):
"""

View File

@ -119,14 +119,6 @@ class ImageMediaItem(MediaManagerItem):
icon=':/general/general_edit.png',
triggers=self.on_edit_click)
create_widget_action(self.list_view, separator=True)
if self.has_delete_icon:
create_widget_action(
self.list_view,
'listView%s%sItem' % (self.plugin.name.title(), StringContent.Delete.title()),
text=self.plugin.get_string(StringContent.Delete)['title'],
icon=':/general/general_delete.png',
can_shortcuts=True, triggers=self.on_delete_click)
create_widget_action(self.list_view, separator=True)
create_widget_action(
self.list_view,
'listView%s%sItem' % (self.plugin.name.title(), StringContent.Preview.title()),
@ -155,6 +147,14 @@ class ImageMediaItem(MediaManagerItem):
text=translate('OpenLP.MediaManagerItem', '&Add to selected Service Item'),
icon=':/general/general_add.png',
triggers=self.on_add_edit_click)
create_widget_action(self.list_view, separator=True)
if self.has_delete_icon:
create_widget_action(
self.list_view,
'listView%s%sItem' % (self.plugin.name.title(), StringContent.Delete.title()),
text=self.plugin.get_string(StringContent.Delete)['title'],
icon=':/general/general_delete.png',
can_shortcuts=True, triggers=self.on_delete_click)
self.add_custom_context_actions()
# Create the context menu and add all actions from the list_view.
self.menu = QtGui.QMenu()

View File

@ -43,11 +43,6 @@ log = logging.getLogger(__name__)
CLAPPERBOARD = ':/media/slidecontroller_multimedia.png'
OPTICAL = ':/media/media_optical.png'
VIDEO_ICON = build_icon(':/media/media_video.png')
AUDIO_ICON = build_icon(':/media/media_audio.png')
OPTICAL_ICON = build_icon(OPTICAL)
ERROR_ICON = build_icon(':/general/general_delete.png')
class MediaMediaItem(MediaManagerItem, RegistryProperties):
@ -57,10 +52,20 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties):
log.info('%s MediaMediaItem loaded', __name__)
def __init__(self, parent, plugin):
self.setup()
super(MediaMediaItem, self).__init__(parent, plugin)
def setup(self):
"""
Allow early setup to be mocked.
"""
self.icon_path = 'images/image'
self.background = False
self.automatic = ''
super(MediaMediaItem, self).__init__(parent, plugin)
self.optical_icon = build_icon(':/media/media_optical.png')
self.video_icon = build_icon(':/media/media_video.png')
self.audio_icon = build_icon(':/media/media_audio.png')
self.error_icon = build_icon(':/general/general_delete.png')
def setup_item(self):
"""
@ -130,7 +135,8 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties):
optical_button_text = translate('MediaPlugin.MediaItem', 'Load CD/DVD')
optical_button_tooltip = translate('MediaPlugin.MediaItem',
'Load CD/DVD - only supported when VLC is installed and enabled')
self.load_optical = self.toolbar.add_toolbar_action('load_optical', icon=OPTICAL_ICON, text=optical_button_text,
self.load_optical = self.toolbar.add_toolbar_action('load_optical', icon=self.optical_icon,
text=optical_button_text,
tooltip=optical_button_tooltip,
triggers=self.on_load_optical)
if disable_optical_button_text:
@ -351,14 +357,14 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties):
# Handle optical based item
(file_name, title, audio_track, subtitle_track, start, end, clip_name) = parse_optical_path(track)
item_name = QtGui.QListWidgetItem(clip_name)
item_name.setIcon(OPTICAL_ICON)
item_name.setIcon(self.optical_icon)
item_name.setData(QtCore.Qt.UserRole, track)
item_name.setToolTip('%s@%s-%s' % (file_name, format_milliseconds(start), format_milliseconds(end)))
elif not os.path.exists(track):
# File doesn't exist, mark as error.
file_name = os.path.split(str(track))[1]
item_name = QtGui.QListWidgetItem(file_name)
item_name.setIcon(ERROR_ICON)
item_name.setIcon(self.error_icon)
item_name.setData(QtCore.Qt.UserRole, track)
item_name.setToolTip(track)
elif track_info.isFile():
@ -366,9 +372,9 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties):
file_name = os.path.split(str(track))[1]
item_name = QtGui.QListWidgetItem(file_name)
if '*.%s' % (file_name.split('.')[-1].lower()) in self.media_controller.audio_extensions_list:
item_name.setIcon(AUDIO_ICON)
item_name.setIcon(self.audio_icon)
else:
item_name.setIcon(VIDEO_ICON)
item_name.setIcon(self.video_icon)
item_name.setData(QtCore.Qt.UserRole, track)
item_name.setToolTip(track)
if item_name:

View File

@ -290,6 +290,13 @@ class MessageListener(object):
log.info('Message Listener loaded')
def __init__(self, media_item):
self._setup(media_item)
def _setup(self, media_item):
"""
Start up code moved out to make mocking easier
:param media_item: The plugin media item handing Presentations
"""
self.controllers = media_item.controllers
self.media_item = media_item
self.preview_handler = Controller(False)
@ -346,6 +353,12 @@ class MessageListener(object):
self.handler = self.media_item.find_controller_by_type(file)
if not self.handler:
return
else:
# the saved handler is not present so need to use one based on file suffix.
if not self.controllers[self.handler].available:
self.handler = self.media_item.find_controller_by_type(file)
if not self.handler:
return
if is_live:
controller = self.live_handler
else:

View File

@ -137,22 +137,6 @@ class PresentationPlugin(Plugin):
self.register_controllers(controller)
return bool(self.controllers)
def app_startup(self):
"""
Perform tasks on application startup.
"""
# TODO: Can be removed when the upgrade path from 2.0.x to 2.2.x is no longer needed
super().app_startup()
files_from_config = Settings().value('presentations/presentations files')
for file in files_from_config:
try:
self.media_item.clean_up_thumbnails(file, True)
except AttributeError:
pass
self.media_item.list_view.clear()
Settings().setValue('presentations/thumbnail_scheme', 'md5')
self.media_item.validate_and_load(files_from_config)
def about(self):
"""
Return information about this plugin.

View File

@ -271,9 +271,15 @@ window.OpenLP = {
if (typeof value[0] !== "number"){
value[0] = OpenLP.escapeString(value[0])
}
var txt = "";
if (value[2].length > 0) {
txt = value[1] + " ( " + value[2] + " )";
} else {
txt = value[1];
}
ul.append($("<li>").append($("<a>").attr("href", "#options")
.attr("data-rel", "dialog").attr("value", value[0])
.click(OpenLP.showOptions).text(value[1])));
.click(OpenLP.showOptions).text(txt)));
});
}
ul.listview("refresh");

View File

@ -150,6 +150,7 @@ class HttpRouter(RegistryProperties):
self.routes = [
('^/$', {'function': self.serve_file, 'secure': False}),
('^/(stage)$', {'function': self.serve_file, 'secure': False}),
('^/(stage)/(.*)$', {'function': self.stages, '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}),
@ -170,6 +171,7 @@ class HttpRouter(RegistryProperties):
self.settings_section = 'remotes'
self.translate()
self.html_dir = os.path.join(AppLocation.get_directory(AppLocation.PluginsDir), 'remotes', 'html')
self.config_dir = os.path.join(AppLocation.get_data_path(), 'stages')
def do_post_processor(self):
"""
@ -309,10 +311,13 @@ class HttpRouter(RegistryProperties):
"""
Translate various strings in the mobile app.
"""
remote = translate('RemotePlugin.Mobile', 'Remote')
stage = translate('RemotePlugin.Mobile', 'Stage View')
live = translate('RemotePlugin.Mobile', 'Live View')
self.template_vars = {
'app_title': translate('RemotePlugin.Mobile', '%s Remote' % UiStrings().OLPV2x),
'stage_title': translate('RemotePlugin.Mobile', '%s Stage View' % UiStrings().OLPV2x),
'live_title': translate('RemotePlugin.Mobile', '%s Live View' % UiStrings().OLPV2x),
'app_title': "%s %s" % (UiStrings().OLPV2x, remote),
'stage_title': "%s %s" % (UiStrings().OLPV2x, stage),
'live_title': "%s %s" % (UiStrings().OLPV2x, live),
'service_manager': translate('RemotePlugin.Mobile', 'Service Manager'),
'slide_controller': translate('RemotePlugin.Mobile', 'Slide Controller'),
'alerts': translate('RemotePlugin.Mobile', 'Alerts'),
@ -337,24 +342,32 @@ class HttpRouter(RegistryProperties):
'settings': translate('RemotePlugin.Mobile', 'Settings'),
}
def serve_file(self, file_name=None):
def stages(self, url_path, file_name):
"""
Send a file to the socket. For now, just a subset of file types and must be top level inside the html folder.
If subfolders requested return 404, easier for security for the present.
Allow Stage view to be delivered with custom views.
Ultimately for i18n, this could first look for xx/file.html before falling back to file.html.
where xx is the language, e.g. 'en'
:param url_path: base path of the URL. Not used but passed by caller
:param file_name: file name with path
:return:
"""
log.debug('serve file request %s' % file_name)
if not file_name:
file_name = 'index.html'
elif file_name == 'stage':
file_name = 'stage.html'
elif file_name == 'main':
file_name = 'main.html'
path = os.path.normpath(os.path.join(self.html_dir, file_name))
if not path.startswith(self.html_dir):
parts = file_name.split('/')
if len(parts) == 1:
file_name = os.path.join(parts[0], 'stage.html')
elif len(parts) == 3:
file_name = os.path.join(parts[1], parts[2])
path = os.path.normpath(os.path.join(self.config_dir, file_name))
if not path.startswith(self.config_dir):
return self.do_not_found()
return self._process_file(path)
def _process_file(self, path):
"""
Common file processing code
:param path: path to file to be loaded
:return: web resource to be loaded
"""
content = None
ext, content_type = self.get_content_type(path)
file_handle = None
@ -377,10 +390,32 @@ class HttpRouter(RegistryProperties):
self.end_headers()
return content
def serve_file(self, file_name=None):
"""
Send a file to the socket. For now, just a subset of file types and must be top level inside the html folder.
If subfolders requested return 404, easier for security for the present.
Ultimately for i18n, this could first look for xx/file.html before falling back to file.html.
where xx is the language, e.g. 'en'
"""
log.debug('serve file request %s' % file_name)
if not file_name:
file_name = 'index.html'
elif file_name == 'stage':
file_name = 'stage.html'
elif file_name == 'main':
file_name = 'main.html'
path = os.path.normpath(os.path.join(self.html_dir, file_name))
if not path.startswith(self.html_dir):
return self.do_not_found()
return self._process_file(path)
def get_content_type(self, file_name):
"""
Examines the extension of the file and determines what the content_type should be, defaults to text/plain
Returns the extension and the content_type
:param file_name: name of file
"""
ext = os.path.splitext(file_name)[1]
content_type = FILE_TYPES.get(ext, 'text/plain')
@ -389,6 +424,10 @@ class HttpRouter(RegistryProperties):
def serve_thumbnail(self, controller_name=None, dimensions=None, file_name=None):
"""
Serve an image file. If not found return 404.
:param file_name: file name to be served
:param dimensions: image size
:param controller_name: controller to be called
"""
log.debug('serve thumbnail %s/thumbnails%s/%s' % (controller_name, dimensions, file_name))
supported_controllers = ['presentations', 'images']
@ -493,6 +532,8 @@ class HttpRouter(RegistryProperties):
def controller_text(self, var):
"""
Perform an action on the slide controller.
:param var: variable - not used
"""
log.debug("controller_text var = %s" % var)
current_item = self.live_controller.service_item
@ -626,6 +667,8 @@ class HttpRouter(RegistryProperties):
def go_live(self, plugin_name):
"""
Go live on an item of type ``plugin``.
:param plugin_name: name of plugin
"""
try:
request_id = json.loads(self.request_data)['request']['id']
@ -639,6 +682,8 @@ class HttpRouter(RegistryProperties):
def add_to_service(self, plugin_name):
"""
Add item of type ``plugin_name`` to the end of the service.
:param plugin_name: name of plugin to be called
"""
try:
request_id = json.loads(self.request_data)['request']['id']

View File

@ -167,7 +167,7 @@ class HTTPSServer(HTTPServer):
local_data = AppLocation.get_directory(AppLocation.DataDir)
self.socket = ssl.SSLSocket(
sock=socket.socket(self.address_family, self.socket_type),
ssl_version=ssl.PROTOCOL_TLSv1,
ssl_version=ssl.PROTOCOL_TLSv1_2,
certfile=os.path.join(local_data, 'remotes', 'openlp.crt'),
keyfile=os.path.join(local_data, 'remotes', 'openlp.key'),
server_side=True)

View File

@ -177,8 +177,9 @@ class RemoteTab(SettingsTab):
'Show thumbnails of non-text slides in remote and stage view.'))
self.android_app_group_box.setTitle(translate('RemotePlugin.RemoteTab', 'Android App'))
self.qr_description_label.setText(
translate('RemotePlugin.RemoteTab', 'Scan the QR code or click <a href="https://play.google.com/store/'
'apps/details?id=org.openlp.android">download</a> to install the Android app from Google Play.'))
translate('RemotePlugin.RemoteTab', 'Scan the QR code or click <a href="%s">download</a> to install the '
'Android app from Google Play.') %
'https://play.google.com/store/apps/details?id=org.openlp.android2')
self.https_settings_group_box.setTitle(translate('RemotePlugin.RemoteTab', 'HTTPS Server'))
self.https_error_label.setText(
translate('RemotePlugin.RemoteTab', 'Could not find an SSL certificate. The HTTPS server will not be '

View File

@ -228,6 +228,7 @@ class SongImportForm(OpenLPWizard, RegistryProperties):
:param listbox: A listbox (QListWidget).
:param filters: The file extension filters. It should contain the file descriptions as well as the file
extensions. For example::
'SongBeamer Files (*.sng)'
"""
if filters:

View File

@ -173,6 +173,7 @@ def init_schema(url):
Setup the songs database connection and initialise the database schema.
:param url: The database to setup
The song database contains the following tables:
* authors

View File

@ -36,7 +36,7 @@ class EasySlidesImport(SongImport):
Import songs exported from EasySlides
The format example is here:
http://wiki.openlp.org/Development:EasySlides_-_Song_Data_Format
http://wiki.openlp.org/Development:EasySlides\_-_Song_Data_Format
"""
def __init__(self, manager, **kwargs):
"""

View File

@ -171,7 +171,7 @@ class OpenOfficeImport(SongImport):
"""
log.debug('create property OpenOffice')
if is_win():
property_object = self.controller.manager.Bridge_GetStruct('com.sun.star.beans.PropertyValue')
property_object = self.ooo_manager.Bridge_GetStruct('com.sun.star.beans.PropertyValue')
else:
property_object = PropertyValue()
property_object.Name = name

View File

@ -71,7 +71,7 @@ class PowerSongImport(SongImport):
"""
Checks if source is a PowerSong 1.0 folder:
* is a directory
* contains at least one *.song file
* contains at least one \*.song file
"""
if os.path.isdir(import_source):
for file in os.listdir(import_source):

View File

@ -36,50 +36,49 @@ class SongProImport(SongImport):
**SongPro Song File Format:**
SongPro has the option to export under its File menu
This produces files containing single or multiple songs
The file is text with lines tagged with # followed by an identifier.
This is documented here: http://creationsoftware.com/ImportIdentifiers.php
An example here: http://creationsoftware.com/ExampleImportingManySongs.txt
#A - next line is the Song Author
#B - the lines following until next tagged line are the "Bridge" words
(can be in rtf or plain text) which we map as B1
#C - the lines following until next tagged line are the chorus words
(can be in rtf or plain text)
which we map as C1
#D - the lines following until next tagged line are the "Ending" words
(can be in rtf or plain text) which we map as E1
#E - this song ends here, so we process the song -
and start again at the next line
#G - next line is the Group
#M - next line is the Song Number
#N - next line are Notes
#R - next line is the SongCopyright
#O - next line is the Verse Sequence
#T - next line is the Song Title
#1 - #7 the lines following until next tagged line are the verse x words
(can be in rtf or plain text)
| SongPro has the option to export under its File menu
| This produces files containing single or multiple songs
| The file is text with lines tagged with # followed by an identifier.
| This is documented here: http://creationsoftware.com/ImportIdentifiers.php
| An example here: http://creationsoftware.com/ExampleImportingManySongs.txt
|
| #A - next line is the Song Author
| #B - the lines following until next tagged line are the "Bridge" words
| (can be in rtf or plain text) which we map as B1
| #C - the lines following until next tagged line are the chorus words
| (can be in rtf or plain text)
| which we map as C1
| #D - the lines following until next tagged line are the "Ending" words
| (can be in rtf or plain text) which we map as E1
| #E - this song ends here, so we process the song -
| and start again at the next line
| #G - next line is the Group
| #M - next line is the Song Number
| #N - next line are Notes
| #R - next line is the SongCopyright
| #O - next line is the Verse Sequence
| #T - next line is the Song Title
| #1 - #7 the lines following until next tagged line are the verse x words
| (can be in rtf or plain text)
"""
def __init__(self, manager, **kwargs):
"""
Initialise the SongPro importer.
"""
SongImport.__init__(self, manager, **kwargs)
super(SongProImport, self).__init__(manager, **kwargs)
def do_import(self):
"""
Receive a single file or a list of files to import.
"""
self.encoding = None
with open(self.import_source, 'r') as songs_file:
with open(self.import_source, 'rt') as songs_file:
self.import_wizard.progress_bar.setMaximum(0)
tag = ''
text = ''
for file_line in songs_file:
if self.stop_import_flag:
break
file_line = str(file_line, 'cp1252')
file_text = file_line.rstrip()
if file_text and file_text[0] == '#':
self.process_section(tag, text.rstrip())
@ -87,6 +86,7 @@ class SongProImport(SongImport):
text = ''
else:
text += file_line
self.finish()
def process_section(self, tag, text):
"""

View File

@ -60,16 +60,16 @@ class SongShowPlusImport(SongImport):
* Each piece of data in the song file has some information that precedes it.
* The general format of this data is as follows:
4 Bytes, forming a 32 bit number, a key if you will, this describes what the data is (see blockKey below)
4 Bytes, forming a 32 bit number, which is the number of bytes until the next block starts
1 Byte, which tells how many bytes follows
1 or 4 Bytes, describes how long the string is, if its 1 byte, the string is less than 255
The next bytes are the actual data.
The next block of data follows on.
| 4 Bytes, forming a 32 bit number, a key if you will, this describes what the data is (see blockKey below)
| 4 Bytes, forming a 32 bit number, which is the number of bytes until the next block starts
| 1 Byte, which tells how many bytes follows
| 1 or 4 Bytes, describes how long the string is, if its 1 byte, the string is less than 255
| The next bytes are the actual data.
| The next block of data follows on.
This description does differ for verses. Which includes extra bytes stating the verse type or number. In some cases
a "custom" verse is used, in that case, this block will in include 2 strings, with the associated string length
descriptors. The first string is the name of the verse, the second is the verse content.
This description does differ for verses. Which includes extra bytes stating the verse type or number. In some
cases a "custom" verse is used, in that case, this block will in include 2 strings, with the associated string
length descriptors. The first string is the name of the verse, the second is the verse content.
The file is ended with four null bytes.

View File

@ -24,7 +24,7 @@ The :mod:`worshipcenterpro` module provides the functionality for importing
a WorshipCenter Pro database into the OpenLP database.
"""
import logging
import re
import pyodbc
from openlp.core.common import translate
@ -71,8 +71,41 @@ class WorshipCenterProImport(SongImport):
break
self.set_defaults()
self.title = songs[song]['TITLE']
if 'AUTHOR' in songs[song]:
self.parse_author(songs[song]['AUTHOR'])
if 'CCLISONGID' in songs[song]:
self.ccli_number = songs[song]['CCLISONGID']
if 'COMMENTS' in songs[song]:
self.add_comment(songs[song]['COMMENTS'])
if 'COPY' in songs[song]:
self.add_copyright(songs[song]['COPY'])
if 'SUBJECT' in songs[song]:
self.topics.append(songs[song]['SUBJECT'])
lyrics = songs[song]['LYRICS'].strip('&crlf;&crlf;')
for verse in lyrics.split('&crlf;&crlf;'):
verse = verse.replace('&crlf;', '\n')
self.add_verse(verse)
marker_type = 'v'
# Find verse markers if any
marker_start = verse.find('<')
if marker_start > -1:
marker_end = verse.find('>')
marker = verse[marker_start + 1:marker_end]
# Identify the marker type
if 'REFRAIN' in marker or 'CHORUS' in marker:
marker_type = 'c'
elif 'BRIDGE' in marker:
marker_type = 'b'
elif 'PRECHORUS' in marker:
marker_type = 'p'
elif 'END' in marker:
marker_type = 'e'
elif 'INTRO' in marker:
marker_type = 'i'
elif 'TAG' in marker:
marker_type = 'o'
else:
marker_type = 'v'
# Strip tags from text
verse = re.sub('<[^<]+?>', '', verse)
self.add_verse(verse.strip(), marker_type)
self.finish()

View File

@ -75,7 +75,8 @@ class ZionWorxImport(SongImport):
"""
Receive a CSV file (from a ZionWorx database dump) to import.
"""
with open(self.import_source, 'rb') as songs_file:
# Encoding should always be ISO-8859-1
with open(self.import_source, 'rt', encoding='ISO-8859-1') as songs_file:
field_names = ['SongNum', 'Title1', 'Title2', 'Lyrics', 'Writer', 'Copyright', 'Keywords',
'DefaultStyle']
songs_reader = csv.DictReader(songs_file, field_names)
@ -112,10 +113,10 @@ class ZionWorxImport(SongImport):
if line and not line.isspace():
verse += line + '\n'
elif verse:
self.add_verse(verse)
self.add_verse(verse, 'v')
verse = ''
if verse:
self.add_verse(verse)
self.add_verse(verse, 'v')
title = self.title
if not self.finish():
self.log_error(translate('SongsPlugin.ZionWorxImport', 'Record %d') % index +
@ -123,8 +124,7 @@ class ZionWorxImport(SongImport):
def _decode(self, str):
"""
Decodes CSV input to unicode, stripping all control characters (except new lines).
Strips all control characters (except new lines).
"""
# This encoding choice seems OK. ZionWorx has no option for setting the
# encoding for its songs, so we assume encoding is always the same.
return str(str, 'cp1252').translate(CONTROL_CHARS_MAP)
# ZionWorx has no option for setting the encoding for its songs, so we assume encoding is always the same.
return str.translate(CONTROL_CHARS_MAP)

View File

@ -115,7 +115,7 @@ class SongMediaItem(MediaManagerItem):
Is triggered when the songs config is updated
"""
log.debug('config_updated')
self.search_as_you_type = Settings().value(self.settings_section + '/search as type')
self.is_search_as_you_type_enabled = Settings().value('advanced/search as type')
self.update_service_on_edit = Settings().value(self.settings_section + '/update service on edit')
self.add_song_from_service = Settings().value(self.settings_section + '/add song from service')
self.display_songbook = Settings().value(self.settings_section + '/display songbook')
@ -279,7 +279,7 @@ class SongMediaItem(MediaManagerItem):
If search as type enabled invoke the search on each key press. If the Lyrics are being searched do not start
till 7 characters have been entered.
"""
if self.search_as_you_type:
if self.is_search_as_you_type_enabled:
search_length = 1
if self.search_text_edit.current_search_type() == SongSearch.Entire:
search_length = 4
@ -590,4 +590,4 @@ class SongMediaItem(MediaManagerItem):
:param show_error: Is this an error?
"""
search_results = self.search_entire(string)
return [[song.id, song.title] for song in search_results]
return [[song.id, song.title, song.alternate_title] for song in search_results]

View File

@ -113,6 +113,7 @@ class SongXML(object):
Iterates through the verses in the XML and returns a list of verses and their attributes.
:param xml: The XML of the song to be parsed.
The returned list has the following format::
[[{'type': 'v', 'label': '1'}, u"optional slide split 1[---]optional slide split 2"],
@ -120,17 +121,7 @@ class SongXML(object):
"""
self.song_xml = None
verse_list = []
if not xml.startswith('<?xml') and not xml.startswith('<song'):
# This is an old style song, without XML. Let's handle it correctly by iterating through the verses, and
# then recreating the internal xml object as well.
self.song_xml = objectify.fromstring('<song version="1.0" />')
self.lyrics = etree.SubElement(self.song_xml, 'lyrics')
verses = xml.split('\n\n')
for count, verse in enumerate(verses):
verse_list.append([{'type': 'v', 'label': str(count)}, str(verse)])
self.add_verse_to_lyrics('v', str(count), verse)
return verse_list
elif xml.startswith('<?xml'):
if xml.startswith('<?xml'):
xml = xml[38:]
try:
self.song_xml = objectify.fromstring(xml)

View File

@ -21,14 +21,15 @@
###############################################################################
"""
The :mod:`songcompare` module provides functionality to search for
duplicate songs. It has one single :function:`songs_probably_equal`.
duplicate songs. It has one single :func:`songs_probably_equal`.
The algorithm is based on the diff algorithm.
First a diffset is calculated for two songs.
To compensate for typos all differences that are smaller than a
| The algorithm is based on the diff algorithm.
| First a diffset is calculated for two songs.
| To compensate for typos all differences that are smaller than a
limit (<max_typo_size) and are surrounded by larger equal blocks
(>min_fragment_size) are removed and the surrounding equal parts are merged.
Finally two conditions can qualify a song tuple to be a duplicate:
| Finally two conditions can qualify a song tuple to be a duplicate:
1. There is a block of equal content that is at least min_block_size large.
This condition should hit for all larger songs that have a long enough
equal part. Even if only one verse is equal this condition should still hit.

View File

@ -23,11 +23,12 @@
The :mod:`~openlp.plugins.songs.lib.songselect` module contains the SongSelect importer itself.
"""
import logging
import sys
from http.cookiejar import CookieJar
from urllib.parse import urlencode
from urllib.request import HTTPCookieProcessor, URLError, build_opener
from html.parser import HTMLParser
from html import unescape
from bs4 import BeautifulSoup, NavigableString
@ -130,8 +131,8 @@ class SongSelectImport(object):
break
for result in search_results:
song = {
'title': self.html_parser.unescape(result.find('h3').string),
'authors': [self.html_parser.unescape(author.string) for author in result.find_all('li')],
'title': unescape(result.find('h3').string),
'authors': [unescape(author.string) for author in result.find_all('li')],
'link': BASE_URL + result.find('a')['href']
}
if callback:
@ -167,7 +168,7 @@ class SongSelectImport(object):
if callback:
callback()
song['copyright'] = '/'.join([li.string for li in song_page.find('ul', 'copyright').find_all('li')])
song['copyright'] = self.html_parser.unescape(song['copyright'])
song['copyright'] = unescape(song['copyright'])
song['ccli_number'] = song_page.find('ul', 'info').find('li').string.split(':')[1].strip()
song['verses'] = []
verses = lyrics_page.find('section', 'lyrics').find_all('p')
@ -180,9 +181,9 @@ class SongSelectImport(object):
else:
verse['lyrics'] += '\n'
verse['lyrics'] = verse['lyrics'].strip(' \n\r\t')
song['verses'].append(self.html_parser.unescape(verse))
song['verses'].append(unescape(verse))
for counter, author in enumerate(song['authors']):
song['authors'][counter] = self.html_parser.unescape(author)
song['authors'][counter] = unescape(author)
return song
def save_song(self, song):

View File

@ -41,9 +41,6 @@ class SongsTab(SettingsTab):
self.mode_group_box.setObjectName('mode_group_box')
self.mode_layout = QtGui.QVBoxLayout(self.mode_group_box)
self.mode_layout.setObjectName('mode_layout')
self.search_as_type_check_box = QtGui.QCheckBox(self.mode_group_box)
self.search_as_type_check_box.setObjectName('SearchAsType_check_box')
self.mode_layout.addWidget(self.search_as_type_check_box)
self.tool_bar_active_check_box = QtGui.QCheckBox(self.mode_group_box)
self.tool_bar_active_check_box.setObjectName('tool_bar_active_check_box')
self.mode_layout.addWidget(self.tool_bar_active_check_box)
@ -62,7 +59,6 @@ class SongsTab(SettingsTab):
self.left_layout.addWidget(self.mode_group_box)
self.left_layout.addStretch()
self.right_layout.addStretch()
self.search_as_type_check_box.stateChanged.connect(self.on_search_as_type_check_box_changed)
self.tool_bar_active_check_box.stateChanged.connect(self.on_tool_bar_active_check_box_changed)
self.update_on_edit_check_box.stateChanged.connect(self.on_update_on_edit_check_box_changed)
self.add_from_service_check_box.stateChanged.connect(self.on_add_from_service_check_box_changed)
@ -71,7 +67,6 @@ class SongsTab(SettingsTab):
def retranslateUi(self):
self.mode_group_box.setTitle(translate('SongsPlugin.SongsTab', 'Songs Mode'))
self.search_as_type_check_box.setText(translate('SongsPlugin.SongsTab', 'Enable search as you type'))
self.tool_bar_active_check_box.setText(translate('SongsPlugin.SongsTab',
'Display verses on live tool bar'))
self.update_on_edit_check_box.setText(translate('SongsPlugin.SongsTab', 'Update service from song edit'))
@ -79,8 +74,8 @@ class SongsTab(SettingsTab):
'Import missing songs from service files'))
self.display_songbook_check_box.setText(translate('SongsPlugin.SongsTab', 'Display songbook in footer'))
self.display_copyright_check_box.setText(translate('SongsPlugin.SongsTab',
'Display "%s" symbol before copyright info' %
SongStrings.CopyrightSymbol))
'Display "%s" symbol before copyright info') %
SongStrings.CopyrightSymbol)
def on_search_as_type_check_box_changed(self, check_state):
self.song_search = (check_state == QtCore.Qt.Checked)
@ -103,13 +98,11 @@ class SongsTab(SettingsTab):
def load(self):
settings = Settings()
settings.beginGroup(self.settings_section)
self.song_search = settings.value('search as type')
self.tool_bar = settings.value('display songbar')
self.update_edit = settings.value('update service on edit')
self.update_load = settings.value('add song from service')
self.display_songbook = settings.value('display songbook')
self.display_copyright_symbol = settings.value('display copyright symbol')
self.search_as_type_check_box.setChecked(self.song_search)
self.tool_bar_active_check_box.setChecked(self.tool_bar)
self.update_on_edit_check_box.setChecked(self.update_edit)
self.add_from_service_check_box.setChecked(self.update_load)
@ -120,7 +113,6 @@ class SongsTab(SettingsTab):
def save(self):
settings = Settings()
settings.beginGroup(self.settings_section)
settings.setValue('search as type', self.song_search)
settings.setValue('display songbar', self.tool_bar)
settings.setValue('update service on edit', self.update_edit)
settings.setValue('add song from service', self.update_load)

View File

@ -57,7 +57,6 @@ __default_settings__ = {
'songs/last search type': SongSearch.Entire,
'songs/last import type': SongFormat.OpenLyrics,
'songs/update service on edit': False,
'songs/search as type': True,
'songs/add song from service': True,
'songs/display songbar': True,
'songs/display songbook': False,
@ -226,11 +225,9 @@ class SongsPlugin(Plugin):
Called to find out if the song plugin is currently using a theme.
:param theme: The theme to check for usage
:return: True if the theme is being used, otherwise returns False
:return: count of the number of times the theme is used.
"""
if self.manager.get_all_objects(Song, Song.theme_name == theme):
return True
return False
return len(self.manager.get_all_objects(Song, Song.theme_name == theme))
def rename_theme(self, old_theme, new_theme):
"""

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 766 B

151
scripts/lp-merge.py Executable file
View File

@ -0,0 +1,151 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2015 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 script is used by developers to merge branches from launchpad.net into
other branches, which is basically what happens every time a merge proposal
is merged into trunk. This script simplifies the process and helps avoiding
merging to the wrong branch by doing some checks.
For the script to work it is assumed that the developer doing the merging
has a checkout (not branch) of the target branch.
Merge a branch
--------------
Once a branch has been approved for merging, go to the local folder with the
checkout of the target branch, copy the url from the merge proposal on launchpad
and use it like this:
script/lp-merge.py <url>
The url could look like this:
https://code.launchpad.net/~tomasgroth/openlp/doc22update4/+merge/271874
First you'll be asked whether to merge the branch from the url to the local
branch, which will be done if you answer 'y'. The script checks that the current
folder is the right one before merging.
Then you'll be asked whether to commit the changes to the branch. The script
shows the command it will run, including detected linked bugs and author. If you
wish to change it, you can chose to run 'qcommit', an bzr GUI. Note that you'll
have to install qbzr for this to work. If you choose to run qcommit the script
will print the detected bugs + author for easy copying into the GUI.
"""
import subprocess
import re
import sys
import os
import urllib.request
from bs4 import BeautifulSoup
# Check that the argument count is correct
if len(sys.argv) != 2:
print('\n\tUsage: ' + sys.argv[0] + ' <url to approved merge request>\n')
exit()
url = sys.argv[1]
pattern = re.compile('.+?/\+merge/\d+')
match = pattern.match(url)
# Check that the given argument is an url in the right format
if not url.startswith('https://code.launchpad.net/~') or match is None:
print('The url is not valid! It should look like this:\n '
'https://code.launchpad.net/~tomasgroth/openlp/doc22update4/+merge/271874')
page = urllib.request.urlopen(url)
soup = BeautifulSoup(page.read(), 'lxml')
# Find this span tag that contains the branch url
# <span class="branch-url">
span_branch_url = soup.find('span', class_='branch-url')
branch_url = span_branch_url.contents[0]
# Find this tag that describes the branch. We'll use that for commit message
# <meta name="description" content="...">
meta = soup.find('meta', attrs={"name": "description"})
commit_message = meta.get('content')
# Find all tr-tags with this class. Makes it possible to get bug numbers.
# <tr class="bug-branch-summary"
bug_rows = soup.find_all('tr', class_='bug-branch-summary')
bugs = []
for row in bug_rows:
id_attr = row.get('id')
bugs.append(id_attr[8:])
# Find target branch name using the tag below
# <div class="context-publication"><h1>Merge ... into...
div_branches = soup.find('div', class_='context-publication')
branches = div_branches.h1.contents[0]
target_branch = '+branch/' + branches[(branches.find(' into lp:')+9):]
# Check that we are in the right branch
bzr_info_output = subprocess.check_output(['bzr', 'info'])
if target_branch not in bzr_info_output.decode():
print('ERROR: It seems you are not in the right folder...')
exit()
# Find the authors email address. It is hidden in a javascript line like this:
# conf = {"status_value": "Needs review", "source_revid": "tomasgroth@yahoo.dk-20150921204550-gxduegmcmty9rljf",
# "user_can_edit_status": false, ...
script_tag = soup.find('script', attrs={"id": "codereview-script"})
content = script_tag.contents[0]
start_pos = content.find('source_revid') + 16
pattern = re.compile('.*\w-\d\d\d\d\d+')
match = pattern.match(content[start_pos:])
author_email = match.group()[:-15]
# Merge the branch
do_merge = input('Merge ' + branch_url + ' into local branch? (y/N/q): ').lower()
if do_merge == 'y':
subprocess.call(['bzr', 'merge', branch_url])
elif do_merge == 'q':
exit()
# Create commit command
commit_command = ['bzr', 'commit']
for bug in bugs:
commit_command.append('--fixes')
commit_command.append('lp:' + bug)
commit_command.append('-m')
commit_command.append(commit_message)
commit_command.append('--author')
commit_command.append('"' + author_email + '"')
print('About to run the bzr command below:\n')
print(' '.join(commit_command))
do_commit = input('Run the command (y), use qcommit (qcommit) or cancel (C): ').lower()
if do_commit == 'y':
subprocess.call(commit_command)
elif do_commit == 'qcommit':
# Setup QT workaround to make qbzr look right on my box
my_env = os.environ.copy()
my_env['QT_GRAPHICSSYSTEM'] = 'native'
# Print stuff that kan be copy/pasted into qbzr GUI
print('These bugs can be copy/pasted in: lp:' + ' lp:'.join(bugs))
print('The authors email is: ' + author_email)
# Run qcommit
subprocess.call(['bzr', 'qcommit', '-m', commit_message], env=my_env)

View File

@ -0,0 +1,144 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2015 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 #
###############################################################################
import sys
from unittest import TestCase
from openlp.core import parse_options
from tests.helpers.testmixin import TestMixin
class TestInitFunctions(TestMixin, TestCase):
def parse_options_basic_test(self):
"""
Test the parse options process works
"""
# GIVEN: a a set of system arguments.
sys.argv[1:] = []
# WHEN: We we parse them to expand to options
args = parse_options()
# THEN: the following fields will have been extracted.
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.assertFalse(args.no_error_form, 'The no_error_form 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.rargs, [], 'The service file should be blank')
def parse_options_debug_test(self):
"""
Test the parse options process works for debug only
"""
# GIVEN: a a set of system arguments.
sys.argv[1:] = ['-l debug']
# WHEN: We we parse them to expand to options
args = parse_options()
# THEN: the following fields will have been extracted.
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.assertFalse(args.no_error_form, 'The no_error_form 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.rargs, [], 'The service file should be blank')
def parse_options_debug_and_portable_test(self):
"""
Test the parse options process works for debug and portable
"""
# GIVEN: a a set of system arguments.
sys.argv[1:] = ['--portable']
# WHEN: We we parse them to expand to options
args = parse_options()
# THEN: the following fields will have been extracted.
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.assertFalse(args.no_error_form, 'The no_error_form should be set to False')
self.assertTrue(args.portable, 'The portable flag should be set to true')
self.assertEquals(args.style, None, 'There are no style flags to be processed')
self.assertEquals(args.rargs, [], 'The service file should be blank')
def parse_options_all_no_file_test(self):
"""
Test the parse options process works with two options
"""
# GIVEN: a a set of system arguments.
sys.argv[1:] = ['-l debug', '-d']
# WHEN: We we parse them to expand to options
args = parse_options()
# THEN: the following fields will have been extracted.
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.assertFalse(args.no_error_form, 'The no_error_form 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.rargs, [], 'The service file should be blank')
def parse_options_file_test(self):
"""
Test the parse options process works with a file
"""
# GIVEN: a a set of system arguments.
sys.argv[1:] = ['dummy_temp']
# WHEN: We we parse them to expand to options
args = parse_options()
# THEN: the following fields will have been extracted.
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.assertFalse(args.no_error_form, 'The no_error_form 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.rargs, 'dummy_temp', 'The service file should not be blank')
def parse_options_file_and_debug_test(self):
"""
Test the parse options process works with a file
"""
# GIVEN: a a set of system arguments.
sys.argv[1:] = ['-l debug', 'dummy_temp']
# WHEN: We we parse them to expand to options
args = parse_options()
# THEN: the following fields will have been extracted.
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.assertFalse(args.no_error_form, 'The no_error_form 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.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

@ -90,10 +90,15 @@ class TestProjectorDB(TestCase):
"""
if not hasattr(self, 'projector'):
with patch('openlp.core.lib.projector.db.init_url') as mocked_init_url:
mocked_init_url.start()
mocked_init_url.return_value = 'sqlite:///%s' % tmpfile
self.projector = ProjectorDB()
def tearDown(self):
"""
Clean up
"""
self.projector = None
def find_record_by_ip_test(self):
"""
Test find record by IP

View File

@ -0,0 +1,69 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2015 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 #
###############################################################################
"""
Package to test the openlp.core.ui.advancedtab package.
"""
from unittest import TestCase
from openlp.core.common import Registry
from openlp.core.ui.advancedtab import AdvancedTab
from openlp.core.ui.settingsform import SettingsForm
from tests.helpers.testmixin import TestMixin
class TestAdvancedTab(TestCase, TestMixin):
def setUp(self):
"""
Set up a few things for the tests
"""
Registry.create()
def test_creation(self):
"""
Test that Advanced Tab is created.
"""
# GIVEN: A new Advanced Tab
settings_form = SettingsForm(None)
# WHEN: I create an advanced tab
advanced_tab = AdvancedTab(settings_form)
# THEN:
self.assertEqual("Advanced", advanced_tab.tab_title, 'The tab title should be Advanced')
def test_change_search_as_type(self):
"""
Test that when search as type is changed custom and song configs are updated
"""
# GIVEN: A new Advanced Tab
settings_form = SettingsForm(None)
advanced_tab = AdvancedTab(settings_form)
# WHEN: I change search as type check box
advanced_tab.on_search_as_type_check_box_changed(True)
# THEN: we should have two post save processed to run
self.assertEqual(2, len(settings_form.processes), 'Two post save processes should be created')
self.assertTrue("songs_config_updated" in settings_form.processes, 'The songs plugin should be called')
self.assertTrue("custom_config_updated" in settings_form.processes, 'The custom plugin should be called')

View File

@ -71,7 +71,7 @@ class TestMainWindow(TestCase, TestMixin):
with patch('openlp.core.ui.servicemanager.ServiceManager.load_file') as mocked_load_path:
# WHEN the argument is processed
self.main_window.open_cmd_line_files()
self.main_window.open_cmd_line_files(service)
# THEN the service from the arguments is loaded
mocked_load_path.assert_called_with(service), 'load_path should have been called with the service\'s path'
@ -86,7 +86,7 @@ class TestMainWindow(TestCase, TestMixin):
with patch('openlp.core.ui.servicemanager.ServiceManager.load_file') as mocked_load_path:
# WHEN the argument is processed
self.main_window.open_cmd_line_files()
self.main_window.open_cmd_line_files("")
# THEN the file should not be opened
assert not mocked_load_path.called, 'load_path should not have been called'

View File

@ -640,6 +640,51 @@ class TestSlideController(TestCase):
mocked_preview_widget.change_slide.assert_called_once_with(7)
mocked_slide_selected.assert_called_once_with()
@patch.object(Registry, 'execute')
def process_item_test(self, mocked_execute):
"""
Test that presentation service-items is closed when followed by a media service-item
"""
# GIVEN: A mocked presentation service item, a mocked media service item, a mocked Registry.execute
# and a slide controller with many mocks.
mocked_pres_item = MagicMock()
mocked_pres_item.name = 'mocked_presentation_item'
mocked_pres_item.is_command.return_value = True
mocked_pres_item.is_media.return_value = False
mocked_pres_item.is_image.return_value = False
mocked_pres_item.from_service = False
mocked_pres_item.get_frames.return_value = []
mocked_media_item = MagicMock()
mocked_media_item.name = 'mocked_media_item'
mocked_media_item.is_command.return_value = True
mocked_media_item.is_media.return_value = True
mocked_media_item.is_image.return_value = False
mocked_media_item.from_service = False
mocked_media_item.get_frames.return_value = []
Registry.create()
mocked_main_window = MagicMock()
Registry().register('main_window', mocked_main_window)
slide_controller = SlideController(None)
slide_controller.service_item = mocked_pres_item
slide_controller.is_live = False
slide_controller.preview_widget = MagicMock()
slide_controller.enable_tool_bar = MagicMock()
slide_controller.on_media_start = MagicMock()
slide_controller.slide_selected = MagicMock()
slide_controller.on_stop_loop = MagicMock()
slide_controller.info_label = MagicMock()
slide_controller.display = MagicMock()
slide_controller.split = 0
slide_controller.type_prefix = 'test'
# WHEN: _process_item is called
slide_controller._process_item(mocked_media_item, 0)
# THEN: Registry.execute should have been called to stop the presentation
self.assertEqual(3, mocked_execute.call_count, 'Execute should have been called 3 times')
self.assertEqual('mocked_presentation_item_stop', mocked_execute.call_args_list[1][0][0],
'The presentation should have been stopped.')
class TestInfoLabel(TestCase):

View File

@ -240,4 +240,4 @@ class TestThemeManager(TestCase):
theme_manager.unzip_theme('theme.file', 'folder')
# THEN: The critical_error_message_box should have been called
mocked_critical_error_message_box.assert_called_once(ANY, ANY)
self.assertEqual(mocked_critical_error_message_box.call_count, 1, 'Should have been called once')

View File

@ -25,7 +25,7 @@ Package to test the openlp.core.ui.media.vlcplayer package.
import os
import sys
from datetime import datetime, timedelta
from unittest import TestCase
from unittest import TestCase, skip
from openlp.core.common import Registry
from openlp.core.ui.media import MediaState, MediaType
@ -50,6 +50,22 @@ class TestVLCPlayer(TestCase, TestMixin):
del sys.modules['openlp.core.ui.media.vendor.vlc']
MockDateTime.revert()
@skip('No way to test this')
@patch('openlp.core.ui.media.vlcplayer.vlc')
def get_vlc_fails_and_removes_module_test(self, mocked_vlc):
"""
Test that when the VLC import fails, it removes the module from sys.modules
"""
# GIVEN: We're on OS X and we don't have the VLC plugin path set
mocked_vlc.Instance.side_effect = NameError
mocked_vlc.libvlc_get_version.return_value = b'0.0.0'
# WHEN: An checking if the player is available
get_vlc()
# THEN: The extra environment variable should be there
self.assertNotIn('openlp.core.ui.media.vendor.vlc', sys.modules)
@patch('openlp.core.ui.media.vlcplayer.is_macosx')
def fix_vlc_22_plugin_path_test(self, mocked_is_macosx):
"""
@ -74,10 +90,6 @@ class TestVLCPlayer(TestCase, TestMixin):
"""
# GIVEN: We're not on OS X and we don't have the VLC plugin path set
mocked_is_macosx.return_value = False
if 'VLC_PLUGIN_PATH' in os.environ:
del os.environ['VLC_PLUGIN_PATH']
if 'openlp.core.ui.media.vendor.vlc' in sys.modules:
del sys.modules['openlp.core.ui.media.vendor.vlc']
# WHEN: An checking if the player is available
get_vlc()

View File

@ -25,7 +25,7 @@ Package to test the openlp.core.utils.actions package.
from unittest import TestCase
from openlp.core.common.settings import Settings
from openlp.core.utils import VersionThread, get_application_version, get_uno_command
from openlp.core.utils import VersionThread, get_uno_command
from tests.functional import MagicMock, patch
from tests.helpers.testmixin import TestMixin

View File

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2015 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 #
###############################################################################

View File

@ -0,0 +1,84 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2015 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 CSV Bible importer.
"""
import os
import json
from unittest import TestCase
from tests.functional import MagicMock, patch
from openlp.core.common.registry import Registry
from openlp.plugins.alerts.lib.alertsmanager import AlertsManager
class TestAlertManager(TestCase):
def setUp(self):
"""
Create the UI
"""
Registry.create()
def remove_message_text_test(self):
"""
Test that Alerts are not triggered with empty strings
"""
# GIVEN: A valid Alert Manager
alert_manager = AlertsManager(None)
alert_manager.display_alert = MagicMock()
# WHEN: Called with an empty string
alert_manager.alert_text('')
# THEN: the display should not have been triggered
self.assertFalse(alert_manager.display_alert.called, 'The Alert should not have been called')
def trigger_message_text_test(self):
"""
Test that Alerts are triggered with a text string
"""
# GIVEN: A valid Alert Manager
alert_manager = AlertsManager(None)
alert_manager.display_alert = MagicMock()
# WHEN: Called with an empty string
alert_manager.alert_text(['This is a string'])
# THEN: the display should have been triggered
self.assertTrue(alert_manager.display_alert.called, 'The Alert should have been called')
def line_break_message_text_test(self):
"""
Test that Alerts are triggered with a text string but line breaks are removed
"""
# GIVEN: A valid Alert Manager
alert_manager = AlertsManager(None)
alert_manager.display_alert = MagicMock()
# WHEN: Called with an empty string
alert_manager.alert_text(['This is \n a string'])
# THEN: the display should have been triggered
self.assertTrue(alert_manager.display_alert.called, 'The Alert should have been called')
alert_manager.display_alert.assert_called_once_with('This is a string')

View File

@ -0,0 +1,106 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2015 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 lib submodule of the Presentations plugin.
"""
from unittest import TestCase
from openlp.core.common import Registry
from openlp.plugins.presentations.lib.mediaitem import MessageListener, PresentationMediaItem
from tests.functional import patch, MagicMock
from tests.helpers.testmixin import TestMixin
class TestMessageListener(TestCase, TestMixin):
"""
Test the Presentation Message Listener.
"""
def setUp(self):
"""
Set up the components need for all tests.
"""
Registry.create()
Registry().register('service_manager', MagicMock())
Registry().register('main_window', MagicMock())
with patch('openlp.plugins.presentations.lib.mediaitem.MediaManagerItem._setup'), \
patch('openlp.plugins.presentations.lib.mediaitem.PresentationMediaItem.setup_item'):
self.media_item = PresentationMediaItem(None, MagicMock, MagicMock())
@patch('openlp.plugins.presentations.lib.mediaitem.MessageListener._setup')
def start_presentation_test(self, media_mock):
"""
Find and chose a controller to play a presentations.
"""
# GIVEN: A single controller and service item wanting to use the controller
mock_item = MagicMock()
mock_item.processor = 'Powerpoint'
mock_item.get_frame_path.return_value = "test.ppt"
self.media_item.automatic = False
mocked_controller = MagicMock()
mocked_controller.available = True
mocked_controller.supports = ['ppt']
controllers = {
'Powerpoint': mocked_controller
}
ml = MessageListener(self.media_item)
ml.media_item = self.media_item
ml.controllers = controllers
ml.preview_handler = MagicMock()
ml.timer = MagicMock()
# WHEN: request the presentation to start
ml.startup([mock_item, False, False, False])
# THEN: The controllers will be setup.
self.assertTrue(len(controllers), 'We have loaded a controller')
@patch('openlp.plugins.presentations.lib.mediaitem.MessageListener._setup')
def start_presentation_with_no_player_test(self, media_mock):
"""
Find and chose a controller to play a presentations when the player is not available.
"""
# GIVEN: A single controller and service item wanting to use the controller
mock_item = MagicMock()
mock_item.processor = 'Powerpoint'
mock_item.get_frame_path.return_value = "test.ppt"
self.media_item.automatic = False
mocked_controller = MagicMock()
mocked_controller.available = True
mocked_controller.supports = ['ppt']
mocked_controller1 = MagicMock()
mocked_controller1.available = False
mocked_controller1.supports = ['ppt']
controllers = {
'Impress': mocked_controller,
'Powerpoint': mocked_controller1
}
ml = MessageListener(self.media_item)
ml.media_item = self.media_item
ml.controllers = controllers
ml.preview_handler = MagicMock()
ml.timer = MagicMock()
# WHEN: request the presentation to start
ml.startup([mock_item, False, False, False])
# THEN: The controllers will be setup.
self.assertTrue(len(controllers), 'We have loaded a controller')

Some files were not shown because too many files have changed in this diff Show More