forked from openlp/openlp
HEAD
This commit is contained in:
commit
83cd5c1a88
@ -8,7 +8,7 @@
|
||||
*.nja
|
||||
*.orig
|
||||
*.pyc
|
||||
*.qm
|
||||
resources/i18n/*.ts
|
||||
*.rej
|
||||
*.ropeproject
|
||||
*.~\?~
|
||||
|
@ -16,3 +16,5 @@ include copyright.txt
|
||||
include LICENSE
|
||||
include README.txt
|
||||
include openlp/.version
|
||||
include package.json
|
||||
include karma.conf.js
|
||||
|
@ -26,18 +26,21 @@ module.exports = function(config) {
|
||||
// source files, that you wanna generate coverage for
|
||||
// do not include tests or libraries
|
||||
// (these files will be instrumented by Istanbul)
|
||||
"display.js": ["coverage"]
|
||||
// "display.js": ["coverage"]
|
||||
},
|
||||
|
||||
// test results reporter to use
|
||||
// possible values: "dots", "progress"
|
||||
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
|
||||
reporters: ["progress", "coverage"],
|
||||
reporters: ["dots", "junit"],
|
||||
|
||||
// configure the coverateReporter
|
||||
coverageReporter: {
|
||||
/* coverageReporter: {
|
||||
type : "html",
|
||||
dir : "htmlcov/"
|
||||
}, */
|
||||
junitReporter: {
|
||||
outputFile: "test-results.xml"
|
||||
},
|
||||
|
||||
// web server port
|
||||
@ -60,11 +63,11 @@ module.exports = function(config) {
|
||||
|
||||
// start these browsers
|
||||
// available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
|
||||
browsers: ["PhantomJS"],
|
||||
browsers: ["Chromium"],
|
||||
|
||||
// Continuous Integration mode
|
||||
// if true, Karma captures browsers, runs the tests and exits
|
||||
singleRun: false,
|
||||
singleRun: true,
|
||||
|
||||
// Concurrency level
|
||||
// how many browser should be started simultaneous
|
||||
|
86
openlp/__main__.py
Normal file
86
openlp/__main__.py
Normal file
@ -0,0 +1,86 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||
|
||||
##########################################################################
|
||||
# OpenLP - Open Source Lyrics Projection #
|
||||
# ---------------------------------------------------------------------- #
|
||||
# Copyright (c) 2008-2019 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, either version 3 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# 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, see <https://www.gnu.org/licenses/>. #
|
||||
##########################################################################
|
||||
"""
|
||||
The entrypoint for OpenLP
|
||||
"""
|
||||
import atexit
|
||||
import faulthandler
|
||||
import logging
|
||||
import multiprocessing
|
||||
import sys
|
||||
|
||||
# from OpenGL import GL
|
||||
|
||||
from openlp.core.app import main
|
||||
from openlp.core.common import is_macosx, is_win
|
||||
from openlp.core.common.applocation import AppLocation
|
||||
from openlp.core.common.path import create_paths
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
error_log_file = None
|
||||
|
||||
|
||||
def tear_down_fault_handling():
|
||||
"""
|
||||
When Python exits, close the file we were using for the faulthandler
|
||||
"""
|
||||
global error_log_file
|
||||
error_log_file.close()
|
||||
|
||||
|
||||
def set_up_fault_handling():
|
||||
"""
|
||||
Set up the Python fault handler
|
||||
"""
|
||||
global error_log_file
|
||||
# Create the cache directory if it doesn't exist, and enable the fault handler to log to an error log file
|
||||
try:
|
||||
create_paths(AppLocation.get_directory(AppLocation.CacheDir))
|
||||
error_log_file = (AppLocation.get_directory(AppLocation.CacheDir) / 'error.log').open('wb')
|
||||
atexit.register(tear_down_fault_handling)
|
||||
faulthandler.enable(error_log_file)
|
||||
except OSError:
|
||||
log.exception('An exception occurred when enabling the fault handler')
|
||||
atexit.unregister(tear_down_fault_handling)
|
||||
if error_log_file:
|
||||
error_log_file.close()
|
||||
|
||||
|
||||
def start():
|
||||
"""
|
||||
Instantiate and run the application.
|
||||
"""
|
||||
set_up_fault_handling()
|
||||
# Add support for using multiprocessing from frozen Windows executable (built using PyInstaller),
|
||||
# see https://docs.python.org/3/library/multiprocessing.html#multiprocessing.freeze_support
|
||||
if is_win():
|
||||
multiprocessing.freeze_support()
|
||||
# Mac OS X passes arguments like '-psn_XXXX' to the application. This argument is actually a process serial number.
|
||||
# However, this causes a conflict with other OpenLP arguments. Since we do not use this argument we can delete it
|
||||
# to avoid any potential conflicts.
|
||||
if is_macosx():
|
||||
sys.argv = [x for x in sys.argv if not x.startswith('-psn')]
|
||||
main()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
start()
|
@ -24,7 +24,7 @@ The :mod:`~openlp.core.api.tab` module contains the settings tab for the API
|
||||
"""
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
from openlp.core.common import get_local_ip4
|
||||
from openlp.core.common import get_network_interfaces
|
||||
from openlp.core.common.i18n import UiStrings, translate
|
||||
from openlp.core.common.registry import Registry
|
||||
from openlp.core.common.settings import Settings
|
||||
@ -194,8 +194,7 @@ class ApiTab(SettingsTab):
|
||||
http_url_temp = http_url + 'main'
|
||||
self.live_url.setText('<a href="{url}">{url}</a>'.format(url=http_url_temp))
|
||||
|
||||
@staticmethod
|
||||
def get_ip_address(ip_address):
|
||||
def get_ip_address(self, ip_address):
|
||||
"""
|
||||
returns the IP address in dependency of the passed address
|
||||
ip_address == 0.0.0.0: return the IP address of the first valid interface
|
||||
@ -203,9 +202,8 @@ class ApiTab(SettingsTab):
|
||||
"""
|
||||
if ip_address == ZERO_URL:
|
||||
# In case we have more than one interface
|
||||
ifaces = get_local_ip4()
|
||||
for key in iter(ifaces):
|
||||
ip_address = ifaces.get(key)['ip']
|
||||
for _, interface in get_network_interfaces().items():
|
||||
ip_address = interface['ip']
|
||||
# We only want the first interface returned
|
||||
break
|
||||
return ip_address
|
||||
|
99
openlp/core/api/zeroconf.py
Normal file
99
openlp/core/api/zeroconf.py
Normal file
@ -0,0 +1,99 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||
|
||||
##########################################################################
|
||||
# OpenLP - Open Source Lyrics Projection #
|
||||
# ---------------------------------------------------------------------- #
|
||||
# Copyright (c) 2008-2019 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, either version 3 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# 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, see <https://www.gnu.org/licenses/>. #
|
||||
##########################################################################
|
||||
"""
|
||||
The :mod:`~openlp.core.api.zeroconf` module runs a Zerconf server so that OpenLP can advertise the
|
||||
RESTful API for devices on the network to discover.
|
||||
"""
|
||||
import socket
|
||||
from time import sleep
|
||||
|
||||
from zeroconf import ServiceInfo, Zeroconf
|
||||
|
||||
from openlp.core.common import get_network_interfaces
|
||||
from openlp.core.common.registry import Registry
|
||||
from openlp.core.common.settings import Settings
|
||||
from openlp.core.threading import ThreadWorker, run_thread
|
||||
|
||||
|
||||
class ZeroconfWorker(ThreadWorker):
|
||||
"""
|
||||
This thread worker runs a Zeroconf service
|
||||
"""
|
||||
address = None
|
||||
http_port = 4316
|
||||
ws_port = 4317
|
||||
_can_run = False
|
||||
|
||||
def __init__(self, ip_address, http_port=4316, ws_port=4317):
|
||||
"""
|
||||
Create the worker for the Zeroconf service
|
||||
"""
|
||||
super().__init__()
|
||||
self.address = socket.inet_aton(ip_address)
|
||||
self.http_port = http_port
|
||||
self.ws_port = ws_port
|
||||
|
||||
def can_run(self):
|
||||
"""
|
||||
Check if the worker can continue to run. This is mostly so that we can override this method
|
||||
and test the class.
|
||||
"""
|
||||
return self._can_run
|
||||
|
||||
def start(self):
|
||||
"""
|
||||
Start the service
|
||||
"""
|
||||
http_info = ServiceInfo('_http._tcp.local.', 'OpenLP._http._tcp.local.',
|
||||
address=self.address, port=self.http_port, properties={})
|
||||
ws_info = ServiceInfo('_ws._tcp.local.', 'OpenLP._ws._tcp.local.',
|
||||
address=self.address, port=self.ws_port, properties={})
|
||||
zc = Zeroconf()
|
||||
zc.register_service(http_info)
|
||||
zc.register_service(ws_info)
|
||||
self._can_run = True
|
||||
while self.can_run():
|
||||
sleep(0.1)
|
||||
zc.unregister_service(http_info)
|
||||
zc.unregister_service(ws_info)
|
||||
zc.close()
|
||||
self.quit.emit()
|
||||
|
||||
def stop(self):
|
||||
"""
|
||||
Stop the service
|
||||
"""
|
||||
self._can_run = False
|
||||
|
||||
|
||||
def start_zeroconf():
|
||||
"""
|
||||
Start the Zeroconf service
|
||||
"""
|
||||
# When we're running tests, just skip this set up if this flag is set
|
||||
if Registry().get_flag('no_web_server'):
|
||||
return
|
||||
http_port = Settings().value('api/port')
|
||||
ws_port = Settings().value('api/websocket port')
|
||||
for name, interface in get_network_interfaces().items():
|
||||
worker = ZeroconfWorker(interface['ip'], http_port, ws_port)
|
||||
run_thread(worker, 'api_zeroconf_{name}'.format(name=name))
|
@ -391,7 +391,11 @@ def main():
|
||||
vlc_lib = 'libvlc.dylib'
|
||||
elif is_win():
|
||||
vlc_lib = 'libvlc.dll'
|
||||
os.environ['PYTHON_VLC_LIB_PATH'] = str(AppLocation.get_directory(AppLocation.AppDir) / vlc_lib)
|
||||
# Path to libvlc
|
||||
os.environ['PYTHON_VLC_LIB_PATH'] = str(AppLocation.get_directory(AppLocation.AppDir) / 'vlc' / vlc_lib)
|
||||
log.debug('VLC Path: {}'.format(os.environ['PYTHON_VLC_LIB_PATH']))
|
||||
# Path to VLC directory containing VLC's "plugins" directory
|
||||
os.environ['PYTHON_VLC_MODULE_PATH'] = str(AppLocation.get_directory(AppLocation.AppDir) / 'vlc')
|
||||
log.debug('VLC Path: {}'.format(os.environ['PYTHON_VLC_LIB_PATH']))
|
||||
# Initialise the Registry
|
||||
Registry.create()
|
||||
|
@ -45,15 +45,16 @@ log = logging.getLogger(__name__ + '.__init__')
|
||||
FIRST_CAMEL_REGEX = re.compile('(.)([A-Z][a-z]+)')
|
||||
SECOND_CAMEL_REGEX = re.compile('([a-z0-9])([A-Z])')
|
||||
CONTROL_CHARS = re.compile(r'[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x9F]')
|
||||
INVALID_FILE_CHARS = re.compile(r'[\\/:\*\?"<>\|\+\[\]%]')
|
||||
INVALID_FILE_CHARS = re.compile(r'[\\/:*?"<>|+\[\]%]')
|
||||
IMAGES_FILTER = None
|
||||
REPLACMENT_CHARS_MAP = str.maketrans({'\u2018': '\'', '\u2019': '\'', '\u201c': '"', '\u201d': '"', '\u2026': '...',
|
||||
'\u2013': '-', '\u2014': '-', '\v': '\n\n', '\f': '\n\n'})
|
||||
NEW_LINE_REGEX = re.compile(r' ?(\r\n?|\n) ?')
|
||||
WHITESPACE_REGEX = re.compile(r'[ \t]+')
|
||||
INTERFACE_FILTER = re.compile('lo|loopback|docker|tun', re.IGNORECASE)
|
||||
|
||||
|
||||
def get_local_ip4():
|
||||
def get_network_interfaces():
|
||||
"""
|
||||
Creates a dictionary of local IPv4 interfaces on local machine.
|
||||
If no active interfaces available, returns a dict of localhost IPv4 information
|
||||
@ -61,43 +62,33 @@ def get_local_ip4():
|
||||
:returns: Dict of interfaces
|
||||
"""
|
||||
log.debug('Getting local IPv4 interface(es) information')
|
||||
my_ip4 = {}
|
||||
for iface in QNetworkInterface.allInterfaces():
|
||||
interfaces = {}
|
||||
for interface in QNetworkInterface.allInterfaces():
|
||||
interface_name = interface.name()
|
||||
if INTERFACE_FILTER.search(interface_name):
|
||||
log.debug('Filtering out interfaces we don\'t care about: {name}'.format(name=interface_name))
|
||||
continue
|
||||
log.debug('Checking for isValid and flags == IsUP | IsRunning')
|
||||
if not iface.isValid() or not (iface.flags() & (QNetworkInterface.IsUp | QNetworkInterface.IsRunning)):
|
||||
if not interface.isValid() or not (interface.flags() & (QNetworkInterface.IsUp | QNetworkInterface.IsRunning)):
|
||||
continue
|
||||
log.debug('Checking address(es) protocol')
|
||||
for address in iface.addressEntries():
|
||||
for address in interface.addressEntries():
|
||||
ip = address.ip()
|
||||
log.debug('Checking for protocol == IPv4Protocol')
|
||||
if ip.protocol() == QAbstractSocket.IPv4Protocol:
|
||||
log.debug('Getting interface information')
|
||||
my_ip4[iface.name()] = {'ip': ip.toString(),
|
||||
'broadcast': address.broadcast().toString(),
|
||||
'netmask': address.netmask().toString(),
|
||||
'prefix': address.prefixLength(),
|
||||
'localnet': QHostAddress(address.netmask().toIPv4Address() &
|
||||
ip.toIPv4Address()).toString()
|
||||
}
|
||||
log.debug('Adding {iface} to active list'.format(iface=iface.name()))
|
||||
if len(my_ip4) == 0:
|
||||
interfaces[interface_name] = {
|
||||
'ip': ip.toString(),
|
||||
'broadcast': address.broadcast().toString(),
|
||||
'netmask': address.netmask().toString(),
|
||||
'prefix': address.prefixLength(),
|
||||
'localnet': QHostAddress(address.netmask().toIPv4Address() &
|
||||
ip.toIPv4Address()).toString()
|
||||
}
|
||||
log.debug('Adding {interface} to active list'.format(interface=interface.name()))
|
||||
if len(interfaces) == 0:
|
||||
log.warning('No active IPv4 network interfaces detected')
|
||||
return my_ip4
|
||||
if 'localhost' in my_ip4:
|
||||
log.debug('Renaming windows localhost to lo')
|
||||
my_ip4['lo'] = my_ip4['localhost']
|
||||
my_ip4.pop('localhost')
|
||||
if len(my_ip4) == 1:
|
||||
if 'lo' in my_ip4:
|
||||
# No active interfaces - so leave localhost in there
|
||||
log.warning('No active IPv4 interfaces found except localhost')
|
||||
else:
|
||||
# Since we have a valid IP4 interface, remove localhost
|
||||
if 'lo' in my_ip4:
|
||||
log.debug('Found at least one IPv4 interface, removing localhost')
|
||||
my_ip4.pop('lo')
|
||||
|
||||
return my_ip4
|
||||
return interfaces
|
||||
|
||||
|
||||
def trace_error_handler(logger):
|
||||
@ -112,21 +103,21 @@ def trace_error_handler(logger):
|
||||
logger.error(log_string)
|
||||
|
||||
|
||||
def extension_loader(glob_pattern, excluded_files=[]):
|
||||
def extension_loader(glob_pattern, excluded_files=None):
|
||||
"""
|
||||
A utility function to find and load OpenLP extensions, such as plugins, presentation and media controllers and
|
||||
importers.
|
||||
|
||||
:param str glob_pattern: A glob pattern used to find the extension(s) to be imported. Should be relative to the
|
||||
application directory. i.e. plugins/*/*plugin.py
|
||||
:param list[str] excluded_files: A list of file names to exclude that the glob pattern may find.
|
||||
:param list[str] | None excluded_files: A list of file names to exclude that the glob pattern may find.
|
||||
:rtype: None
|
||||
"""
|
||||
from openlp.core.common.applocation import AppLocation
|
||||
app_dir = AppLocation.get_directory(AppLocation.AppDir)
|
||||
for extension_path in app_dir.glob(glob_pattern):
|
||||
extension_path = extension_path.relative_to(app_dir)
|
||||
if extension_path.name in excluded_files:
|
||||
if extension_path.name in (excluded_files or []):
|
||||
continue
|
||||
log.debug('Attempting to import %s', extension_path)
|
||||
module_name = path_to_module(extension_path)
|
||||
@ -181,6 +172,21 @@ class SlideLimits(object):
|
||||
Next = 3
|
||||
|
||||
|
||||
class Singleton(type):
|
||||
"""
|
||||
Provide a `Singleton` metaclass https://stackoverflow.com/questions/6760685/creating-a-singleton-in-python
|
||||
"""
|
||||
_instances = {}
|
||||
|
||||
def __call__(cls, *args, **kwargs):
|
||||
"""
|
||||
Create a new instance if one does not already exist.
|
||||
"""
|
||||
if cls not in cls._instances:
|
||||
cls._instances[cls] = super().__call__(*args, **kwargs)
|
||||
return cls._instances[cls]
|
||||
|
||||
|
||||
def de_hump(name):
|
||||
"""
|
||||
Change any Camel Case string to python string
|
||||
@ -394,7 +400,7 @@ def get_images_filter():
|
||||
global IMAGES_FILTER
|
||||
if not IMAGES_FILTER:
|
||||
log.debug('Generating images filter.')
|
||||
formats = list(map(bytes.decode, list(map(bytes, QtGui.QImageReader.supportedImageFormats()))))
|
||||
formats = list(map(bytes.decode, map(bytes, QtGui.QImageReader.supportedImageFormats())))
|
||||
visible_formats = '(*.{text})'.format(text='; *.'.join(formats))
|
||||
actual_formats = '(*.{text})'.format(text=' *.'.join(formats))
|
||||
IMAGES_FILTER = '{text} {visible} {actual}'.format(text=translate('OpenLP', 'Image Files'),
|
||||
|
@ -260,7 +260,7 @@ class ActionList(object):
|
||||
return
|
||||
# We have to do this to ensure that the loaded shortcut list e. g. STRG+O (German) is converted to CTRL+O,
|
||||
# which is only done when we convert the strings in this way (QKeySequencet -> uncode).
|
||||
shortcuts = list(map(QtGui.QKeySequence.toString, list(map(QtGui.QKeySequence, shortcuts))))
|
||||
shortcuts = list(map(QtGui.QKeySequence.toString, map(QtGui.QKeySequence, shortcuts)))
|
||||
# Check the alternate shortcut first, to avoid problems when the alternate shortcut becomes the primary shortcut
|
||||
# after removing the (initial) primary shortcut due to conflicts.
|
||||
if len(shortcuts) == 2:
|
||||
|
@ -29,7 +29,7 @@ from collections import namedtuple
|
||||
|
||||
from PyQt5 import QtCore, QtWidgets
|
||||
|
||||
from openlp.core.common import is_macosx, is_win
|
||||
from openlp.core.common import Singleton, is_macosx, is_win
|
||||
from openlp.core.common.applocation import AppLocation
|
||||
from openlp.core.common.settings import Settings
|
||||
|
||||
@ -327,22 +327,11 @@ class LanguageManager(object):
|
||||
return LanguageManager.__qm_list__
|
||||
|
||||
|
||||
class UiStrings(object):
|
||||
class UiStrings(metaclass=Singleton):
|
||||
"""
|
||||
Provide standard strings for objects to use.
|
||||
"""
|
||||
__instance__ = None
|
||||
|
||||
def __new__(cls):
|
||||
"""
|
||||
Override the default object creation method to return a single instance.
|
||||
"""
|
||||
if not cls.__instance__:
|
||||
cls.__instance__ = object.__new__(cls)
|
||||
cls.load(cls)
|
||||
return cls.__instance__
|
||||
|
||||
def load(self):
|
||||
def __init__(self):
|
||||
"""
|
||||
These strings should need a good reason to be retranslated elsewhere.
|
||||
Should some/more/less of these have an & attached?
|
||||
@ -436,6 +425,7 @@ class UiStrings(object):
|
||||
self.ResetLiveBG = translate('OpenLP.Ui', 'Reset live background.')
|
||||
self.RequiredShowInFooter = translate('OpenLP.Ui', 'Required, this will be displayed in footer.')
|
||||
self.Seconds = translate('OpenLP.Ui', 's', 'The abbreviated unit for seconds')
|
||||
self.SaveAndClose = translate('OpenLP.ui', translate('SongsPlugin.EditSongForm', '&Save && Close'))
|
||||
self.SaveAndPreview = translate('OpenLP.Ui', 'Save && Preview')
|
||||
self.Search = translate('OpenLP.Ui', 'Search')
|
||||
self.SearchThemes = translate('OpenLP.Ui', 'Search Themes...', 'Search bar place holder text ')
|
||||
@ -503,7 +493,7 @@ def format_time(text, local_time):
|
||||
"""
|
||||
return local_time.strftime(match.group())
|
||||
|
||||
return re.sub(r'\%[a-zA-Z]', match_formatting, text)
|
||||
return re.sub(r'%[a-zA-Z]', match_formatting, text)
|
||||
|
||||
|
||||
def get_locale_key(string, numeric=False):
|
||||
|
@ -23,29 +23,19 @@
|
||||
Provide Registry Services
|
||||
"""
|
||||
import logging
|
||||
import sys
|
||||
|
||||
from openlp.core.common import de_hump, trace_error_handler
|
||||
from openlp.core.common import Singleton, de_hump, trace_error_handler
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Registry(object):
|
||||
class Registry(metaclass=Singleton):
|
||||
"""
|
||||
This is the Component Registry. It is a singleton object and is used to provide a look up service for common
|
||||
objects.
|
||||
"""
|
||||
log.info('Registry loaded')
|
||||
__instance__ = None
|
||||
|
||||
def __new__(cls):
|
||||
"""
|
||||
Re-implement the __new__ method to make sure we create a true singleton.
|
||||
"""
|
||||
if not cls.__instance__:
|
||||
cls.__instance__ = object.__new__(cls)
|
||||
return cls.__instance__
|
||||
|
||||
@classmethod
|
||||
def create(cls):
|
||||
@ -57,20 +47,9 @@ class Registry(object):
|
||||
registry.service_list = {}
|
||||
registry.functions_list = {}
|
||||
registry.working_flags = {}
|
||||
# Allow the tests to remove Registry entries but not the live system
|
||||
registry.running_under_test = 'nose' in sys.argv[0] or 'pytest' in sys.argv[0]
|
||||
registry.initialising = True
|
||||
return registry
|
||||
|
||||
@classmethod
|
||||
def destroy(cls):
|
||||
"""
|
||||
Destroy the Registry.
|
||||
"""
|
||||
if cls.__instance__.running_under_test:
|
||||
del cls.__instance__
|
||||
cls.__instance__ = None
|
||||
|
||||
def get(self, key):
|
||||
"""
|
||||
Extracts the registry value from the list based on the key passed in
|
||||
|
@ -90,12 +90,13 @@ def upgrade_screens(number, x_position, y_position, height, width, can_override,
|
||||
number: {
|
||||
'number': number,
|
||||
geometry_key: {
|
||||
'x': x_position,
|
||||
'y': y_position,
|
||||
'height': height,
|
||||
'width': width
|
||||
'x': int(x_position),
|
||||
'y': int(y_position),
|
||||
'height': int(height),
|
||||
'width': int(width)
|
||||
},
|
||||
'is_display': is_display_screen
|
||||
'is_display': is_display_screen,
|
||||
'is_primary': can_override
|
||||
}
|
||||
}
|
||||
|
||||
@ -309,7 +310,7 @@ class Settings(QtCore.QSettings):
|
||||
('songuasge/db hostname', 'songusage/db hostname', []),
|
||||
('songuasge/db database', 'songusage/db database', []),
|
||||
('presentations / Powerpoint Viewer', '', []),
|
||||
(['core/monitor', 'core/x position', 'core/y position', 'core/height', 'core/width', 'core/override',
|
||||
(['core/monitor', 'core/x position', 'core/y position', 'core/height', 'core/width', 'core/override position',
|
||||
'core/display on monitor'], 'core/screens', [(upgrade_screens, [1, 0, 0, None, None, False, False])]),
|
||||
('bibles/proxy name', '', []), # Just remove these bible proxy settings. They weren't used in 2.4!
|
||||
('bibles/proxy address', '', []),
|
||||
|
@ -384,8 +384,9 @@ var Display = {
|
||||
* Checks if the present slide content fits within the slide
|
||||
*/
|
||||
doesContentFit: function () {
|
||||
console.debug("scrollHeight: " + $(".slides")[0].scrollHeight + ", clientHeight: " + $(".slides")[0].clientHeight);
|
||||
return $(".slides")[0].clientHeight >= $(".slides")[0].scrollHeight;
|
||||
var currSlide = $(".slides")[0];
|
||||
console.debug("scrollHeight: " + currSlide.scrollHeight + ", clientHeight: " + currSlide.clientHeight);
|
||||
return currSlide.clientHeight >= currSlide.scrollHeight;
|
||||
},
|
||||
/**
|
||||
* Generate the OpenLP startup splashscreen
|
||||
@ -436,7 +437,7 @@ var Display = {
|
||||
/**
|
||||
* Set fullscreen image from base64 data
|
||||
* @param {string} bg_color - The background color
|
||||
* @param {string} image - Path to the image
|
||||
* @param {string} image_data - base64 encoded image data
|
||||
*/
|
||||
setFullscreenImageFromData: function(bg_color, image_data) {
|
||||
Display.clearSlides();
|
||||
@ -596,9 +597,8 @@ var Display = {
|
||||
* @param {string} verse - The verse number, e.g. "v1"
|
||||
* @param {string} text - The HTML for the verse, e.g. "line1<br>line2"
|
||||
* @param {string} footer_text - The HTML for the footer"
|
||||
* @param {bool} [reinit=true] - Re-initialize Reveal. Defaults to true.
|
||||
*/
|
||||
addTextSlide: function (verse, text, footer_text) {
|
||||
addTextSlide: function (verse, text, footerText) {
|
||||
var html = _prepareText(text);
|
||||
if (this._slides.hasOwnProperty(verse)) {
|
||||
var slide = $("#" + verse)[0];
|
||||
@ -614,11 +614,9 @@ var Display = {
|
||||
slidesDiv.appendChild(slide);
|
||||
var slides = $(".slides > section");
|
||||
this._slides[verse] = slides.length - 1;
|
||||
|
||||
console.debug(" footer_text: " + footer_text);
|
||||
|
||||
var footerDiv = $(".footer")[0];
|
||||
footerDiv.innerHTML = footer_text;
|
||||
if (footerText) {
|
||||
$(".footer")[0].innerHTML = footerText;
|
||||
}
|
||||
}
|
||||
if ((arguments.length > 3) && (arguments[3] === true)) {
|
||||
this.reinit();
|
||||
@ -650,9 +648,10 @@ var Display = {
|
||||
var section = document.createElement("section");
|
||||
section.setAttribute("id", index);
|
||||
section.setAttribute("data-background", "#000");
|
||||
section.setAttribute("style", "height: 100%; width: 100%;");
|
||||
var img = document.createElement('img');
|
||||
img.src = slide["path"];
|
||||
img.setAttribute("style", "height: 100%; width: 100%;");
|
||||
img.setAttribute("style", "max-width: 100%; max-height: 100%; margin: 0; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);");
|
||||
section.appendChild(img);
|
||||
slidesDiv.appendChild(section);
|
||||
Display._slides[index.toString()] = index;
|
||||
@ -700,25 +699,28 @@ var Display = {
|
||||
* Play a video
|
||||
*/
|
||||
playVideo: function () {
|
||||
if ($("#video").length == 1) {
|
||||
$("#video")[0].play();
|
||||
var videoElem = $("#video");
|
||||
if (videoElem.length == 1) {
|
||||
videoElem[0].play();
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Pause a video
|
||||
*/
|
||||
pauseVideo: function () {
|
||||
if ($("#video").length == 1) {
|
||||
$("#video")[0].pause();
|
||||
var videoElem = $("#video");
|
||||
if (videoElem.length == 1) {
|
||||
videoElem[0].pause();
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Stop a video
|
||||
*/
|
||||
stopVideo: function () {
|
||||
if ($("#video").length == 1) {
|
||||
$("#video")[0].pause();
|
||||
$("#video")[0].currentTime = 0.0;
|
||||
var videoElem = $("#video");
|
||||
if (videoElem.length == 1) {
|
||||
videoElem[0].pause();
|
||||
videoElem[0].currentTime = 0.0;
|
||||
}
|
||||
},
|
||||
/**
|
||||
@ -726,8 +728,9 @@ var Display = {
|
||||
* @param seconds The position in seconds to seek to
|
||||
*/
|
||||
seekVideo: function (seconds) {
|
||||
if ($("#video").length == 1) {
|
||||
$("#video")[0].currentTime = seconds;
|
||||
var videoElem = $("#video");
|
||||
if (videoElem.length == 1) {
|
||||
videoElem[0].currentTime = seconds;
|
||||
}
|
||||
},
|
||||
/**
|
||||
@ -735,8 +738,9 @@ var Display = {
|
||||
* @param rate A Double of the rate. 1.0 => 100% speed, 0.75 => 75% speed, 1.25 => 125% speed, etc.
|
||||
*/
|
||||
setPlaybackRate: function (rate) {
|
||||
if ($("#video").length == 1) {
|
||||
$("#video")[0].playbackRate = rate;
|
||||
var videoElem = $("#video");
|
||||
if (videoElem.length == 1) {
|
||||
videoElem[0].playbackRate = rate;
|
||||
}
|
||||
},
|
||||
/**
|
||||
@ -744,24 +748,27 @@ var Display = {
|
||||
* @param level The volume level from 0 to 100.
|
||||
*/
|
||||
setVideoVolume: function (level) {
|
||||
if ($("#video").length == 1) {
|
||||
$("#video")[0].volume = level / 100.0;
|
||||
var videoElem = $("#video");
|
||||
if (videoElem.length == 1) {
|
||||
videoElem[0].volume = level / 100.0;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Mute the volume
|
||||
*/
|
||||
toggleVideoMute: function () {
|
||||
if ($("#video").length == 1) {
|
||||
$("#video")[0].muted = !$("#video")[0].muted;
|
||||
var videoElem = $("#video");
|
||||
if (videoElem.length == 1) {
|
||||
videoElem[0].muted = !videoElem[0].muted;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Clear the background audio playlist
|
||||
*/
|
||||
clearPlaylist: function () {
|
||||
if ($("#background-audio").length == 1) {
|
||||
var audio = $("#background-audio")[0];
|
||||
var backgroundAudoElem = $("#background-audio");
|
||||
if (backgroundAudoElem.length == 1) {
|
||||
var audio = backgroundAudoElem[0];
|
||||
/* audio.playList */
|
||||
}
|
||||
},
|
||||
@ -843,7 +850,6 @@ var Display = {
|
||||
},
|
||||
setTheme: function (theme) {
|
||||
this._theme = theme;
|
||||
var slidesDiv = $(".slides")
|
||||
// Set the background
|
||||
var globalBackground = $("#global-background")[0];
|
||||
var backgroundStyle = {};
|
||||
|
@ -47,7 +47,7 @@ log = logging.getLogger(__name__)
|
||||
|
||||
SLIM_CHARS = 'fiíIÍjlĺľrtť.,;/ ()|"\'!:\\'
|
||||
CHORD_LINE_MATCH = re.compile(r'\[(.*?)\]([\u0080-\uFFFF,\w]*)'
|
||||
r'([\u0080-\uFFFF,\w,\s,\.,\,,\!,\?,\;,\:,\|,\",\',\-,\_]*)(\Z)?')
|
||||
r'([\u0080-\uFFFF\w\s\.\,\!\?\;\:\|\"\'\-\_]*)(\Z)?')
|
||||
CHORD_TEMPLATE = '<span class="chordline">{chord}</span>'
|
||||
FIRST_CHORD_TEMPLATE = '<span class="chordline firstchordline">{chord}</span>'
|
||||
CHORD_LINE_TEMPLATE = '<span class="chord"><span><strong>{chord}</strong></span></span>{tail}{whitespace}{remainder}'
|
||||
@ -67,6 +67,15 @@ FOOTER_COPYRIGHT = 'Public Domain'
|
||||
CCLI_NO = '123456'
|
||||
|
||||
|
||||
def remove_chords(text):
|
||||
"""
|
||||
Remove chords from the text
|
||||
|
||||
:param text: Text to be cleaned
|
||||
"""
|
||||
return re.sub(r'\[.+?\]', r'', text)
|
||||
|
||||
|
||||
def remove_tags(text, can_remove_chords=False):
|
||||
"""
|
||||
Remove Tags from text for display
|
||||
@ -82,7 +91,7 @@ def remove_tags(text, can_remove_chords=False):
|
||||
text = text.replace(tag['end tag'], '')
|
||||
# Remove ChordPro tags
|
||||
if can_remove_chords:
|
||||
text = re.sub(r'\[.+?\]', r'', text)
|
||||
text = remove_chords(text)
|
||||
return text
|
||||
|
||||
|
||||
@ -377,6 +386,8 @@ def render_tags(text, can_render_chords=False, is_printing=False):
|
||||
text = render_chords_for_printing(text, '{br}')
|
||||
else:
|
||||
text = render_chords(text)
|
||||
else:
|
||||
text = remove_chords(text)
|
||||
for tag in FormattingTags.get_html_tags():
|
||||
text = text.replace(tag['start tag'], tag['start html'])
|
||||
text = text.replace(tag['end tag'], tag['end html'])
|
||||
@ -482,6 +493,7 @@ class ThemePreviewRenderer(LogMixin, DisplayWindow):
|
||||
|
||||
:param theme_data: The theme to generated a preview for.
|
||||
:param force_page: Flag to tell message lines per page need to be generated.
|
||||
:param generate_screenshot: Do I need to generate a screen shot?
|
||||
:rtype: QtGui.QPixmap
|
||||
"""
|
||||
# save value for use in format_slide
|
||||
@ -502,6 +514,26 @@ class ThemePreviewRenderer(LogMixin, DisplayWindow):
|
||||
self.force_page = False
|
||||
return None
|
||||
|
||||
def get_theme(self, item):
|
||||
"""
|
||||
:param item: The :class:`~openlp.core.lib.serviceitem.ServiceItem` item object
|
||||
:return string: The name of the theme to be used
|
||||
|
||||
"""
|
||||
# Just assume we use the global theme.
|
||||
theme_name = Registry().get('theme_manager').global_theme
|
||||
# The theme level is either set to Service or Item. Use the service theme if one is set. We also have to use the
|
||||
# service theme, even when the theme level is set to Item, because the item does not necessarily have to have a
|
||||
# theme.
|
||||
if self.theme_level != ThemeLevel.Global:
|
||||
# When the theme level is at Service and we actually have a service theme then use it.
|
||||
if self.theme_level == ThemeLevel.Service:
|
||||
theme_name = Registry().get('service_manager').service_theme
|
||||
# If we have Item level and have an item theme then use it.
|
||||
if self.theme_level == ThemeLevel.Song and item.theme:
|
||||
theme_name = item.theme
|
||||
return theme_name
|
||||
|
||||
def format_slide(self, text, item):
|
||||
"""
|
||||
Calculate how much text can fit on a slide.
|
||||
@ -514,7 +546,7 @@ class ThemePreviewRenderer(LogMixin, DisplayWindow):
|
||||
QtWidgets.QApplication.instance().processEvents()
|
||||
self.log_debug('format slide')
|
||||
if item:
|
||||
theme_name = item.theme if item.theme else Registry().get('theme_manager').global_theme
|
||||
theme_name = self.get_theme(item)
|
||||
theme_data = Registry().get('theme_manager').get_theme_data(theme_name)
|
||||
self.theme_height = theme_data.font_main_height
|
||||
# Set theme for preview
|
||||
|
@ -28,6 +28,7 @@ from functools import cmp_to_key
|
||||
|
||||
from PyQt5 import QtCore, QtWidgets
|
||||
|
||||
from openlp.core.common import Singleton
|
||||
from openlp.core.common.i18n import translate
|
||||
from openlp.core.common.registry import Registry
|
||||
from openlp.core.common.settings import Settings
|
||||
@ -133,8 +134,13 @@ class Screen(object):
|
||||
self.number = int(screen_dict['number'])
|
||||
self.is_display = screen_dict['is_display']
|
||||
self.is_primary = screen_dict['is_primary']
|
||||
self.geometry = QtCore.QRect(screen_dict['geometry']['x'], screen_dict['geometry']['y'],
|
||||
screen_dict['geometry']['width'], screen_dict['geometry']['height'])
|
||||
try:
|
||||
self.geometry = QtCore.QRect(screen_dict['geometry']['x'], screen_dict['geometry']['y'],
|
||||
screen_dict['geometry']['width'], screen_dict['geometry']['height'])
|
||||
except KeyError:
|
||||
# Preserve the current values as this has come from the settings update which does not have
|
||||
# the geometry information
|
||||
pass
|
||||
if 'custom_geometry' in screen_dict:
|
||||
self.custom_geometry = QtCore.QRect(screen_dict['custom_geometry']['x'],
|
||||
screen_dict['custom_geometry']['y'],
|
||||
@ -142,24 +148,15 @@ class Screen(object):
|
||||
screen_dict['custom_geometry']['height'])
|
||||
|
||||
|
||||
class ScreenList(object):
|
||||
class ScreenList(metaclass=Singleton):
|
||||
"""
|
||||
Wrapper to handle the parameters of the display screen.
|
||||
|
||||
To get access to the screen list call ``ScreenList()``.
|
||||
"""
|
||||
log.info('Screen loaded')
|
||||
__instance__ = None
|
||||
screens = []
|
||||
|
||||
def __new__(cls):
|
||||
"""
|
||||
Re-implement __new__ to create a true singleton.
|
||||
"""
|
||||
if not cls.__instance__:
|
||||
cls.__instance__ = object.__new__(cls)
|
||||
return cls.__instance__
|
||||
|
||||
def __iter__(self):
|
||||
"""
|
||||
Convert this object into an iterable, so that we can iterate over it instead of the inner list
|
||||
|
@ -67,7 +67,7 @@ def database_exists(url):
|
||||
create_database(engine.url)
|
||||
database_exists(engine.url) #=> True
|
||||
|
||||
Borrowed from SQLAlchemy_Utils (v0.32.14 )since we only need this one function.
|
||||
Borrowed from SQLAlchemy_Utils (v0.32.14) since we only need this one function.
|
||||
"""
|
||||
|
||||
url = copy(make_url(url))
|
||||
@ -265,7 +265,7 @@ def upgrade_db(url, upgrade):
|
||||
"""
|
||||
if not database_exists(url):
|
||||
log.warning("Database {db} doesn't exist - skipping upgrade checks".format(db=url))
|
||||
return (0, 0)
|
||||
return 0, 0
|
||||
|
||||
log.debug('Checking upgrades for DB {db}'.format(db=url))
|
||||
|
||||
|
@ -59,97 +59,98 @@ class FormattingTags(object):
|
||||
"""
|
||||
temporary_tags = [tag for tag in FormattingTags.html_expands if tag.get('temporary')]
|
||||
FormattingTags.html_expands = []
|
||||
base_tags = []
|
||||
base_tags = [
|
||||
{
|
||||
'desc': translate('OpenLP.FormattingTags', 'Red'),
|
||||
'start tag': '{r}',
|
||||
'start html': '<span style="-webkit-text-fill-color:red">',
|
||||
'end tag': '{/r}', 'end html': '</span>', 'protected': True,
|
||||
'temporary': False
|
||||
}, {
|
||||
'desc': translate('OpenLP.FormattingTags', 'Black'),
|
||||
'start tag': '{b}',
|
||||
'start html': '<span style="-webkit-text-fill-color:black">',
|
||||
'end tag': '{/b}', 'end html': '</span>', 'protected': True,
|
||||
'temporary': False
|
||||
}, {
|
||||
'desc': translate('OpenLP.FormattingTags', 'Blue'),
|
||||
'start tag': '{bl}',
|
||||
'start html': '<span style="-webkit-text-fill-color:blue">',
|
||||
'end tag': '{/bl}', 'end html': '</span>', 'protected': True,
|
||||
'temporary': False
|
||||
}, {
|
||||
'desc': translate('OpenLP.FormattingTags', 'Yellow'),
|
||||
'start tag': '{y}',
|
||||
'start html': '<span style="-webkit-text-fill-color:yellow">',
|
||||
'end tag': '{/y}', 'end html': '</span>', 'protected': True,
|
||||
'temporary': False
|
||||
}, {
|
||||
'desc': translate('OpenLP.FormattingTags', 'Green'),
|
||||
'start tag': '{g}',
|
||||
'start html': '<span style="-webkit-text-fill-color:green">',
|
||||
'end tag': '{/g}', 'end html': '</span>', 'protected': True,
|
||||
'temporary': False
|
||||
}, {
|
||||
'desc': translate('OpenLP.FormattingTags', 'Pink'),
|
||||
'start tag': '{pk}',
|
||||
'start html': '<span style="-webkit-text-fill-color:#FFC0CB">',
|
||||
'end tag': '{/pk}', 'end html': '</span>', 'protected': True,
|
||||
'temporary': False
|
||||
}, {
|
||||
'desc': translate('OpenLP.FormattingTags', 'Orange'),
|
||||
'start tag': '{o}',
|
||||
'start html': '<span style="-webkit-text-fill-color:#FFA500">',
|
||||
'end tag': '{/o}', 'end html': '</span>', 'protected': True,
|
||||
'temporary': False
|
||||
}, {
|
||||
'desc': translate('OpenLP.FormattingTags', 'Purple'),
|
||||
'start tag': '{pp}',
|
||||
'start html': '<span style="-webkit-text-fill-color:#800080">',
|
||||
'end tag': '{/pp}', 'end html': '</span>', 'protected': True,
|
||||
'temporary': False
|
||||
}, {
|
||||
'desc': translate('OpenLP.FormattingTags', 'White'),
|
||||
'start tag': '{w}',
|
||||
'start html': '<span style="-webkit-text-fill-color:white">',
|
||||
'end tag': '{/w}', 'end html': '</span>', 'protected': True,
|
||||
'temporary': False
|
||||
}, {
|
||||
'desc': translate('OpenLP.FormattingTags', 'Superscript'),
|
||||
'start tag': '{su}', 'start html': '<sup>',
|
||||
'end tag': '{/su}', 'end html': '</sup>', 'protected': True,
|
||||
'temporary': False
|
||||
}, {
|
||||
'desc': translate('OpenLP.FormattingTags', 'Subscript'),
|
||||
'start tag': '{sb}', 'start html': '<sub>',
|
||||
'end tag': '{/sb}', 'end html': '</sub>', 'protected': True,
|
||||
'temporary': False
|
||||
}, {
|
||||
'desc': translate('OpenLP.FormattingTags', 'Paragraph'),
|
||||
'start tag': '{p}', 'start html': '<p>', 'end tag': '{/p}',
|
||||
'end html': '</p>', 'protected': True,
|
||||
'temporary': False
|
||||
}, {
|
||||
'desc': translate('OpenLP.FormattingTags', 'Bold'),
|
||||
'start tag': '{st}', 'start html': '<strong>',
|
||||
'end tag': '{/st}', 'end html': '</strong>',
|
||||
'protected': True, 'temporary': False
|
||||
}, {
|
||||
'desc': translate('OpenLP.FormattingTags', 'Italics'),
|
||||
'start tag': '{it}', 'start html': '<em>', 'end tag': '{/it}',
|
||||
'end html': '</em>', 'protected': True, 'temporary': False
|
||||
}, {
|
||||
'desc': translate('OpenLP.FormattingTags', 'Underline'),
|
||||
'start tag': '{u}',
|
||||
'start html': '<span style="text-decoration: underline;">',
|
||||
'end tag': '{/u}', 'end html': '</span>', 'protected': True,
|
||||
'temporary': False
|
||||
}, {
|
||||
'desc': translate('OpenLP.FormattingTags', 'Break'),
|
||||
'start tag': '{br}', 'start html': '<br>', 'end tag': '',
|
||||
'end html': '', 'protected': True,
|
||||
'temporary': False
|
||||
}]
|
||||
# Append the base tags.
|
||||
base_tags.append({
|
||||
'desc': translate('OpenLP.FormattingTags', 'Red'),
|
||||
'start tag': '{r}',
|
||||
'start html': '<span style="-webkit-text-fill-color:red">',
|
||||
'end tag': '{/r}', 'end html': '</span>', 'protected': True,
|
||||
'temporary': False})
|
||||
base_tags.append({
|
||||
'desc': translate('OpenLP.FormattingTags', 'Black'),
|
||||
'start tag': '{b}',
|
||||
'start html': '<span style="-webkit-text-fill-color:black">',
|
||||
'end tag': '{/b}', 'end html': '</span>', 'protected': True,
|
||||
'temporary': False})
|
||||
base_tags.append({
|
||||
'desc': translate('OpenLP.FormattingTags', 'Blue'),
|
||||
'start tag': '{bl}',
|
||||
'start html': '<span style="-webkit-text-fill-color:blue">',
|
||||
'end tag': '{/bl}', 'end html': '</span>', 'protected': True,
|
||||
'temporary': False})
|
||||
base_tags.append({
|
||||
'desc': translate('OpenLP.FormattingTags', 'Yellow'),
|
||||
'start tag': '{y}',
|
||||
'start html': '<span style="-webkit-text-fill-color:yellow">',
|
||||
'end tag': '{/y}', 'end html': '</span>', 'protected': True,
|
||||
'temporary': False})
|
||||
base_tags.append({
|
||||
'desc': translate('OpenLP.FormattingTags', 'Green'),
|
||||
'start tag': '{g}',
|
||||
'start html': '<span style="-webkit-text-fill-color:green">',
|
||||
'end tag': '{/g}', 'end html': '</span>', 'protected': True,
|
||||
'temporary': False})
|
||||
base_tags.append({
|
||||
'desc': translate('OpenLP.FormattingTags', 'Pink'),
|
||||
'start tag': '{pk}',
|
||||
'start html': '<span style="-webkit-text-fill-color:#FFC0CB">',
|
||||
'end tag': '{/pk}', 'end html': '</span>', 'protected': True,
|
||||
'temporary': False})
|
||||
base_tags.append({
|
||||
'desc': translate('OpenLP.FormattingTags', 'Orange'),
|
||||
'start tag': '{o}',
|
||||
'start html': '<span style="-webkit-text-fill-color:#FFA500">',
|
||||
'end tag': '{/o}', 'end html': '</span>', 'protected': True,
|
||||
'temporary': False})
|
||||
base_tags.append({
|
||||
'desc': translate('OpenLP.FormattingTags', 'Purple'),
|
||||
'start tag': '{pp}',
|
||||
'start html': '<span style="-webkit-text-fill-color:#800080">',
|
||||
'end tag': '{/pp}', 'end html': '</span>', 'protected': True,
|
||||
'temporary': False})
|
||||
base_tags.append({
|
||||
'desc': translate('OpenLP.FormattingTags', 'White'),
|
||||
'start tag': '{w}',
|
||||
'start html': '<span style="-webkit-text-fill-color:white">',
|
||||
'end tag': '{/w}', 'end html': '</span>', 'protected': True,
|
||||
'temporary': False})
|
||||
base_tags.append({
|
||||
'desc': translate('OpenLP.FormattingTags', 'Superscript'),
|
||||
'start tag': '{su}', 'start html': '<sup>',
|
||||
'end tag': '{/su}', 'end html': '</sup>', 'protected': True,
|
||||
'temporary': False})
|
||||
base_tags.append({
|
||||
'desc': translate('OpenLP.FormattingTags', 'Subscript'),
|
||||
'start tag': '{sb}', 'start html': '<sub>',
|
||||
'end tag': '{/sb}', 'end html': '</sub>', 'protected': True,
|
||||
'temporary': False})
|
||||
base_tags.append({
|
||||
'desc': translate('OpenLP.FormattingTags', 'Paragraph'),
|
||||
'start tag': '{p}', 'start html': '<p>', 'end tag': '{/p}',
|
||||
'end html': '</p>', 'protected': True,
|
||||
'temporary': False})
|
||||
base_tags.append({
|
||||
'desc': translate('OpenLP.FormattingTags', 'Bold'),
|
||||
'start tag': '{st}', 'start html': '<strong>',
|
||||
'end tag': '{/st}', 'end html': '</strong>',
|
||||
'protected': True, 'temporary': False})
|
||||
base_tags.append({
|
||||
'desc': translate('OpenLP.FormattingTags', 'Italics'),
|
||||
'start tag': '{it}', 'start html': '<em>', 'end tag': '{/it}',
|
||||
'end html': '</em>', 'protected': True, 'temporary': False})
|
||||
base_tags.append({
|
||||
'desc': translate('OpenLP.FormattingTags', 'Underline'),
|
||||
'start tag': '{u}',
|
||||
'start html': '<span style="text-decoration: underline;">',
|
||||
'end tag': '{/u}', 'end html': '</span>', 'protected': True,
|
||||
'temporary': False})
|
||||
base_tags.append({
|
||||
'desc': translate('OpenLP.FormattingTags', 'Break'),
|
||||
'start tag': '{br}', 'start html': '<br>', 'end tag': '',
|
||||
'end html': '', 'protected': True,
|
||||
'temporary': False})
|
||||
FormattingTags.add_html_tags(base_tags)
|
||||
FormattingTags.add_html_tags(temporary_tags)
|
||||
user_expands_string = str(Settings().value('formattingTags/html_tags'))
|
||||
|
@ -39,7 +39,7 @@ from openlp.core.common.applocation import AppLocation
|
||||
from openlp.core.common.i18n import translate
|
||||
from openlp.core.common.mixins import RegistryProperties
|
||||
from openlp.core.common.settings import Settings
|
||||
from openlp.core.display.render import remove_tags, render_tags
|
||||
from openlp.core.display.render import remove_tags, render_tags, render_chords_for_printing
|
||||
from openlp.core.lib import ItemCapabilities
|
||||
from openlp.core.ui.icons import UiIcons
|
||||
|
||||
@ -74,6 +74,7 @@ class ServiceItem(RegistryProperties):
|
||||
self.name = plugin.name
|
||||
self._rendered_slides = None
|
||||
self._display_slides = None
|
||||
self._print_slides = None
|
||||
self.title = ''
|
||||
self.slides = []
|
||||
self.processor = None
|
||||
@ -185,7 +186,7 @@ class ServiceItem(RegistryProperties):
|
||||
self._rendered_slides.append(rendered_slide)
|
||||
display_slide = {
|
||||
'title': raw_slide['title'],
|
||||
'text': remove_tags(page),
|
||||
'text': remove_tags(page, can_remove_chords=True),
|
||||
'verse': verse_tag,
|
||||
}
|
||||
self._display_slides.append(display_slide)
|
||||
@ -209,6 +210,34 @@ class ServiceItem(RegistryProperties):
|
||||
self._create_slides()
|
||||
return self._display_slides
|
||||
|
||||
@property
|
||||
def print_slides(self):
|
||||
"""
|
||||
Render the frames for printing and return them
|
||||
|
||||
:param can_render_chords: bool Whether or not to render the chords
|
||||
"""
|
||||
if not self._print_slides:
|
||||
self._print_slides = []
|
||||
previous_pages = {}
|
||||
index = 0
|
||||
for raw_slide in self.slides:
|
||||
verse_tag = raw_slide['verse']
|
||||
if verse_tag in previous_pages and previous_pages[verse_tag][0] == raw_slide:
|
||||
pages = previous_pages[verse_tag][1]
|
||||
else:
|
||||
pages = self.renderer.format_slide(raw_slide['text'], self)
|
||||
previous_pages[verse_tag] = (raw_slide, pages)
|
||||
for page in pages:
|
||||
slide = {
|
||||
'title': raw_slide['title'],
|
||||
'text': render_chords_for_printing(remove_tags(page), '\n'),
|
||||
'verse': index,
|
||||
'footer': self.raw_footer,
|
||||
}
|
||||
self._print_slides.append(slide)
|
||||
return self._print_slides
|
||||
|
||||
def add_from_image(self, path, title, background=None, thumbnail=None):
|
||||
"""
|
||||
Add an image slide to the service item.
|
||||
@ -321,6 +350,13 @@ class ServiceItem(RegistryProperties):
|
||||
'display_title': slide['display_title'], 'notes': slide['notes']})
|
||||
return {'header': service_header, 'data': service_data}
|
||||
|
||||
def render_text_items(self):
|
||||
"""
|
||||
This method forces the display to be regenerated
|
||||
"""
|
||||
self._display_slides = []
|
||||
self._rendered_slides = []
|
||||
|
||||
def set_from_service(self, service_item, path=None):
|
||||
"""
|
||||
This method takes a service item from a saved service file (passed from the ServiceManager) and extracts the
|
||||
@ -503,7 +539,6 @@ class ServiceItem(RegistryProperties):
|
||||
:param row: The service item slide to be returned
|
||||
"""
|
||||
if self.service_item_type == ServiceItemType.Text:
|
||||
# return self.display_frames[row]['html'].split('\n')[0]
|
||||
return self.rendered_slides[row]['text']
|
||||
elif self.service_item_type == ServiceItemType.Image:
|
||||
return self.slides[row]['path']
|
||||
|
@ -170,6 +170,7 @@ class Theme(object):
|
||||
jsn = get_text_file_string(json_path)
|
||||
self.load_theme(jsn)
|
||||
self.background_filename = None
|
||||
self.version = 2
|
||||
|
||||
def expand_json(self, var, prev=None):
|
||||
"""
|
||||
|
@ -344,14 +344,14 @@ class ProjectorManager(QtWidgets.QWidget, RegistryBase, UiProjectorManager, LogM
|
||||
"""
|
||||
log.debug('Checking for UDP port {port} listener deletion'.format(port=port))
|
||||
if port not in self.pjlink_udp:
|
||||
log.warn('UDP listener for port {port} not there - skipping delete'.format(port=port))
|
||||
log.warning('UDP listener for port {port} not there - skipping delete'.format(port=port))
|
||||
return
|
||||
keep_port = False
|
||||
for item in self.projector_list:
|
||||
if port == item.link.port:
|
||||
keep_port = True
|
||||
if keep_port:
|
||||
log.warn('UDP listener for port {port} needed for other projectors - skipping delete'.format(port=port))
|
||||
log.warning('UDP listener for port {port} needed for other projectors - skipping delete'.format(port=port))
|
||||
return
|
||||
Registry().execute('udp_broadcast_remove', port=port)
|
||||
del self.pjlink_udp[port]
|
||||
|
@ -552,7 +552,7 @@ class PJLink(QtNetwork.QTcpSocket):
|
||||
data = data_in.strip()
|
||||
self.receive_data_signal()
|
||||
# Initial packet checks
|
||||
if (len(data) < 7):
|
||||
if len(data) < 7:
|
||||
self._trash_buffer(msg='get_data(): Invalid packet - length')
|
||||
return
|
||||
elif len(data) > self.max_size:
|
||||
|
@ -177,7 +177,7 @@ class FingerTabBarWidget(QtWidgets.QTabBar):
|
||||
:param height: Remove default height parameter in kwargs
|
||||
"""
|
||||
self.tabSize = QtCore.QSize(kwargs.pop('width', 100), kwargs.pop('height', 25))
|
||||
QtWidgets.QTabBar.__init__(self, parent, *args, **kwargs)
|
||||
super().__init__(parent)
|
||||
|
||||
def paintEvent(self, event):
|
||||
"""
|
||||
@ -215,11 +215,11 @@ class FingerTabWidget(QtWidgets.QTabWidget):
|
||||
Based on thread discussion
|
||||
http://www.riverbankcomputing.com/pipermail/pyqt/2005-December/011724.html
|
||||
"""
|
||||
def __init__(self, parent, *args):
|
||||
def __init__(self, parent):
|
||||
"""
|
||||
Initialize FingerTabWidget instance
|
||||
"""
|
||||
QtWidgets.QTabWidget.__init__(self, parent, *args)
|
||||
super().__init__(parent)
|
||||
self.setTabBar(FingerTabBarWidget(self))
|
||||
|
||||
|
||||
|
@ -28,6 +28,7 @@ contained within the openlp.core module.
|
||||
"""
|
||||
import logging
|
||||
|
||||
from openlp.core.common import Singleton
|
||||
from openlp.core.common.registry import Registry
|
||||
from openlp.core.common.mixins import LogMixin
|
||||
from openlp.core.lib.plugin import PluginStatus
|
||||
@ -52,17 +53,7 @@ class StateModule(LogMixin):
|
||||
self.text = None
|
||||
|
||||
|
||||
class State(LogMixin):
|
||||
|
||||
__instance__ = None
|
||||
|
||||
def __new__(cls):
|
||||
"""
|
||||
Re-implement the __new__ method to make sure we create a true singleton.
|
||||
"""
|
||||
if not cls.__instance__:
|
||||
cls.__instance__ = object.__new__(cls)
|
||||
return cls.__instance__
|
||||
class State(LogMixin, metaclass=Singleton):
|
||||
|
||||
def load_settings(self):
|
||||
self.modules = {}
|
||||
|
@ -24,10 +24,11 @@ The :mod:`openlp.core.threading` module contains some common threading code
|
||||
"""
|
||||
from PyQt5 import QtCore
|
||||
|
||||
from openlp.core.common.mixins import LogMixin
|
||||
from openlp.core.common.registry import Registry
|
||||
|
||||
|
||||
class ThreadWorker(QtCore.QObject):
|
||||
class ThreadWorker(QtCore.QObject, LogMixin):
|
||||
"""
|
||||
The :class:`~openlp.core.threading.ThreadWorker` class provides a base class for all worker objects
|
||||
"""
|
||||
|
@ -93,4 +93,4 @@ class SingleColumnTableWidget(QtWidgets.QTableWidget):
|
||||
self.resizeRowsToContents()
|
||||
|
||||
|
||||
__all__ = ['SingleColumnTableWidget', 'DisplayControllerType']
|
||||
__all__ = ['AlertLocation', 'DisplayControllerType', 'HideMode', 'SingleColumnTableWidget']
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -50,19 +50,20 @@ class AboutForm(QtWidgets.QDialog, UiAboutDialog):
|
||||
Set up the dialog. This method is mocked out in tests.
|
||||
"""
|
||||
self.setup_ui(self)
|
||||
self.button_box.buttons()[0].setFocus()
|
||||
application_version = get_version()
|
||||
about_text = self.about_text_edit.toPlainText()
|
||||
about_text = about_text.replace('<version>', application_version['version'])
|
||||
about_text = self.about_text_edit.toHtml()
|
||||
about_text = about_text.replace('{version}', application_version['version'])
|
||||
if application_version['build']:
|
||||
build_text = translate('OpenLP.AboutForm', ' build {version}').format(version=application_version['build'])
|
||||
else:
|
||||
build_text = ''
|
||||
about_text = about_text.replace('<revision>', build_text)
|
||||
self.about_text_edit.setPlainText(about_text)
|
||||
self.volunteer_button.clicked.connect(self.on_volunteer_button_clicked)
|
||||
about_text = about_text.replace('{revision}', build_text)
|
||||
self.about_text_edit.setHtml(about_text)
|
||||
self.contribute_button.clicked.connect(self.on_contribute_button_clicked)
|
||||
|
||||
def on_volunteer_button_clicked(self):
|
||||
def on_contribute_button_clicked(self):
|
||||
"""
|
||||
Launch a web browser and go to the contribute page on the site.
|
||||
"""
|
||||
webbrowser.open_new('http://openlp.org/en/contribute')
|
||||
webbrowser.open_new('http://openlp.org/contribute')
|
||||
|
@ -81,7 +81,7 @@ class AdvancedTab(SettingsTab):
|
||||
self.ui_layout.addRow(self.media_plugin_check_box)
|
||||
self.hide_mouse_check_box = QtWidgets.QCheckBox(self.ui_group_box)
|
||||
self.hide_mouse_check_box.setObjectName('hide_mouse_check_box')
|
||||
self.ui_layout.addWidget(self.hide_mouse_check_box)
|
||||
self.ui_layout.addRow(self.hide_mouse_check_box)
|
||||
self.double_click_live_check_box = QtWidgets.QCheckBox(self.ui_group_box)
|
||||
self.double_click_live_check_box.setObjectName('double_click_live_check_box')
|
||||
self.ui_layout.addRow(self.double_click_live_check_box)
|
||||
|
@ -40,7 +40,7 @@ class FormattingTagController(object):
|
||||
"""
|
||||
self.html_tag_regex = re.compile(
|
||||
r'<(?:(?P<close>/(?=[^\s/>]+>))?'
|
||||
r'(?P<tag>[^\s/!\?>]+)(?:\s+[^\s=]+="[^"]*")*\s*(?P<empty>/)?'
|
||||
r'(?P<tag>[^\s/!?>]+)(?:\s+[^\s=]+="[^"]*")*\s*(?P<empty>/)?'
|
||||
r'|(?P<cdata>!\[CDATA\[(?:(?!\]\]>).)*\]\])'
|
||||
r'|(?P<procinst>\?(?:(?!\?>).)*\?)'
|
||||
r'|(?P<comment>!--(?:(?!-->).)*--))>')
|
||||
|
@ -27,6 +27,7 @@ import logging
|
||||
import qtawesome as qta
|
||||
from PyQt5 import QtGui, QtWidgets
|
||||
|
||||
from openlp.core.common import Singleton
|
||||
from openlp.core.common.applocation import AppLocation
|
||||
from openlp.core.lib import build_icon
|
||||
|
||||
@ -34,22 +35,11 @@ from openlp.core.lib import build_icon
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class UiIcons(object):
|
||||
class UiIcons(metaclass=Singleton):
|
||||
"""
|
||||
Provide standard icons for objects to use.
|
||||
"""
|
||||
__instance__ = None
|
||||
|
||||
def __new__(cls):
|
||||
"""
|
||||
Override the default object creation method to return a single instance.
|
||||
"""
|
||||
if not cls.__instance__:
|
||||
cls.__instance__ = object.__new__(cls)
|
||||
cls.load(cls)
|
||||
return cls.__instance__
|
||||
|
||||
def load(self):
|
||||
def __init__(self):
|
||||
"""
|
||||
These are the font icons used in the code.
|
||||
"""
|
||||
@ -164,7 +154,8 @@ class UiIcons(object):
|
||||
'video': {'icon': 'fa.file-video-o'},
|
||||
'volunteer': {'icon': 'fa.group'}
|
||||
}
|
||||
self.load_icons(self, icon_list)
|
||||
self.load_icons(icon_list)
|
||||
self.main_icon = build_icon(':/icon/openlp-logo.svg')
|
||||
|
||||
def load_icons(self, icon_list):
|
||||
"""
|
||||
@ -184,7 +175,6 @@ class UiIcons(object):
|
||||
setattr(self, key, qta.icon('fa.plus-circle', color='red'))
|
||||
except Exception:
|
||||
setattr(self, key, qta.icon('fa.plus-circle', color='red'))
|
||||
self.main_icon = build_icon(':/icon/openlp-logo.svg')
|
||||
|
||||
@staticmethod
|
||||
def _print_icons():
|
||||
|
@ -33,8 +33,9 @@ from tempfile import gettempdir
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
from openlp.core.state import State
|
||||
from openlp.core.api import websockets
|
||||
from openlp.core.api.http import server
|
||||
from openlp.core.api.websockets import WebSocketServer
|
||||
from openlp.core.api.http.server import HttpServer
|
||||
from openlp.core.api.zeroconf import start_zeroconf
|
||||
from openlp.core.common import add_actions, is_macosx, is_win
|
||||
from openlp.core.common.actions import ActionList, CategoryOrder
|
||||
from openlp.core.common.applocation import AppLocation
|
||||
@ -495,8 +496,9 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, LogMixin, RegistryPropert
|
||||
self.copy_data = False
|
||||
Settings().set_up_default_values()
|
||||
self.about_form = AboutForm(self)
|
||||
self.ws_server = websockets.WebSocketServer()
|
||||
self.http_server = server.HttpServer(self)
|
||||
self.ws_server = WebSocketServer()
|
||||
self.http_server = HttpServer(self)
|
||||
start_zeroconf()
|
||||
SettingsForm(self)
|
||||
self.formatting_tag_form = FormattingTagForm(self)
|
||||
self.shortcut_form = ShortcutListForm(self)
|
||||
@ -544,7 +546,8 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, LogMixin, RegistryPropert
|
||||
Wait for the threads
|
||||
"""
|
||||
# Sometimes the threads haven't finished, let's wait for them
|
||||
wait_dialog = QtWidgets.QProgressDialog('Waiting for some things to finish...', '', 0, 0, self)
|
||||
wait_dialog = QtWidgets.QProgressDialog(translate('OpenLP.MainWindow', 'Waiting for some things to finish...'),
|
||||
'', 0, 0, self)
|
||||
wait_dialog.setWindowModality(QtCore.Qt.WindowModal)
|
||||
wait_dialog.setAutoClose(False)
|
||||
wait_dialog.setCancelButton(None)
|
||||
@ -633,7 +636,10 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, LogMixin, RegistryPropert
|
||||
# if self.live_controller.display.isVisible():
|
||||
# self.live_controller.display.setFocus()
|
||||
self.activateWindow()
|
||||
if self.application.args:
|
||||
# We have -disable-web-security added by our code.
|
||||
# If a file is passed in we will have that as well so count of 2
|
||||
# If not we need to see if we want to use the previous file.so count of 1
|
||||
if self.application.args and len(self.application.args) > 1:
|
||||
self.open_cmd_line_files(self.application.args)
|
||||
elif Settings().value(self.general_settings_section + '/auto open'):
|
||||
self.service_manager_contents.load_last_file()
|
||||
|
@ -218,14 +218,14 @@ class PrintServiceForm(QtWidgets.QDialog, Ui_PrintServiceDialog, RegistryPropert
|
||||
if item.is_text():
|
||||
verse_def = None
|
||||
verse_html = None
|
||||
for slide in item.get_frames():
|
||||
if not verse_def or verse_def != slide['verseTag'] or verse_html == slide['printing_html']:
|
||||
for slide in item.print_slides:
|
||||
if not verse_def or verse_def != slide['verse'] or verse_html == slide['text']:
|
||||
text_div = self._add_element('div', parent=div, class_id='itemText')
|
||||
elif 'chordspacing' not in slide['printing_html']:
|
||||
elif 'chordspacing' not in slide['text']:
|
||||
self._add_element('br', parent=text_div)
|
||||
self._add_element('span', slide['printing_html'], text_div)
|
||||
verse_def = slide['verseTag']
|
||||
verse_html = slide['printing_html']
|
||||
self._add_element('span', slide['text'], text_div)
|
||||
verse_def = slide['verse']
|
||||
verse_html = slide['text']
|
||||
# Break the page before the div element.
|
||||
if index != 0 and self.page_break_after_text.isChecked():
|
||||
div.set('class', 'item newPage')
|
||||
|
@ -53,7 +53,6 @@ class ScreensTab(SettingsTab):
|
||||
self.setObjectName('self')
|
||||
self.tab_layout = QtWidgets.QVBoxLayout(self)
|
||||
self.tab_layout.setObjectName('tab_layout')
|
||||
|
||||
self.screen_selection_widget = ScreenSelectionWidget(self, ScreenList())
|
||||
self.tab_layout.addWidget(self.screen_selection_widget)
|
||||
self.generic_group_box = QtWidgets.QGroupBox(self)
|
||||
@ -63,13 +62,11 @@ class ScreensTab(SettingsTab):
|
||||
self.display_on_monitor_check.setObjectName('monitor_combo_box')
|
||||
self.generic_group_layout.addWidget(self.display_on_monitor_check)
|
||||
self.tab_layout.addWidget(self.generic_group_box)
|
||||
|
||||
Registry().register_function('config_screen_changed', self.screen_selection_widget.load)
|
||||
|
||||
self.retranslate_ui()
|
||||
|
||||
def retranslate_ui(self):
|
||||
self.setWindowTitle(translate('self', 'self')) # TODO: ???
|
||||
self.generic_group_box.setTitle(translate('OpenLP.ScreensTab', 'Generic screen settings'))
|
||||
self.display_on_monitor_check.setText(translate('OpenLP.ScreensTab', 'Display if a single screen'))
|
||||
|
||||
|
@ -34,6 +34,7 @@ from tempfile import NamedTemporaryFile
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
from openlp.core.state import State
|
||||
from openlp.core.common import ThemeLevel, delete_file
|
||||
from openlp.core.common.actions import ActionList, CategoryOrder
|
||||
from openlp.core.common.applocation import AppLocation
|
||||
@ -45,7 +46,7 @@ from openlp.core.common.settings import Settings
|
||||
from openlp.core.lib import build_icon
|
||||
from openlp.core.lib.exceptions import ValidationError
|
||||
from openlp.core.lib.plugin import PluginStatus
|
||||
from openlp.core.lib.serviceitem import ItemCapabilities, ServiceItem
|
||||
from openlp.core.lib.serviceitem import ItemCapabilities, ServiceItem, ServiceItemType
|
||||
from openlp.core.lib.ui import create_widget_action, critical_error_message_box, find_and_set_in_combo_box
|
||||
from openlp.core.ui.icons import UiIcons
|
||||
from openlp.core.ui.media import AUDIO_EXT, VIDEO_EXT
|
||||
@ -748,9 +749,7 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi
|
||||
if theme:
|
||||
find_and_set_in_combo_box(self.theme_combo_box, theme, set_missing=False)
|
||||
if theme == self.theme_combo_box.currentText():
|
||||
# TODO: Use a local display widget
|
||||
# self.preview_display.set_theme(get_theme_from_name(theme))
|
||||
pass
|
||||
self.service_theme = theme
|
||||
else:
|
||||
if self._save_lite:
|
||||
service_item.set_from_service(item)
|
||||
@ -828,7 +827,7 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi
|
||||
self.auto_start_action.setIcon(UiIcons().inactive)
|
||||
self.auto_start_action.setText(translate('OpenLP.ServiceManager', '&Auto Start - inactive'))
|
||||
if service_item['service_item'].is_text():
|
||||
for plugin in self.plugin_manager.plugins:
|
||||
for plugin in State().list_plugins():
|
||||
if plugin.name == 'custom' and plugin.status == PluginStatus.Active:
|
||||
self.create_custom_action.setVisible(True)
|
||||
break
|
||||
@ -1196,9 +1195,9 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi
|
||||
service_item_from_item = item['service_item']
|
||||
tree_widget_item = QtWidgets.QTreeWidgetItem(self.service_manager_list)
|
||||
if service_item_from_item.is_valid:
|
||||
icon = service_item_from_item.icon.pixmap(80, 80).toImage()
|
||||
icon = icon.scaled(80, 80, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation)
|
||||
if service_item_from_item.notes:
|
||||
icon = service_item_from_item.icon.pixmap(80, 80).toImage()
|
||||
icon = icon.scaled(80, 80, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation)
|
||||
overlay = UiIcons().notes.pixmap(40, 40).toImage()
|
||||
overlay = overlay.scaled(40, 40, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation)
|
||||
painter = QtGui.QPainter(icon)
|
||||
@ -1206,8 +1205,6 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi
|
||||
painter.end()
|
||||
tree_widget_item.setIcon(0, build_icon(icon))
|
||||
elif service_item_from_item.temporary_edit:
|
||||
icon = service_item_from_item.icon.pixmap(80, 80).toImage()
|
||||
icon = icon.scaled(80, 80, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation)
|
||||
overlay = QtGui.QImage(UiIcons().upload)
|
||||
overlay = overlay.scaled(40, 40, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation)
|
||||
painter = QtGui.QPainter(icon)
|
||||
@ -1241,13 +1238,14 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi
|
||||
tree_widget_item.setData(0, QtCore.Qt.UserRole, item['order'])
|
||||
tree_widget_item.setSelected(item['selected'])
|
||||
# Add the children to their parent tree_widget_item.
|
||||
for slide_index, slide in enumerate(service_item_from_item.slides):
|
||||
for slide_index, slide in enumerate(service_item_from_item.get_frames()):
|
||||
child = QtWidgets.QTreeWidgetItem(tree_widget_item)
|
||||
# prefer to use a display_title
|
||||
if service_item_from_item.is_capable(ItemCapabilities.HasDisplayTitle):
|
||||
text = slide['display_title'].replace('\n', ' ')
|
||||
else:
|
||||
if service_item_from_item.is_capable(ItemCapabilities.HasDisplayTitle) or \
|
||||
service_item_from_item.service_item_type == ServiceItemType.Image:
|
||||
text = slide['title'].replace('\n', ' ')
|
||||
else:
|
||||
text = service_item_from_item.get_rendered_frame(slide_index)
|
||||
child.setText(0, text[:40])
|
||||
child.setData(0, QtCore.Qt.UserRole, slide_index)
|
||||
if service_item == item_index:
|
||||
@ -1274,8 +1272,6 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi
|
||||
:param current_index: The combo box index for the selected item
|
||||
"""
|
||||
self.service_theme = self.theme_combo_box.currentText()
|
||||
# TODO: Use a local display widget
|
||||
# self.preview_display.set_theme(get_theme_from_name(theme))
|
||||
Settings().setValue(self.main_window.service_manager_settings_section + '/service theme', self.service_theme)
|
||||
self.regenerate_service_items(True)
|
||||
|
||||
@ -1334,7 +1330,7 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi
|
||||
"""
|
||||
for item_count, item in enumerate(self.service_items):
|
||||
if item['service_item'].edit_id == new_item.edit_id and item['service_item'].name == new_item.name:
|
||||
new_item.render()
|
||||
new_item.create_slides()
|
||||
new_item.merge(item['service_item'])
|
||||
item['service_item'] = new_item
|
||||
self.repaint_service_list(item_count + 1, 0)
|
||||
@ -1367,7 +1363,7 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi
|
||||
self.repaint_service_list(s_item, child)
|
||||
self.live_controller.replace_service_manager_item(item)
|
||||
else:
|
||||
# item.render()
|
||||
item.render_text_items()
|
||||
# nothing selected for dnd
|
||||
if self.drop_position == -1:
|
||||
if isinstance(item, list):
|
||||
@ -1616,8 +1612,6 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi
|
||||
theme_group.addAction(create_widget_action(self.theme_menu, theme, text=theme, checked=False,
|
||||
triggers=self.on_theme_change_action))
|
||||
find_and_set_in_combo_box(self.theme_combo_box, self.service_theme)
|
||||
# TODO: Sort this out
|
||||
# self.renderer.set_service_theme(self.service_theme)
|
||||
self.regenerate_service_items()
|
||||
|
||||
def on_theme_change_action(self):
|
||||
|
@ -101,7 +101,7 @@ class Ui_ShortcutListDialog(object):
|
||||
self.primary_push_button = CaptureShortcutButton(shortcutListDialog)
|
||||
self.primary_push_button.setObjectName('primary_push_button')
|
||||
self.primary_push_button.setMinimumSize(QtCore.QSize(84, 0))
|
||||
self.primary_push_button.setIcon(UiIcons.shortcuts)
|
||||
self.primary_push_button.setIcon(UiIcons().shortcuts)
|
||||
self.primary_layout.addWidget(self.primary_push_button)
|
||||
self.clear_primary_button = QtWidgets.QToolButton(shortcutListDialog)
|
||||
self.clear_primary_button.setObjectName('clear_primary_button')
|
||||
|
@ -34,7 +34,7 @@ from openlp.core.common.settings import Settings
|
||||
from openlp.core.ui.shortcutlistdialog import Ui_ShortcutListDialog
|
||||
|
||||
|
||||
REMOVE_AMPERSAND = re.compile(r'&{1}')
|
||||
REMOVE_AMPERSAND = re.compile(r'&')
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
@ -30,6 +30,7 @@ from xml.etree.ElementTree import XML, ElementTree
|
||||
|
||||
from PyQt5 import QtCore, QtWidgets
|
||||
|
||||
from openlp.core.state import State
|
||||
from openlp.core.common import delete_file
|
||||
from openlp.core.common.applocation import AppLocation
|
||||
from openlp.core.common.i18n import UiStrings, get_locale_key, translate
|
||||
@ -293,7 +294,7 @@ class ThemeManager(QtWidgets.QWidget, RegistryBase, Ui_ThemeManager, LogMixin, R
|
||||
old_theme_data = self.get_theme_data(old_theme_name)
|
||||
self.clone_theme_data(old_theme_data, new_theme_name)
|
||||
self.delete_theme(old_theme_name)
|
||||
for plugin in self.plugin_manager.plugins:
|
||||
for plugin in State().list_plugins():
|
||||
if plugin.uses_theme(old_theme_name):
|
||||
plugin.rename_theme(old_theme_name, new_theme_name)
|
||||
self.renderer.set_theme(self.get_theme_data(new_theme_name))
|
||||
@ -612,7 +613,7 @@ class ThemeManager(QtWidgets.QWidget, RegistryBase, Ui_ThemeManager, LogMixin, R
|
||||
self.log_exception('Importing theme from zip failed {name}'.format(name=file_path))
|
||||
critical_error_message_box(
|
||||
translate('OpenLP.ThemeManager', 'Import Error'),
|
||||
translate('OpenLP.ThemeManager', 'There was a problem imoorting {file_name}.\n\nIt is corrupt,'
|
||||
translate('OpenLP.ThemeManager', 'There was a problem importing {file_name}.\n\nIt is corrupt, '
|
||||
'inaccessible or not a valid theme.').format(file_name=file_path))
|
||||
finally:
|
||||
if not abort_import:
|
||||
@ -771,7 +772,7 @@ class ThemeManager(QtWidgets.QWidget, RegistryBase, Ui_ThemeManager, LogMixin, R
|
||||
# check for use in the system else where.
|
||||
if test_plugin:
|
||||
plugin_usage = ""
|
||||
for plugin in self.plugin_manager.plugins:
|
||||
for plugin in State().list_plugins():
|
||||
used_count = plugin.uses_theme(theme)
|
||||
if used_count:
|
||||
plugin_usage = "{plug}{text}".format(plug=plugin_usage,
|
||||
|
@ -80,7 +80,7 @@ class ListPreviewWidget(QtWidgets.QTableWidget, RegistryProperties):
|
||||
An empty ``ServiceItem`` is used by default. replace_service_manager_item() needs to be called to make this
|
||||
widget display something.
|
||||
"""
|
||||
super(QtWidgets.QTableWidget, self).__init__(parent)
|
||||
super().__init__(parent)
|
||||
self._setup(screen_ratio)
|
||||
|
||||
def _setup(self, screen_ratio):
|
||||
@ -124,7 +124,7 @@ class ListPreviewWidget(QtWidgets.QTableWidget, RegistryProperties):
|
||||
max_img_row_height = Settings().value('advanced/slide max height')
|
||||
# Adjust for row height cap if in use.
|
||||
if isinstance(max_img_row_height, int):
|
||||
if max_img_row_height > 0 and height > max_img_row_height:
|
||||
if 0 < max_img_row_height < height:
|
||||
height = max_img_row_height
|
||||
elif max_img_row_height < 0:
|
||||
# If auto setting, show that number of slides, or if the resulting slides too small, 100px.
|
||||
@ -210,16 +210,20 @@ class ListPreviewWidget(QtWidgets.QTableWidget, RegistryProperties):
|
||||
pixmap = QtGui.QPixmap(str(slide['image']))
|
||||
else:
|
||||
pixmap = QtGui.QPixmap(str(slide['path']))
|
||||
if pixmap.height() > 0:
|
||||
pixmap_ratio = pixmap.width() / pixmap.height()
|
||||
else:
|
||||
pixmap_ratio = 1
|
||||
label.setPixmap(pixmap)
|
||||
container = QtWidgets.QWidget()
|
||||
layout = AspectRatioLayout(container, self.screen_ratio)
|
||||
layout = AspectRatioLayout(container, pixmap_ratio)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
layout.addWidget(label)
|
||||
container.setLayout(layout)
|
||||
slide_height = width // self.screen_ratio
|
||||
max_img_row_height = Settings().value('advanced/slide max height')
|
||||
if isinstance(max_img_row_height, int):
|
||||
if max_img_row_height > 0 and slide_height > max_img_row_height:
|
||||
if 0 < max_img_row_height < slide_height:
|
||||
slide_height = max_img_row_height
|
||||
elif max_img_row_height < 0:
|
||||
# If auto setting, show that number of slides, or if the resulting slides too small, 100px.
|
||||
|
@ -194,11 +194,11 @@ class ScreenButton(QtWidgets.QPushButton):
|
||||
|
||||
|
||||
class ScreenSelectionWidget(QtWidgets.QWidget):
|
||||
def __init__(self, parent=None, screens=[]):
|
||||
def __init__(self, parent=None, screens=None):
|
||||
super().__init__(parent)
|
||||
self.current_screen = None
|
||||
self.identify_labels = []
|
||||
self.screens = screens
|
||||
self.screens = screens or []
|
||||
self.timer = QtCore.QTimer()
|
||||
self.timer.setSingleShot(True)
|
||||
self.timer.setInterval(3000)
|
||||
|
@ -26,6 +26,7 @@ plugin.
|
||||
import logging
|
||||
import re
|
||||
|
||||
from openlp.core.common import Singleton
|
||||
from openlp.core.common.i18n import translate
|
||||
from openlp.core.common.settings import Settings
|
||||
|
||||
@ -64,20 +65,10 @@ class LanguageSelection(object):
|
||||
English = 2
|
||||
|
||||
|
||||
class BibleStrings(object):
|
||||
class BibleStrings(metaclass=Singleton):
|
||||
"""
|
||||
Provide standard strings for objects to use.
|
||||
"""
|
||||
__instance__ = None
|
||||
|
||||
def __new__(cls):
|
||||
"""
|
||||
Override the default object creation method to return a single instance.
|
||||
"""
|
||||
if not cls.__instance__:
|
||||
cls.__instance__ = object.__new__(cls)
|
||||
return cls.__instance__
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
These strings should need a good reason to be retranslated elsewhere.
|
||||
@ -336,11 +327,13 @@ def parse_reference(reference, bible, language_selection, book_ref_id=False):
|
||||
log.debug('Matched reference {text}'.format(text=reference))
|
||||
book = match.group('book')
|
||||
if not book_ref_id:
|
||||
book_ref_id = bible.get_book_ref_id_by_localised_name(book, language_selection)
|
||||
book_ref_ids = bible.get_book_ref_id_by_localised_name(book, language_selection)
|
||||
elif not bible.get_book_by_book_ref_id(book_ref_id):
|
||||
return []
|
||||
else:
|
||||
book_ref_ids = [book_ref_id]
|
||||
# We have not found the book so do not continue
|
||||
if not book_ref_id:
|
||||
if not book_ref_ids:
|
||||
return []
|
||||
ranges = match.group('ranges')
|
||||
range_list = get_reference_match('range_separator').split(ranges)
|
||||
@ -381,22 +374,23 @@ def parse_reference(reference, bible, language_selection, book_ref_id=False):
|
||||
to_chapter = to_verse
|
||||
to_verse = None
|
||||
# Append references to the list
|
||||
if has_range:
|
||||
if not from_verse:
|
||||
from_verse = 1
|
||||
if not to_verse:
|
||||
to_verse = -1
|
||||
if to_chapter and to_chapter > from_chapter:
|
||||
ref_list.append((book_ref_id, from_chapter, from_verse, -1))
|
||||
for i in range(from_chapter + 1, to_chapter):
|
||||
ref_list.append((book_ref_id, i, 1, -1))
|
||||
ref_list.append((book_ref_id, to_chapter, 1, to_verse))
|
||||
elif to_verse >= from_verse or to_verse == -1:
|
||||
ref_list.append((book_ref_id, from_chapter, from_verse, to_verse))
|
||||
elif from_verse:
|
||||
ref_list.append((book_ref_id, from_chapter, from_verse, from_verse))
|
||||
else:
|
||||
ref_list.append((book_ref_id, from_chapter, 1, -1))
|
||||
for book_ref_id in book_ref_ids:
|
||||
if has_range:
|
||||
if not from_verse:
|
||||
from_verse = 1
|
||||
if not to_verse:
|
||||
to_verse = -1
|
||||
if to_chapter and to_chapter > from_chapter:
|
||||
ref_list.append((book_ref_id, from_chapter, from_verse, -1))
|
||||
for i in range(from_chapter + 1, to_chapter):
|
||||
ref_list.append((book_ref_id, i, 1, -1))
|
||||
ref_list.append((book_ref_id, to_chapter, 1, to_verse))
|
||||
elif to_verse >= from_verse or to_verse == -1:
|
||||
ref_list.append((book_ref_id, from_chapter, from_verse, to_verse))
|
||||
elif from_verse:
|
||||
ref_list.append((book_ref_id, from_chapter, from_verse, from_verse))
|
||||
else:
|
||||
ref_list.append((book_ref_id, from_chapter, 1, -1))
|
||||
return ref_list
|
||||
else:
|
||||
log.debug('Invalid reference: {text}'.format(text=reference))
|
||||
|
@ -281,13 +281,14 @@ class BibleDB(Manager):
|
||||
log.debug('BibleDB.get_book("{book}")'.format(book=book))
|
||||
return self.get_object_filtered(Book, Book.name.like(book + '%'))
|
||||
|
||||
def get_books(self):
|
||||
def get_books(self, book=None):
|
||||
"""
|
||||
A wrapper so both local and web bibles have a get_books() method that
|
||||
manager can call. Used in the media manager advanced search tab.
|
||||
"""
|
||||
log.debug('BibleDB.get_books()')
|
||||
return self.get_all_objects(Book, order_by_ref=Book.id)
|
||||
log.debug('BibleDB.get_books("{book}")'.format(book=book))
|
||||
filter = Book.name.like(book + '%') if book else None
|
||||
return self.get_all_objects(Book, filter_clause=filter, order_by_ref=Book.id)
|
||||
|
||||
def get_book_by_book_ref_id(self, ref_id):
|
||||
"""
|
||||
@ -300,39 +301,35 @@ class BibleDB(Manager):
|
||||
|
||||
def get_book_ref_id_by_localised_name(self, book, language_selection):
|
||||
"""
|
||||
Return the id of a named book.
|
||||
Return the ids of a matching named book.
|
||||
|
||||
:param book: The name of the book, according to the selected language.
|
||||
:param language_selection: The language selection the user has chosen in the settings section of the Bible.
|
||||
:rtype: list[int]
|
||||
"""
|
||||
log.debug('get_book_ref_id_by_localised_name("{book}", "{lang}")'.format(book=book, lang=language_selection))
|
||||
from openlp.plugins.bibles.lib import LanguageSelection, BibleStrings
|
||||
book_names = BibleStrings().BookNames
|
||||
# escape reserved characters
|
||||
book_escaped = book
|
||||
for character in RESERVED_CHARACTERS:
|
||||
book_escaped = book_escaped.replace(character, '\\' + character)
|
||||
book_escaped = book.replace(character, '\\' + character)
|
||||
regex_book = re.compile('\\s*{book}\\s*'.format(book='\\s*'.join(book_escaped.split())), re.IGNORECASE)
|
||||
if language_selection == LanguageSelection.Bible:
|
||||
db_book = self.get_book(book)
|
||||
if db_book:
|
||||
return db_book.book_reference_id
|
||||
elif language_selection == LanguageSelection.Application:
|
||||
books = [key for key in list(book_names.keys()) if regex_book.match(str(book_names[key]))]
|
||||
books = [_f for _f in map(BiblesResourcesDB.get_book, books) if _f]
|
||||
for value in books:
|
||||
if self.get_book_by_book_ref_id(value['id']):
|
||||
return value['id']
|
||||
elif language_selection == LanguageSelection.English:
|
||||
books = BiblesResourcesDB.get_books_like(book)
|
||||
if books:
|
||||
book_list = [value for value in books if regex_book.match(value['name'])]
|
||||
if not book_list:
|
||||
book_list = books
|
||||
for value in book_list:
|
||||
if self.get_book_by_book_ref_id(value['id']):
|
||||
return value['id']
|
||||
return False
|
||||
db_books = self.get_books(book)
|
||||
return [db_book.book_reference_id for db_book in db_books]
|
||||
else:
|
||||
book_list = []
|
||||
if language_selection == LanguageSelection.Application:
|
||||
books = [key for key in list(book_names.keys()) if regex_book.match(book_names[key])]
|
||||
book_list = [_f for _f in map(BiblesResourcesDB.get_book, books) if _f]
|
||||
elif language_selection == LanguageSelection.English:
|
||||
books = BiblesResourcesDB.get_books_like(book)
|
||||
if books:
|
||||
book_list = [value for value in books if regex_book.match(value['name'])]
|
||||
if not book_list:
|
||||
book_list = books
|
||||
return [value['id'] for value in book_list if self.get_book_by_book_ref_id(value['id'])]
|
||||
return []
|
||||
|
||||
def get_verses(self, reference_list, show_error=True):
|
||||
"""
|
||||
|
@ -240,8 +240,10 @@ class BibleManager(LogMixin, RegistryProperties):
|
||||
book=book,
|
||||
chapter=chapter))
|
||||
language_selection = self.get_language_selection(bible)
|
||||
book_ref_id = self.db_cache[bible].get_book_ref_id_by_localised_name(book, language_selection)
|
||||
return self.db_cache[bible].get_verse_count(book_ref_id, chapter)
|
||||
book_ref_ids = self.db_cache[bible].get_book_ref_id_by_localised_name(book, language_selection)
|
||||
if book_ref_ids:
|
||||
return self.db_cache[bible].get_verse_count(book_ref_ids[0], chapter)
|
||||
return 0
|
||||
|
||||
def get_verse_count_by_book_ref_id(self, bible, book_ref_id, chapter):
|
||||
"""
|
||||
@ -401,4 +403,4 @@ class BibleManager(LogMixin, RegistryProperties):
|
||||
self.db_cache[bible].finalise()
|
||||
|
||||
|
||||
__all__ = ['BibleFormat']
|
||||
__all__ = ['BibleFormat', 'BibleManager']
|
||||
|
@ -109,7 +109,7 @@ class BibleMediaItem(MediaManagerItem):
|
||||
:param kwargs: Keyword arguments to pass to the super method. (dict)
|
||||
"""
|
||||
self.clear_icon = UiIcons().square
|
||||
self.save_results_icon = UiIcons.save
|
||||
self.save_results_icon = UiIcons().save
|
||||
self.sort_icon = UiIcons().sort
|
||||
self.bible = None
|
||||
self.second_bible = None
|
||||
|
@ -97,6 +97,7 @@ class Ui_CustomEditDialog(object):
|
||||
self.preview_button = QtWidgets.QPushButton()
|
||||
self.button_box = create_button_box(custom_edit_dialog, 'button_box', ['cancel', 'save'],
|
||||
[self.preview_button])
|
||||
self.save_button = self.button_box.button(QtWidgets.QDialogButtonBox.Save)
|
||||
self.dialog_layout.addWidget(self.button_box)
|
||||
self.retranslate_ui(custom_edit_dialog)
|
||||
|
||||
@ -112,3 +113,4 @@ class Ui_CustomEditDialog(object):
|
||||
self.theme_label.setText(translate('CustomPlugin.EditCustomForm', 'The&me:'))
|
||||
self.credit_label.setText(translate('CustomPlugin.EditCustomForm', '&Credits:'))
|
||||
self.preview_button.setText(UiStrings().SaveAndPreview)
|
||||
self.save_button.setText(UiStrings().SaveAndClose)
|
||||
|
@ -349,7 +349,7 @@ class CustomMediaItem(MediaManagerItem):
|
||||
custom.credits = ''
|
||||
custom_xml = CustomXMLBuilder()
|
||||
for (idx, slide) in enumerate(item.slides):
|
||||
custom_xml.add_verse_to_lyrics('custom', str(idx + 1), slide['raw_slide'])
|
||||
custom_xml.add_verse_to_lyrics('custom', str(idx + 1), slide['text'])
|
||||
custom.text = str(custom_xml.extract_xml(), 'utf-8')
|
||||
self.plugin.db_manager.save_object(custom)
|
||||
self.on_search_text_button_clicked()
|
||||
|
@ -55,7 +55,7 @@ if is_win():
|
||||
class SlideShowListenerImport(XSlideShowListenerObj.__class__):
|
||||
pass
|
||||
except (AttributeError, pywintypes.com_error):
|
||||
class SlideShowListenerImport():
|
||||
class SlideShowListenerImport(object):
|
||||
pass
|
||||
|
||||
# Declare an empty exception to match the exception imported from UNO
|
||||
@ -76,7 +76,7 @@ else:
|
||||
except ImportError:
|
||||
uno_available = False
|
||||
|
||||
class SlideShowListenerImport():
|
||||
class SlideShowListenerImport(object):
|
||||
pass
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@ -233,9 +233,7 @@ class ImpressController(PresentationController):
|
||||
self.conf_provider = self.manager.createInstanceWithContext(
|
||||
'com.sun.star.configuration.ConfigurationProvider', uno.getComponentContext())
|
||||
# Setup lookup properties to get Impress settings
|
||||
properties = []
|
||||
properties.append(self.create_property('nodepath', 'org.openoffice.Office.Impress'))
|
||||
properties = tuple(properties)
|
||||
properties = tuple(self.create_property('nodepath', 'org.openoffice.Office.Impress'))
|
||||
try:
|
||||
# Get an updateable configuration view
|
||||
impress_conf_props = self.conf_provider.createInstanceWithArguments(
|
||||
@ -311,9 +309,7 @@ class ImpressDocument(PresentationDocument):
|
||||
if desktop is None:
|
||||
return False
|
||||
self.desktop = desktop
|
||||
properties = []
|
||||
properties.append(self.controller.create_property('Hidden', True))
|
||||
properties = tuple(properties)
|
||||
properties = tuple(self.controller.create_property('Hidden', True))
|
||||
try:
|
||||
self.document = desktop.loadComponentFromURL(url, '_blank', 0, properties)
|
||||
except Exception:
|
||||
@ -338,9 +334,7 @@ class ImpressDocument(PresentationDocument):
|
||||
return
|
||||
temp_folder_path = self.get_temp_folder()
|
||||
thumb_dir_url = temp_folder_path.as_uri()
|
||||
properties = []
|
||||
properties.append(self.controller.create_property('FilterName', 'impress_png_Export'))
|
||||
properties = tuple(properties)
|
||||
properties = tuple(self.controller.create_property('FilterName', 'impress_png_Export'))
|
||||
doc = self.document
|
||||
pages = doc.getDrawPages()
|
||||
if not pages:
|
||||
|
@ -27,6 +27,7 @@ from Pyro4 import Proxy
|
||||
|
||||
from openlp.core.common import delete_file, is_macosx
|
||||
from openlp.core.common.applocation import AppLocation
|
||||
from openlp.core.common.mixins import LogMixin
|
||||
from openlp.core.common.path import Path
|
||||
from openlp.core.common.registry import Registry
|
||||
from openlp.core.display.screens import ScreenList
|
||||
@ -47,7 +48,7 @@ log = logging.getLogger(__name__)
|
||||
register_classes()
|
||||
|
||||
|
||||
class MacLOController(PresentationController):
|
||||
class MacLOController(PresentationController, LogMixin):
|
||||
"""
|
||||
Class to control interactions with MacLO presentations on Mac OS X via Pyro4. It starts the Pyro4 nameserver,
|
||||
starts the LibreOfficeServer, and then controls MacLO via Pyro4.
|
||||
|
@ -246,6 +246,9 @@ class PresentationMediaItem(MediaManagerItem):
|
||||
:rtype: None
|
||||
"""
|
||||
for cidx in self.controllers:
|
||||
if not self.controllers[cidx].enabled():
|
||||
# skip presentation controllers that are not enabled
|
||||
continue
|
||||
file_ext = file_path.suffix[1:]
|
||||
if file_ext in self.controllers[cidx].supports or file_ext in self.controllers[cidx].also_supports:
|
||||
doc = self.controllers[cidx].add_document(file_path)
|
||||
|
@ -29,6 +29,7 @@ from shutil import copyfile
|
||||
|
||||
from PyQt5 import QtCore, QtWidgets
|
||||
|
||||
from openlp.core.state import State
|
||||
from openlp.core.common.applocation import AppLocation
|
||||
from openlp.core.common.i18n import UiStrings, get_natural_key, translate
|
||||
from openlp.core.common.mixins import RegistryProperties
|
||||
@ -416,7 +417,7 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
|
||||
Load the media files into a combobox.
|
||||
"""
|
||||
self.from_media_button.setVisible(False)
|
||||
for plugin in self.plugin_manager.plugins:
|
||||
for plugin in State().list_plugins():
|
||||
if plugin.name == 'media' and plugin.status == PluginStatus.Active:
|
||||
self.from_media_button.setVisible(True)
|
||||
self.media_form.populate_files(plugin.media_item.get_list(MediaType.Audio))
|
||||
|
@ -615,14 +615,16 @@ def transpose_chord(chord, transpose_value, notation):
|
||||
:return: The transposed chord.
|
||||
"""
|
||||
# See https://en.wikipedia.org/wiki/Musical_note#12-tone_chromatic_scale
|
||||
notes_sharp_notation = {}
|
||||
notes_flat_notation = {}
|
||||
notes_sharp_notation['german'] = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'H']
|
||||
notes_flat_notation['german'] = ['C', 'Db', 'D', 'Eb', 'Fb', 'F', 'Gb', 'G', 'Ab', 'A', 'B', 'H']
|
||||
notes_sharp_notation['english'] = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B']
|
||||
notes_flat_notation['english'] = ['C', 'Db', 'D', 'Eb', 'Fb', 'F', 'Gb', 'G', 'Ab', 'A', 'Bb', 'B']
|
||||
notes_sharp_notation['neo-latin'] = ['Do', 'Do#', 'Re', 'Re#', 'Mi', 'Fa', 'Fa#', 'Sol', 'Sol#', 'La', 'La#', 'Si']
|
||||
notes_flat_notation['neo-latin'] = ['Do', 'Reb', 'Re', 'Mib', 'Fab', 'Fa', 'Solb', 'Sol', 'Lab', 'La', 'Sib', 'Si']
|
||||
notes_sharp_notation = {
|
||||
'german': ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'H'],
|
||||
'english': ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'],
|
||||
'neo-latin': ['Do', 'Do#', 'Re', 'Re#', 'Mi', 'Fa', 'Fa#', 'Sol', 'Sol#', 'La', 'La#', 'Si']
|
||||
}
|
||||
notes_flat_notation = {
|
||||
'german': ['C', 'Db', 'D', 'Eb', 'Fb', 'F', 'Gb', 'G', 'Ab', 'A', 'B', 'H'],
|
||||
'english': ['C', 'Db', 'D', 'Eb', 'Fb', 'F', 'Gb', 'G', 'Ab', 'A', 'Bb', 'B'],
|
||||
'neo-latin': ['Do', 'Reb', 'Re', 'Mib', 'Fab', 'Fa', 'Solb', 'Sol', 'Lab', 'La', 'Sib', 'Si']
|
||||
}
|
||||
chord_split = chord.replace('♭', 'b').split('/')
|
||||
transposed_chord = ''
|
||||
last_chord = ''
|
||||
|
@ -374,7 +374,9 @@ def init_schema(url):
|
||||
mapper(SongBookEntry, songs_songbooks_table, properties={
|
||||
'songbook': relation(Book)
|
||||
})
|
||||
mapper(Book, song_books_table)
|
||||
mapper(Book, song_books_table, properties={
|
||||
'songs': relation(Song, secondary=songs_songbooks_table)
|
||||
})
|
||||
mapper(MediaFile, media_files_table)
|
||||
mapper(Song, songs_table, properties={
|
||||
# Use the authors_songs relation when you need access to the 'author_type' attribute
|
||||
|
@ -146,7 +146,9 @@ class CCLIFileImport(SongImport):
|
||||
"""
|
||||
log.debug('USR file text: {text}'.format(text=text_list))
|
||||
song_author = ''
|
||||
song_fields = ''
|
||||
song_topics = ''
|
||||
song_words = ''
|
||||
for line in text_list:
|
||||
if line.startswith('[S '):
|
||||
ccli, line = line.split(']', 1)
|
||||
|
@ -28,6 +28,7 @@ import re
|
||||
|
||||
from openlp.core.common.settings import Settings
|
||||
from openlp.plugins.songs.lib.importers.songimport import SongImport
|
||||
from openlp.plugins.songs.lib.db import AuthorType
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@ -39,6 +40,7 @@ class ChordProImport(SongImport):
|
||||
|
||||
This importer is based on the information available on these webpages:
|
||||
|
||||
- https://www.chordpro.org
|
||||
- http://webchord.sourceforge.net/tech.html
|
||||
- http://www.vromans.org/johan/projects/Chordii/chordpro/
|
||||
- http://www.tenbyten.com/software/songsgen/help/HtmlHelp/files_reference.htm
|
||||
@ -73,6 +75,29 @@ class ChordProImport(SongImport):
|
||||
self.title = tag_value
|
||||
elif tag_name in ['subtitle', 'su', 'st']:
|
||||
self.alternate_title = tag_value
|
||||
elif tag_name == 'composer':
|
||||
self.parse_author(tag_value, AuthorType.Music)
|
||||
elif tag_name in ['lyricist', 'artist', 'author']: # author is not an official directive
|
||||
self.parse_author(tag_value, AuthorType.Words)
|
||||
elif tag_name == 'meta':
|
||||
meta_tag_name, meta_tag_value = tag_value.split(' ', 1)
|
||||
# Skip, if no value
|
||||
if not meta_tag_value:
|
||||
continue
|
||||
# The meta-tag can contain anything. We check for the ones above and a few more
|
||||
if meta_tag_name in ['title', 't']:
|
||||
self.title = meta_tag_value
|
||||
elif meta_tag_name in ['subtitle', 'su', 'st']:
|
||||
self.alternate_title = meta_tag_value
|
||||
elif meta_tag_name == 'composer':
|
||||
self.parse_author(meta_tag_value, AuthorType.Music)
|
||||
elif meta_tag_name in ['lyricist', 'artist', 'author']:
|
||||
self.parse_author(meta_tag_value, AuthorType.Words)
|
||||
elif meta_tag_name in ['topic', 'topics']:
|
||||
for topic in meta_tag_value.split(','):
|
||||
self.topics.append(topic.strip())
|
||||
elif 'ccli' in meta_tag_name:
|
||||
self.ccli_number = meta_tag_value
|
||||
elif tag_name in ['comment', 'c', 'comment_italic', 'ci', 'comment_box', 'cb']:
|
||||
# Detect if the comment is used as a chorus repeat marker
|
||||
if tag_value.lower().startswith('chorus'):
|
||||
@ -156,6 +181,13 @@ class ChordProImport(SongImport):
|
||||
'songs/disable chords import'):
|
||||
current_verse = re.sub(r'\[.*?\]', '', current_verse)
|
||||
self.add_verse(current_verse.rstrip(), current_verse_type)
|
||||
# if no title was in directives, get it from the first line
|
||||
if not self.title:
|
||||
(verse_def, verse_text, lang) = self.verses[0]
|
||||
# strip any chords from the title
|
||||
self.title = re.sub(r'\[.*?\]', '', verse_text.split('\n')[0])
|
||||
# strip the last char if it a punctuation
|
||||
self.title = re.sub(r'[^\w\s]$', '', self.title)
|
||||
if not self.finish():
|
||||
self.log_error(song_file.name)
|
||||
|
||||
|
@ -87,6 +87,7 @@ class DreamBeamImport(SongImport):
|
||||
if self.stop_import_flag:
|
||||
return
|
||||
self.set_defaults()
|
||||
author_copyright = ''
|
||||
parser = etree.XMLParser(remove_blank_text=True)
|
||||
try:
|
||||
with file_path.open('r') as xml_file:
|
||||
@ -142,7 +143,7 @@ class DreamBeamImport(SongImport):
|
||||
author_copyright = song_xml.Text2.Text.text
|
||||
if author_copyright:
|
||||
author_copyright = str(author_copyright)
|
||||
if author_copyright.find(str(SongStrings.CopyrightSymbol)) >= 0:
|
||||
if author_copyright.find(SongStrings.CopyrightSymbol) >= 0:
|
||||
self.add_copyright(author_copyright)
|
||||
else:
|
||||
self.parse_author(author_copyright)
|
||||
|
@ -137,9 +137,11 @@ class EasySlidesImport(SongImport):
|
||||
except UnicodeDecodeError:
|
||||
log.exception('Unicode decode error while decoding Contents')
|
||||
self._success = False
|
||||
return
|
||||
except AttributeError:
|
||||
log.exception('no Contents')
|
||||
self._success = False
|
||||
return
|
||||
lines = lyrics.split('\n')
|
||||
# we go over all lines first, to determine information,
|
||||
# which tells us how to parse verses later
|
||||
|
@ -118,8 +118,8 @@ class EasyWorshipSongImport(SongImport):
|
||||
# 40/48/56 Entry count int32le 4 Number of items in the schedule
|
||||
# 44/52/60 Entry length int16le 2 Length of schedule entries: 0x0718 = 1816
|
||||
# Get file version
|
||||
type, = struct.unpack('<38s', self.ews_file.read(38))
|
||||
version = type.decode()[-3:]
|
||||
file_type, = struct.unpack('<38s', self.ews_file.read(38))
|
||||
version = file_type.decode()[-3:]
|
||||
# Set fileposition based on filetype/version
|
||||
file_pos = 0
|
||||
if version == ' 5':
|
||||
@ -268,13 +268,13 @@ class EasyWorshipSongImport(SongImport):
|
||||
self.db_set_record_struct(field_descriptions)
|
||||
# Pick out the field description indexes we will need
|
||||
try:
|
||||
success = True
|
||||
fi_title = self.db_find_field(b'Title')
|
||||
fi_author = self.db_find_field(b'Author')
|
||||
fi_copy = self.db_find_field(b'Copyright')
|
||||
fi_admin = self.db_find_field(b'Administrator')
|
||||
fi_words = self.db_find_field(b'Words')
|
||||
fi_ccli = self.db_find_field(b'Song Number')
|
||||
success = True
|
||||
except IndexError:
|
||||
# This is the wrong table
|
||||
success = False
|
||||
|
@ -302,7 +302,7 @@ class FoilPresenter(object):
|
||||
break
|
||||
author_temp = []
|
||||
for author in strings:
|
||||
temp = re.split(r',(?=\D{2})|(?<=\D),|\/(?=\D{3,})|(?<=\D);', author)
|
||||
temp = re.split(r',(?=\D{2})|(?<=\D),|/(?=\D{3,})|(?<=\D);', author)
|
||||
for tempx in temp:
|
||||
author_temp.append(tempx)
|
||||
for author in author_temp:
|
||||
|
@ -145,9 +145,7 @@ class OpenOfficeImport(SongImport):
|
||||
"""
|
||||
self.file_path = file_path
|
||||
url = file_path.as_uri()
|
||||
properties = []
|
||||
properties.append(self.create_property('Hidden', True))
|
||||
properties = tuple(properties)
|
||||
properties = tuple(self.create_property('Hidden', True))
|
||||
try:
|
||||
self.document = self.desktop.loadComponentFromURL(url, '_blank', 0, properties)
|
||||
if not self.document.supportsService("com.sun.star.presentation.PresentationDocument") and not \
|
||||
|
@ -136,8 +136,8 @@ class OPSProImport(SongImport):
|
||||
verse_text = re.sub(r'^\d+\r\n', '', verse_text)
|
||||
verse_def = 'v' + verse_number.group(1)
|
||||
# Detect verse tags
|
||||
elif re.match(r'^.+?\:\r\n', verse_text):
|
||||
tag_match = re.match(r'^(.+?)\:\r\n(.*)', verse_text, flags=re.DOTALL)
|
||||
elif re.match(r'^.+?:\r\n', verse_text):
|
||||
tag_match = re.match(r'^(.+?):\r\n(.*)', verse_text, flags=re.DOTALL)
|
||||
tag = tag_match.group(1).lower()
|
||||
tag = tag.split(' ')[0]
|
||||
verse_text = tag_match.group(2)
|
||||
|
@ -80,7 +80,7 @@ class ProPresenterImport(SongImport):
|
||||
self.parse_author(author)
|
||||
|
||||
# ProPresenter 4
|
||||
if(self.version >= 400 and self.version < 500):
|
||||
if 400 <= self.version < 500:
|
||||
self.copyright = root.get('CCLICopyrightInfo')
|
||||
self.ccli_number = root.get('CCLILicenseNumber')
|
||||
count = 0
|
||||
@ -95,7 +95,7 @@ class ProPresenterImport(SongImport):
|
||||
self.add_verse(words, "v{count}".format(count=count))
|
||||
|
||||
# ProPresenter 5
|
||||
elif(self.version >= 500 and self.version < 600):
|
||||
elif 500 <= self.version < 600:
|
||||
self.copyright = root.get('CCLICopyrightInfo')
|
||||
self.ccli_number = root.get('CCLILicenseNumber')
|
||||
count = 0
|
||||
@ -111,7 +111,7 @@ class ProPresenterImport(SongImport):
|
||||
self.add_verse(words, "v{count:d}".format(count=count))
|
||||
|
||||
# ProPresenter 6
|
||||
elif(self.version >= 600 and self.version < 700):
|
||||
elif 600 <= self.version < 700:
|
||||
self.copyright = root.get('CCLICopyrightYear')
|
||||
self.ccli_number = root.get('CCLISongNumber')
|
||||
count = 0
|
||||
@ -128,7 +128,7 @@ class ProPresenterImport(SongImport):
|
||||
b64Data = contents.text
|
||||
data = base64.standard_b64decode(b64Data)
|
||||
words = None
|
||||
if(contents.get('rvXMLIvarName') == "RTFData"):
|
||||
if contents.get('rvXMLIvarName') == "RTFData":
|
||||
words, encoding = strip_rtf(data.decode())
|
||||
break
|
||||
if words:
|
||||
|
@ -128,7 +128,7 @@ class SongBeamerImport(SongImport):
|
||||
# The encoding should only be ANSI (cp1252), UTF-8, Unicode, Big-Endian-Unicode.
|
||||
# So if it doesn't start with 'u' we default to cp1252. See:
|
||||
# https://forum.songbeamer.com/viewtopic.php?p=419&sid=ca4814924e37c11e4438b7272a98b6f2
|
||||
if not self.input_file_encoding.lower().startswith('u'):
|
||||
if self.input_file_encoding and not self.input_file_encoding.lower().startswith('u'):
|
||||
self.input_file_encoding = 'cp1252'
|
||||
with file_path.open(encoding=self.input_file_encoding) as song_file:
|
||||
song_data = song_file.readlines()
|
||||
|
@ -75,7 +75,7 @@ class VideoPsalmImport(SongImport):
|
||||
c = next(file_content_it)
|
||||
processed_content += '"' + c
|
||||
# Remove control characters
|
||||
elif (c < chr(32)):
|
||||
elif c < chr(32):
|
||||
processed_content += ' '
|
||||
# Handle escaped characters
|
||||
elif c == '\\':
|
||||
|
@ -221,7 +221,7 @@ class OpenLyrics(object):
|
||||
"""
|
||||
IMPLEMENTED_VERSION = '0.8'
|
||||
START_TAGS_REGEX = re.compile(r'\{(\w+)\}')
|
||||
END_TAGS_REGEX = re.compile(r'\{\/(\w+)\}')
|
||||
END_TAGS_REGEX = re.compile(r'\{/(\w+)\}')
|
||||
VERSE_TAG_SPLITTER = re.compile('([a-zA-Z]+)([0-9]*)([a-zA-Z]?)')
|
||||
|
||||
def __init__(self, manager):
|
||||
|
@ -34,7 +34,7 @@ class SongStrings(object):
|
||||
Author = translate('OpenLP.Ui', 'Author', 'Singular')
|
||||
Authors = translate('OpenLP.Ui', 'Authors', 'Plural')
|
||||
AuthorUnknown = translate('OpenLP.Ui', 'Author Unknown') # Used to populate the database.
|
||||
CopyrightSymbol = translate('OpenLP.Ui', '\xa9', 'Copyright symbol.')
|
||||
CopyrightSymbol = '\xa9'
|
||||
SongBook = translate('OpenLP.Ui', 'Songbook', 'Singular')
|
||||
SongBooks = translate('OpenLP.Ui', 'Songbooks', 'Plural')
|
||||
SongIncomplete = translate('OpenLP.Ui', 'Title and/or verses not found')
|
||||
|
11
package.json
11
package.json
@ -9,17 +9,16 @@
|
||||
"dependencies": {
|
||||
"jasmine-core": "^2.6.4",
|
||||
"karma": "^3.1.4",
|
||||
"karma-chrome-launcher": "^3.1.0",
|
||||
"karma-coverage": "^1.1.2",
|
||||
"karma-firefox-launcher": "^1.2.0",
|
||||
"karma-jasmine": "^1.1.0",
|
||||
"karma-phantomjs-launcher": "^1.0.4",
|
||||
"phantomjs-prebuilt": "^2.1.16"
|
||||
"karma-junit-reporter": "^1.2.0",
|
||||
"karma-log-reporter": "0.0.4"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "karma start --single-run"
|
||||
},
|
||||
"author": "OpenLP Developers",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"devDependencies": {
|
||||
"karma-log-reporter": "0.0.4"
|
||||
}
|
||||
"license": "GPL-3.0-or-later"
|
||||
}
|
||||
|
10977
resources/i18n/af.ts
10977
resources/i18n/af.ts
File diff suppressed because it is too large
Load Diff
10975
resources/i18n/bg.ts
10975
resources/i18n/bg.ts
File diff suppressed because it is too large
Load Diff
11003
resources/i18n/cs.ts
11003
resources/i18n/cs.ts
File diff suppressed because it is too large
Load Diff
10996
resources/i18n/da.ts
10996
resources/i18n/da.ts
File diff suppressed because it is too large
Load Diff
10995
resources/i18n/de.ts
10995
resources/i18n/de.ts
File diff suppressed because it is too large
Load Diff
10973
resources/i18n/el.ts
10973
resources/i18n/el.ts
File diff suppressed because it is too large
Load Diff
11075
resources/i18n/en.ts
11075
resources/i18n/en.ts
File diff suppressed because it is too large
Load Diff
11073
resources/i18n/en_GB.ts
11073
resources/i18n/en_GB.ts
File diff suppressed because it is too large
Load Diff
10995
resources/i18n/en_ZA.ts
10995
resources/i18n/en_ZA.ts
File diff suppressed because it is too large
Load Diff
11055
resources/i18n/es.ts
11055
resources/i18n/es.ts
File diff suppressed because it is too large
Load Diff
10995
resources/i18n/et.ts
10995
resources/i18n/et.ts
File diff suppressed because it is too large
Load Diff
11251
resources/i18n/fi.ts
11251
resources/i18n/fi.ts
File diff suppressed because it is too large
Load Diff
10990
resources/i18n/fr.ts
10990
resources/i18n/fr.ts
File diff suppressed because it is too large
Load Diff
11050
resources/i18n/hu.ts
11050
resources/i18n/hu.ts
File diff suppressed because it is too large
Load Diff
11015
resources/i18n/id.ts
11015
resources/i18n/id.ts
File diff suppressed because it is too large
Load Diff
10975
resources/i18n/ja.ts
10975
resources/i18n/ja.ts
File diff suppressed because it is too large
Load Diff
10975
resources/i18n/ko.ts
10975
resources/i18n/ko.ts
File diff suppressed because it is too large
Load Diff
11065
resources/i18n/lt.ts
11065
resources/i18n/lt.ts
File diff suppressed because it is too large
Load Diff
11053
resources/i18n/nb.ts
11053
resources/i18n/nb.ts
File diff suppressed because it is too large
Load Diff
10993
resources/i18n/nl.ts
10993
resources/i18n/nl.ts
File diff suppressed because it is too large
Load Diff
10995
resources/i18n/pl.ts
10995
resources/i18n/pl.ts
File diff suppressed because it is too large
Load Diff
11008
resources/i18n/pt_BR.ts
11008
resources/i18n/pt_BR.ts
File diff suppressed because it is too large
Load Diff
10995
resources/i18n/ru.ts
10995
resources/i18n/ru.ts
File diff suppressed because it is too large
Load Diff
10994
resources/i18n/sk.ts
10994
resources/i18n/sk.ts
File diff suppressed because it is too large
Load Diff
10985
resources/i18n/sv.ts
10985
resources/i18n/sv.ts
File diff suppressed because it is too large
Load Diff
10974
resources/i18n/ta_LK.ts
10974
resources/i18n/ta_LK.ts
File diff suppressed because it is too large
Load Diff
10974
resources/i18n/th_TH.ts
10974
resources/i18n/th_TH.ts
File diff suppressed because it is too large
Load Diff
10971
resources/i18n/zh_CN.ts
10971
resources/i18n/zh_CN.ts
File diff suppressed because it is too large
Load Diff
10976
resources/i18n/zh_TW.ts
10976
resources/i18n/zh_TW.ts
File diff suppressed because it is too large
Load Diff
@ -21,51 +21,9 @@
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||
##########################################################################
|
||||
"""
|
||||
The entrypoint for OpenLP
|
||||
A compatibility entrypoint for OpenLP
|
||||
"""
|
||||
import faulthandler
|
||||
import logging
|
||||
import multiprocessing
|
||||
import sys
|
||||
|
||||
# from OpenGL import GL
|
||||
|
||||
from openlp.core.app import main
|
||||
from openlp.core.common import is_macosx, is_win
|
||||
from openlp.core.common.applocation import AppLocation
|
||||
from openlp.core.common.path import create_paths
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def set_up_fault_handling():
|
||||
"""
|
||||
Set up the Python fault handler
|
||||
"""
|
||||
# Create the cache directory if it doesn't exist, and enable the fault handler to log to an error log file
|
||||
try:
|
||||
create_paths(AppLocation.get_directory(AppLocation.CacheDir))
|
||||
faulthandler.enable((AppLocation.get_directory(AppLocation.CacheDir) / 'error.log').open('wb'))
|
||||
except OSError:
|
||||
log.exception('An exception occurred when enabling the fault handler')
|
||||
|
||||
|
||||
def start():
|
||||
"""
|
||||
Instantiate and run the application.
|
||||
"""
|
||||
set_up_fault_handling()
|
||||
# Add support for using multiprocessing from frozen Windows executable (built using PyInstaller),
|
||||
# see https://docs.python.org/3/library/multiprocessing.html#multiprocessing.freeze_support
|
||||
if is_win():
|
||||
multiprocessing.freeze_support()
|
||||
# Mac OS X passes arguments like '-psn_XXXX' to the application. This argument is actually a process serial number.
|
||||
# However, this causes a conflict with other OpenLP arguments. Since we do not use this argument we can delete it
|
||||
# to avoid any potential conflicts.
|
||||
if is_macosx():
|
||||
sys.argv = [x for x in sys.argv if not x.startswith('-psn')]
|
||||
main()
|
||||
|
||||
from openlp import __main__
|
||||
|
||||
if __name__ == '__main__':
|
||||
start()
|
||||
__main__.start()
|
||||
|
9
scripts/.tx/config
Normal file
9
scripts/.tx/config
Normal file
@ -0,0 +1,9 @@
|
||||
[main]
|
||||
host = https://www.transifex.com
|
||||
|
||||
[openlp.openlp-30x]
|
||||
file_filter = ../resources/i18n/<lang>.ts
|
||||
minimum_perc = 0
|
||||
source_file = ../resources/i18n/en.ts
|
||||
source_lang = en
|
||||
type = qt
|
@ -18,7 +18,7 @@ environment:
|
||||
|
||||
install:
|
||||
# Install dependencies from pypi
|
||||
- "%PYTHON%\\python.exe -m pip install sqlalchemy alembic appdirs chardet beautifulsoup4 lxml Mako mysql-connector-python pytest mock pyodbc psycopg2 pypiwin32 websockets asyncio waitress six webob requests QtAwesome PyQt5 PyQtWebEngine pymediainfo PyMuPDF QDarkStyle python-vlc Pyro4"
|
||||
- "%PYTHON%\\python.exe -m pip install sqlalchemy alembic appdirs chardet beautifulsoup4 lxml Mako mysql-connector-python pytest mock pyodbc psycopg2 pypiwin32 websockets asyncio waitress six webob requests QtAwesome PyQt5 PyQtWebEngine pymediainfo PyMuPDF QDarkStyle python-vlc Pyro4 zeroconf"
|
||||
|
||||
build: off
|
||||
|
||||
|
@ -90,13 +90,14 @@ MODULES = [
|
||||
'requests',
|
||||
'qtawesome',
|
||||
'pymediainfo',
|
||||
'vlc'
|
||||
'vlc',
|
||||
'zeroconf'
|
||||
]
|
||||
|
||||
|
||||
OPTIONAL_MODULES = [
|
||||
('qdarkstyle', '(dark style support)'),
|
||||
('mysql.connector', '(MySQL support)'),
|
||||
('pymysql', '(MySQL support)'),
|
||||
('pyodbc', '(ODBC support)'),
|
||||
('psycopg2', '(PostgreSQL support)'),
|
||||
('enchant', '(spell checker)'),
|
||||
|
46
scripts/pull_translations
Executable file
46
scripts/pull_translations
Executable file
@ -0,0 +1,46 @@
|
||||
#!/bin/sh
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||
|
||||
##########################################################################
|
||||
# OpenLP - Open Source Lyrics Projection #
|
||||
# ---------------------------------------------------------------------- #
|
||||
# Copyright (c) 2008-2019 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, either version 3 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# 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, see <https://www.gnu.org/licenses/>. #
|
||||
##########################################################################
|
||||
#
|
||||
# This script automates the update of the translations on OpenLP.
|
||||
#
|
||||
# It uses the tx client from Transifex for all the heavy lifting
|
||||
# All download *.ts files are converted to *.qm files which are used by
|
||||
# OpenLP.
|
||||
#
|
||||
###############################################################################
|
||||
pwd=`pwd`
|
||||
result=${PWD##*/}; echo $result
|
||||
|
||||
if [ $result != 'scripts' ] ; then
|
||||
echo 'This script must be run from the scripts directory'
|
||||
exit
|
||||
fi
|
||||
|
||||
|
||||
rm ../resources/i18n/*.ts
|
||||
|
||||
echo
|
||||
echo Downloading the translated files
|
||||
echo
|
||||
tx pull -a --minimum-perc=45
|
||||
|
||||
echo Translation update complete
|
52
scripts/push_translations
Executable file
52
scripts/push_translations
Executable file
@ -0,0 +1,52 @@
|
||||
##########################################################################
|
||||
# OpenLP - Open Source Lyrics Projection #
|
||||
# ---------------------------------------------------------------------- #
|
||||
# Copyright (c) 2008-2019 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, either version 3 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# 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, see <https://www.gnu.org/licenses/>. #
|
||||
##########################################################################
|
||||
#
|
||||
# This script automates the update of the translations on OpenLP.
|
||||
#
|
||||
# It uses the tx client from Transifex for all the heavy lifting
|
||||
# All download *.ts files are converted to *.qm files which are used by
|
||||
# OpenLP.
|
||||
#
|
||||
###############################################################################
|
||||
pwd=`pwd`
|
||||
result=${PWD##*/}; echo $result
|
||||
|
||||
if [ $result != 'scripts' ] ; then
|
||||
echo 'This script must be run from the scripts directory'
|
||||
exit
|
||||
fi
|
||||
|
||||
echo
|
||||
echo Generation translation control file
|
||||
echo
|
||||
rm ../resources/i18n/*.ts
|
||||
python3 $pwd/translation_utils.py -p
|
||||
|
||||
echo Creating base translation file
|
||||
cd ..
|
||||
pylupdate5 -verbose -noobsolete openlp.pro
|
||||
cd scripts
|
||||
|
||||
echo Check of invalid characters in push file
|
||||
grep -axv '.*' ../resources/i18n/en.ts
|
||||
|
||||
tx push -s
|
||||
|
||||
echo New translation file pushed.
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user