forked from openlp/openlp
Merge trunk
This commit is contained in:
commit
0d4541b8dc
|
@ -1 +1 @@
|
|||
2.1.6
|
||||
2.2
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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):
|
||||
"""
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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']:
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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')):
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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_()
|
||||
|
|
|
@ -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",
|
||||
....
|
||||
...
|
||||
},
|
||||
...
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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=''):
|
||||
"""
|
||||
|
|
|
@ -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):
|
||||
"""
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
"""
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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):
|
||||
"""
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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']
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 '
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
"""
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
"""
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
"""
|
||||
|
|
1356
resources/i18n/af.ts
1356
resources/i18n/af.ts
File diff suppressed because it is too large
Load Diff
1068
resources/i18n/bg.ts
1068
resources/i18n/bg.ts
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
1364
resources/i18n/el.ts
1364
resources/i18n/el.ts
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
1384
resources/i18n/fi.ts
1384
resources/i18n/fi.ts
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
1504
resources/i18n/ko.ts
1504
resources/i18n/ko.ts
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 |
|
@ -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)
|
|
@ -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')
|
|
@ -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
|
||||
|
|
|
@ -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')
|
|
@ -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'
|
||||
|
|
|
@ -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):
|
||||
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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 #
|
||||
###############################################################################
|
|
@ -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')
|
|
@ -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
Loading…
Reference in New Issue