Merge trunk updates

This commit is contained in:
John Lines 2019-08-12 07:34:02 +01:00
commit 4cbc4c9d82
53 changed files with 420 additions and 329 deletions

View File

@ -45,7 +45,7 @@ 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'})
@ -103,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)
@ -172,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
@ -385,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'),

View File

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

View File

@ -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 &amp; 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):

View File

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

View File

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

View File

@ -281,8 +281,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
@ -333,7 +334,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();
@ -372,7 +373,6 @@ 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) {
var html = _prepareText(text);
@ -476,25 +476,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;
}
},
/**
@ -502,8 +505,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;
}
},
/**
@ -511,8 +515,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;
}
},
/**
@ -520,24 +525,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 */
}
},
@ -619,7 +627,6 @@ var Display = {
},
setTheme: function (theme) {
this._theme = theme;
var slidesDiv = $(".slides")
// Set the background
var globalBackground = $("#global-background")[0];
var backgroundStyle = {};

View File

@ -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}'
@ -482,6 +482,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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 = {}

View File

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

View File

@ -93,4 +93,4 @@ class SingleColumnTableWidget(QtWidgets.QTableWidget):
self.resizeRowsToContents()
__all__ = ['SingleColumnTableWidget', 'DisplayControllerType']
__all__ = ['AlertLocation', 'DisplayControllerType', 'HideMode', 'SingleColumnTableWidget']

View File

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

View File

@ -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>!--(?:(?!-->).)*--))>')

View File

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

View File

@ -635,7 +635,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()

View File

@ -219,13 +219,13 @@ class PrintServiceForm(QtWidgets.QDialog, Ui_PrintServiceDialog, RegistryPropert
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']:
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')

View File

@ -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
@ -828,7 +829,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

View File

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

View File

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

View File

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

View File

@ -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.
@ -219,7 +219,7 @@ class ListPreviewWidget(QtWidgets.QTableWidget, RegistryProperties):
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.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -291,6 +291,7 @@ class Ui_EditSongDialog(object):
self.warning_label.setObjectName('warning_label')
self.bottom_layout.addWidget(self.warning_label)
self.button_box = create_button_box(edit_song_dialog, 'button_box', ['cancel', 'save'])
self.save_button = self.button_box.button(QtWidgets.QDialogButtonBox.Save)
self.bottom_layout.addWidget(self.button_box)
self.dialog_layout.addLayout(self.bottom_layout)
self.retranslate_ui(edit_song_dialog)
@ -341,6 +342,7 @@ class Ui_EditSongDialog(object):
translate('SongsPlugin.EditSongForm', '<strong>Warning:</strong> Not all of the verses are in use.')
self.no_verse_order_entered_warning = \
translate('SongsPlugin.EditSongForm', '<strong>Warning:</strong> You have not entered a verse order.')
self.save_button.setText(UiStrings().SaveAndPreview)
def create_combo_box(parent, name, editable=True):

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 == '\\':

View File

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

View File

@ -26,7 +26,7 @@ from pathlib import Path
from unittest import TestCase
from unittest.mock import MagicMock, call, patch
from openlp.core.common import clean_button_text, de_hump, extension_loader, is_linux, is_macosx, is_win, \
from openlp.core.common import Singleton, clean_button_text, de_hump, extension_loader, is_linux, is_macosx, is_win, \
normalize_str, path_to_module, trace_error_handler
@ -163,6 +163,48 @@ class TestCommonFunctions(TestCase):
mocked_logger.error.assert_called_with(
'OpenLP Error trace\n File openlp.fake at line 56 \n\t called trace_error_handler_test')
def test_singleton_metaclass_multiple_init(self):
"""
Test that a class using the Singleton Metaclass is only initialised once despite being called several times and
that the same instance is returned each time..
"""
# GIVEN: The Singleton Metaclass and a test class using it
class SingletonClass(metaclass=Singleton):
def __init__(self):
pass
with patch.object(SingletonClass, '__init__', return_value=None) as patched_init:
# WHEN: Initialising the class multiple times
inst_1 = SingletonClass()
inst_2 = SingletonClass()
# THEN: The __init__ method of the SingletonClass should have only been called once, and both returned values
# should be the same instance.
assert inst_1 is inst_2
assert patched_init.call_count == 1
def test_singleton_metaclass_multiple_classes(self):
"""
Test that multiple classes using the Singleton Metaclass return the different an appropriate instances.
"""
# GIVEN: Two different classes using the Singleton Metaclass
class SingletonClass1(metaclass=Singleton):
def __init__(self):
pass
class SingletonClass2(metaclass=Singleton):
def __init__(self):
pass
# WHEN: Initialising both classes
s_c1 = SingletonClass1()
s_c2 = SingletonClass2()
# THEN: The instances should be an instance of the appropriate class
assert isinstance(s_c1, SingletonClass1)
assert isinstance(s_c2, SingletonClass2)
def test_is_win(self):
"""
Test the is_win() function

View File

@ -25,7 +25,78 @@ Package to test the openlp.core.lib.theme package.
from pathlib import Path
from unittest import TestCase
from openlp.core.lib.theme import Theme
from openlp.core.lib.theme import BackgroundType, Theme
class TestBackgroundType(TestCase):
"""
Test the BackgroundType enum methods.
"""
def test_solid_to_string(self):
"""
Test the to_string method of :class:`BackgroundType`
"""
# GIVEN: A BackgroundType member
background_type = BackgroundType.Solid
# WHEN: Calling BackgroundType.to_string
# THEN: The string equivalent should have been returned
assert BackgroundType.to_string(background_type) == 'solid'
def test_gradient_to_string(self):
"""
Test the to_string method of :class:`BackgroundType`
"""
# GIVEN: A BackgroundType member
background_type = BackgroundType.Gradient
# WHEN: Calling BackgroundType.to_string
# THEN: The string equivalent should have been returned
assert BackgroundType.to_string(background_type) == 'gradient'
def test_image_to_string(self):
"""
Test the to_string method of :class:`BackgroundType`
"""
# GIVEN: A BackgroundType member
background_type = BackgroundType.Image
# WHEN: Calling BackgroundType.to_string
# THEN: The string equivalent should have been returned
assert BackgroundType.to_string(background_type) == 'image'
def test_transparent_to_string(self):
"""
Test the to_string method of :class:`BackgroundType`
"""
# GIVEN: A BackgroundType member
background_type = BackgroundType.Transparent
# WHEN: Calling BackgroundType.to_string
# THEN: The string equivalent should have been returned
assert BackgroundType.to_string(background_type) == 'transparent'
def test_video_to_string(self):
"""
Test the to_string method of :class:`BackgroundType`
"""
# GIVEN: A BackgroundType member
background_type = BackgroundType.Video
# WHEN: Calling BackgroundType.to_string
# THEN: The string equivalent should have been returned
assert BackgroundType.to_string(background_type) == 'video'
def test_stream_to_string(self):
"""
Test the to_string method of :class:`BackgroundType`
"""
# GIVEN: A BackgroundType member
background_type = BackgroundType.Stream
# WHEN: Calling BackgroundType.to_string
# THEN: The string equivalent should have been returned
assert BackgroundType.to_string(background_type) == 'stream'
class TestTheme(TestCase):
@ -111,4 +182,4 @@ class TestTheme(TestCase):
assert 0 == theme.display_vertical_align, 'display_vertical_align should be 0'
assert theme.font_footer_bold is False, 'font_footer_bold should be False'
assert 'Arial' == theme.font_main_name, 'font_main_name should be "Arial"'
assert 47 == len(theme.__dict__), 'The theme should have 47 attributes'
assert 48 == len(theme.__dict__), 'The theme should have 48 attributes'

View File

@ -33,7 +33,7 @@ from tests.helpers.testmixin import TestMixin
class TestIcons(TestCase, TestMixin):
@patch('openlp.core.ui.icons.UiIcons.load')
@patch('openlp.core.ui.icons.UiIcons.__init__', return_value=None)
def test_simple_icon(self, _):
# GIVEN: an basic set of icons
icons = UiIcons()