forked from openlp/openlp
HEAD
This commit is contained in:
commit
bf3e891567
@ -33,12 +33,14 @@ import os
|
|||||||
import shutil
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
from datetime import datetime
|
||||||
from traceback import format_exception
|
from traceback import format_exception
|
||||||
|
|
||||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||||
|
|
||||||
from openlp.core.common import Registry, OpenLPMixin, AppLocation, LanguageManager, Settings, UiStrings, \
|
from openlp.core.common import Registry, OpenLPMixin, AppLocation, LanguageManager, Settings, UiStrings, \
|
||||||
check_directory_exists, is_macosx, is_win, translate
|
check_directory_exists, is_macosx, is_win, translate
|
||||||
|
from openlp.core.common.path import Path
|
||||||
from openlp.core.common.versionchecker import VersionThread, get_application_version
|
from openlp.core.common.versionchecker import VersionThread, get_application_version
|
||||||
from openlp.core.lib import ScreenList
|
from openlp.core.lib import ScreenList
|
||||||
from openlp.core.resources import qInitResources
|
from openlp.core.resources import qInitResources
|
||||||
@ -153,10 +155,8 @@ class OpenLP(OpenLPMixin, QtWidgets.QApplication):
|
|||||||
self.processEvents()
|
self.processEvents()
|
||||||
if not has_run_wizard:
|
if not has_run_wizard:
|
||||||
self.main_window.first_time()
|
self.main_window.first_time()
|
||||||
# update_check = Settings().value('core/update check')
|
version = VersionThread(self.main_window)
|
||||||
# if update_check:
|
version.start()
|
||||||
# version = VersionThread(self.main_window)
|
|
||||||
# version.start()
|
|
||||||
self.main_window.is_display_blank()
|
self.main_window.is_display_blank()
|
||||||
self.main_window.app_startup()
|
self.main_window.app_startup()
|
||||||
return self.exec()
|
return self.exec()
|
||||||
@ -181,7 +181,7 @@ class OpenLP(OpenLPMixin, QtWidgets.QApplication):
|
|||||||
"""
|
"""
|
||||||
Check if the data folder path exists.
|
Check if the data folder path exists.
|
||||||
"""
|
"""
|
||||||
data_folder_path = AppLocation.get_data_path()
|
data_folder_path = str(AppLocation.get_data_path())
|
||||||
if not os.path.exists(data_folder_path):
|
if not os.path.exists(data_folder_path):
|
||||||
log.critical('Database was not found in: ' + data_folder_path)
|
log.critical('Database was not found in: ' + data_folder_path)
|
||||||
status = QtWidgets.QMessageBox.critical(None, translate('OpenLP', 'Data Directory Error'),
|
status = QtWidgets.QMessageBox.critical(None, translate('OpenLP', 'Data Directory Error'),
|
||||||
@ -251,10 +251,9 @@ class OpenLP(OpenLPMixin, QtWidgets.QApplication):
|
|||||||
if QtWidgets.QMessageBox.question(None, translate('OpenLP', 'Backup'),
|
if QtWidgets.QMessageBox.question(None, translate('OpenLP', 'Backup'),
|
||||||
translate('OpenLP', 'OpenLP has been upgraded, do you want to create\n'
|
translate('OpenLP', 'OpenLP has been upgraded, do you want to create\n'
|
||||||
'a backup of the old data folder?'),
|
'a backup of the old data folder?'),
|
||||||
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No,
|
defaultButton=QtWidgets.QMessageBox.Yes) == QtWidgets.QMessageBox.Yes:
|
||||||
QtWidgets.QMessageBox.Yes) == QtWidgets.QMessageBox.Yes:
|
|
||||||
# Create copy of data folder
|
# Create copy of data folder
|
||||||
data_folder_path = AppLocation.get_data_path()
|
data_folder_path = str(AppLocation.get_data_path())
|
||||||
timestamp = time.strftime("%Y%m%d-%H%M%S")
|
timestamp = time.strftime("%Y%m%d-%H%M%S")
|
||||||
data_folder_backup_path = data_folder_path + '-' + timestamp
|
data_folder_backup_path = data_folder_path + '-' + timestamp
|
||||||
try:
|
try:
|
||||||
@ -338,6 +337,8 @@ def parse_options(args=None):
|
|||||||
parser.add_argument('-d', '--dev-version', dest='dev_version', action='store_true',
|
parser.add_argument('-d', '--dev-version', dest='dev_version', action='store_true',
|
||||||
help='Ignore the version file and pull the version directly from Bazaar')
|
help='Ignore the version file and pull the version directly from Bazaar')
|
||||||
parser.add_argument('-s', '--style', dest='style', help='Set the Qt5 style (passed directly to Qt5).')
|
parser.add_argument('-s', '--style', dest='style', help='Set the Qt5 style (passed directly to Qt5).')
|
||||||
|
parser.add_argument('-w', '--no-web-server', dest='no_web_server', action='store_false',
|
||||||
|
help='Turn off the Web and Socket Server ')
|
||||||
parser.add_argument('rargs', nargs='?', default=[])
|
parser.add_argument('rargs', nargs='?', default=[])
|
||||||
# Parse command line options and deal with them. Use args supplied pragmatically if possible.
|
# Parse command line options and deal with them. Use args supplied pragmatically if possible.
|
||||||
return parser.parse_args(args) if args else parser.parse_args()
|
return parser.parse_args(args) if args else parser.parse_args()
|
||||||
@ -347,15 +348,17 @@ def set_up_logging(log_path):
|
|||||||
"""
|
"""
|
||||||
Setup our logging using log_path
|
Setup our logging using log_path
|
||||||
|
|
||||||
:param log_path: the path
|
:param openlp.core.common.path.Path log_path: The file to save the log to.
|
||||||
|
:rtype: None
|
||||||
"""
|
"""
|
||||||
check_directory_exists(log_path, True)
|
check_directory_exists(log_path, True)
|
||||||
filename = os.path.join(log_path, 'openlp.log')
|
file_path = log_path / 'openlp.log'
|
||||||
logfile = logging.FileHandler(filename, 'w', encoding="UTF-8")
|
# TODO: FileHandler accepts a Path object in Py3.6
|
||||||
|
logfile = logging.FileHandler(str(file_path), 'w', encoding='UTF-8')
|
||||||
logfile.setFormatter(logging.Formatter('%(asctime)s %(name)-55s %(levelname)-8s %(message)s'))
|
logfile.setFormatter(logging.Formatter('%(asctime)s %(name)-55s %(levelname)-8s %(message)s'))
|
||||||
log.addHandler(logfile)
|
log.addHandler(logfile)
|
||||||
if log.isEnabledFor(logging.DEBUG):
|
if log.isEnabledFor(logging.DEBUG):
|
||||||
print('Logging to: {name}'.format(name=filename))
|
print('Logging to: {name}'.format(name=file_path))
|
||||||
|
|
||||||
|
|
||||||
def main(args=None):
|
def main(args=None):
|
||||||
@ -391,16 +394,16 @@ def main(args=None):
|
|||||||
application.setApplicationName('OpenLPPortable')
|
application.setApplicationName('OpenLPPortable')
|
||||||
Settings.setDefaultFormat(Settings.IniFormat)
|
Settings.setDefaultFormat(Settings.IniFormat)
|
||||||
# Get location OpenLPPortable.ini
|
# Get location OpenLPPortable.ini
|
||||||
application_path = AppLocation.get_directory(AppLocation.AppDir)
|
portable_path = (AppLocation.get_directory(AppLocation.AppDir) / '..' / '..').resolve()
|
||||||
set_up_logging(os.path.abspath(os.path.join(application_path, '..', '..', 'Other')))
|
data_path = portable_path / 'Data'
|
||||||
|
set_up_logging(portable_path / 'Other')
|
||||||
log.info('Running portable')
|
log.info('Running portable')
|
||||||
portable_settings_file = os.path.abspath(os.path.join(application_path, '..', '..', 'Data', 'OpenLP.ini'))
|
portable_settings_path = data_path / 'OpenLP.ini'
|
||||||
# Make this our settings file
|
# Make this our settings file
|
||||||
log.info('INI file: {name}'.format(name=portable_settings_file))
|
log.info('INI file: {name}'.format(name=portable_settings_path))
|
||||||
Settings.set_filename(portable_settings_file)
|
Settings.set_filename(str(portable_settings_path))
|
||||||
portable_settings = Settings()
|
portable_settings = Settings()
|
||||||
# Set our data path
|
# Set our data path
|
||||||
data_path = os.path.abspath(os.path.join(application_path, '..', '..', 'Data',))
|
|
||||||
log.info('Data path: {name}'.format(name=data_path))
|
log.info('Data path: {name}'.format(name=data_path))
|
||||||
# Point to our data path
|
# Point to our data path
|
||||||
portable_settings.setValue('advanced/data path', data_path)
|
portable_settings.setValue('advanced/data path', data_path)
|
||||||
@ -411,6 +414,7 @@ def main(args=None):
|
|||||||
set_up_logging(AppLocation.get_directory(AppLocation.CacheDir))
|
set_up_logging(AppLocation.get_directory(AppLocation.CacheDir))
|
||||||
Registry.create()
|
Registry.create()
|
||||||
Registry().register('application', application)
|
Registry().register('application', application)
|
||||||
|
Registry().set_flag('no_web_server', args.no_web_server)
|
||||||
application.setApplicationVersion(get_application_version()['version'])
|
application.setApplicationVersion(get_application_version()['version'])
|
||||||
# Check if an instance of OpenLP is already running. Quit if there is a running instance and the user only wants one
|
# Check if an instance of OpenLP is already running. Quit if there is a running instance and the user only wants one
|
||||||
if application.is_already_running():
|
if application.is_already_running():
|
||||||
@ -419,8 +423,21 @@ def main(args=None):
|
|||||||
if application.is_data_path_missing():
|
if application.is_data_path_missing():
|
||||||
application.shared_memory.detach()
|
application.shared_memory.detach()
|
||||||
sys.exit()
|
sys.exit()
|
||||||
# Remove/convert obsolete settings.
|
# Upgrade settings.
|
||||||
Settings().remove_obsolete_settings()
|
settings = Settings()
|
||||||
|
if settings.can_upgrade():
|
||||||
|
now = datetime.now()
|
||||||
|
# Only back up if OpenLP has previously run.
|
||||||
|
if settings.value('core/has run wizard'):
|
||||||
|
back_up_path = AppLocation.get_data_path() / (now.strftime('%Y-%m-%d %H-%M') + '.conf')
|
||||||
|
log.info('Settings about to be upgraded. Existing settings are being backed up to {back_up_path}'
|
||||||
|
.format(back_up_path=back_up_path))
|
||||||
|
QtWidgets.QMessageBox.information(
|
||||||
|
None, translate('OpenLP', 'Settings Upgrade'),
|
||||||
|
translate('OpenLP', 'Your settings are about to upgraded. A backup will be created at {back_up_path}')
|
||||||
|
.format(back_up_path=back_up_path))
|
||||||
|
settings.export(back_up_path)
|
||||||
|
settings.upgrade_settings()
|
||||||
# First time checks in settings
|
# First time checks in settings
|
||||||
if not Settings().value('core/has run wizard'):
|
if not Settings().value('core/has run wizard'):
|
||||||
if not FirstTimeLanguageForm().exec():
|
if not FirstTimeLanguageForm().exec():
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<!DOCTYPE html>
|
# -*- coding: utf-8 -*-
|
||||||
<html>
|
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||||
<!--
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
# OpenLP - Open Source Lyrics Projection #
|
# OpenLP - Open Source Lyrics Projection #
|
||||||
# --------------------------------------------------------------------------- #
|
# --------------------------------------------------------------------------- #
|
||||||
@ -19,16 +19,10 @@
|
|||||||
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||||
###############################################################################
|
###############################################################################
|
||||||
-->
|
|
||||||
<head>
|
from openlp.core.api.http.endpoint import Endpoint
|
||||||
<meta charset="utf-8" />
|
from openlp.core.api.http import register_endpoint, requires_auth
|
||||||
<title>${live_title}</title>
|
from openlp.core.api.tab import ApiTab
|
||||||
<link rel="stylesheet" href="/css/main.css" />
|
from openlp.core.api.poll import Poller
|
||||||
<link rel="shortcut icon" type="image/x-icon" href="/images/favicon.ico">
|
|
||||||
<script type="text/javascript" src="/assets/jquery.min.js"></script>
|
__all__ = ['Endpoint', 'ApiTab', 'register_endpoint', 'requires_auth']
|
||||||
<script type="text/javascript" src="/js/main.js"></script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<img id="image" class="size"/>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -19,9 +19,7 @@
|
|||||||
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
"""
|
||||||
from .remotetab import RemoteTab
|
The Endpoint class, which provides plugins with a way to serve their own portion of the API
|
||||||
from .httprouter import HttpRouter
|
"""
|
||||||
from .httpserver import OpenLPServer
|
from .pluginhelpers import search, live, service
|
||||||
|
|
||||||
__all__ = ['RemoteTab', 'OpenLPServer', 'HttpRouter']
|
|
143
openlp/core/api/endpoint/controller.py
Normal file
143
openlp/core/api/endpoint/controller.py
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# OpenLP - Open Source Lyrics Projection #
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# Copyright (c) 2008-2017 OpenLP Developers #
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# This program is free software; you can redistribute it and/or modify it #
|
||||||
|
# under the terms of the GNU General Public License as published by the Free #
|
||||||
|
# Software Foundation; version 2 of the License. #
|
||||||
|
# #
|
||||||
|
# This program is distributed in the hope that it will be useful, but WITHOUT #
|
||||||
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
|
||||||
|
# more details. #
|
||||||
|
# #
|
||||||
|
# You should have received a copy of the GNU General Public License along #
|
||||||
|
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||||
|
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||||
|
###############################################################################
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import urllib.request
|
||||||
|
import urllib.error
|
||||||
|
import json
|
||||||
|
|
||||||
|
from openlp.core.api.http.endpoint import Endpoint
|
||||||
|
from openlp.core.api.http import requires_auth
|
||||||
|
from openlp.core.common import Registry, AppLocation, Settings
|
||||||
|
from openlp.core.lib import ItemCapabilities, create_thumb
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
controller_endpoint = Endpoint('controller')
|
||||||
|
api_controller_endpoint = Endpoint('api')
|
||||||
|
|
||||||
|
|
||||||
|
@api_controller_endpoint.route('controller/live/text')
|
||||||
|
@controller_endpoint.route('live/text')
|
||||||
|
def controller_text(request):
|
||||||
|
"""
|
||||||
|
Perform an action on the slide controller.
|
||||||
|
|
||||||
|
:param request: the http request - not used
|
||||||
|
"""
|
||||||
|
log.debug("controller_text ")
|
||||||
|
live_controller = Registry().get('live_controller')
|
||||||
|
current_item = live_controller.service_item
|
||||||
|
data = []
|
||||||
|
if current_item:
|
||||||
|
for index, frame in enumerate(current_item.get_frames()):
|
||||||
|
item = {}
|
||||||
|
# Handle text (songs, custom, bibles)
|
||||||
|
if current_item.is_text():
|
||||||
|
if frame['verseTag']:
|
||||||
|
item['tag'] = str(frame['verseTag'])
|
||||||
|
else:
|
||||||
|
item['tag'] = str(index + 1)
|
||||||
|
item['chords_text'] = str(frame['chords_text'])
|
||||||
|
item['text'] = str(frame['text'])
|
||||||
|
item['html'] = str(frame['html'])
|
||||||
|
# Handle images, unless a custom thumbnail is given or if thumbnails is disabled
|
||||||
|
elif current_item.is_image() and not frame.get('image', '') and Settings().value('api/thumbnails'):
|
||||||
|
item['tag'] = str(index + 1)
|
||||||
|
thumbnail_path = os.path.join('images', 'thumbnails', frame['title'])
|
||||||
|
full_thumbnail_path = str(AppLocation.get_data_path() / thumbnail_path)
|
||||||
|
# Create thumbnail if it doesn't exists
|
||||||
|
if not os.path.exists(full_thumbnail_path):
|
||||||
|
create_thumb(current_item.get_frame_path(index), full_thumbnail_path, False)
|
||||||
|
Registry().get('image_manager').add_image(full_thumbnail_path, frame['title'], None, 88, 88)
|
||||||
|
item['img'] = urllib.request.pathname2url(os.path.sep + thumbnail_path)
|
||||||
|
item['text'] = str(frame['title'])
|
||||||
|
item['html'] = str(frame['title'])
|
||||||
|
else:
|
||||||
|
# Handle presentation etc.
|
||||||
|
item['tag'] = str(index + 1)
|
||||||
|
if current_item.is_capable(ItemCapabilities.HasDisplayTitle):
|
||||||
|
item['title'] = str(frame['display_title'])
|
||||||
|
if current_item.is_capable(ItemCapabilities.HasNotes):
|
||||||
|
item['slide_notes'] = str(frame['notes'])
|
||||||
|
if current_item.is_capable(ItemCapabilities.HasThumbnails) and Settings().value('api/thumbnails'):
|
||||||
|
# If the file is under our app directory tree send the portion after the match
|
||||||
|
data_path = str(AppLocation.get_data_path())
|
||||||
|
if frame['image'][0:len(data_path)] == data_path:
|
||||||
|
item['img'] = urllib.request.pathname2url(frame['image'][len(data_path):])
|
||||||
|
Registry().get('image_manager').add_image(frame['image'], frame['title'], None, 88, 88)
|
||||||
|
item['text'] = str(frame['title'])
|
||||||
|
item['html'] = str(frame['title'])
|
||||||
|
item['selected'] = (live_controller.selected_row == index)
|
||||||
|
data.append(item)
|
||||||
|
json_data = {'results': {'slides': data}}
|
||||||
|
if current_item:
|
||||||
|
json_data['results']['item'] = live_controller.service_item.unique_identifier
|
||||||
|
return json_data
|
||||||
|
|
||||||
|
|
||||||
|
@api_controller_endpoint.route('controller/live/set')
|
||||||
|
@controller_endpoint.route('live/set')
|
||||||
|
@requires_auth
|
||||||
|
def controller_set(request):
|
||||||
|
"""
|
||||||
|
Perform an action on the slide controller.
|
||||||
|
|
||||||
|
:param request: The action to perform.
|
||||||
|
"""
|
||||||
|
event = getattr(Registry().get('live_controller'), 'slidecontroller_live_set')
|
||||||
|
try:
|
||||||
|
json_data = request.GET.get('data')
|
||||||
|
data = int(json.loads(json_data)['request']['id'])
|
||||||
|
event.emit([data])
|
||||||
|
except KeyError:
|
||||||
|
log.error("Endpoint controller/live/set request id not found")
|
||||||
|
return {'results': {'success': True}}
|
||||||
|
|
||||||
|
|
||||||
|
@controller_endpoint.route('{action:next|previous}')
|
||||||
|
@requires_auth
|
||||||
|
def controller_direction(request, controller, action):
|
||||||
|
"""
|
||||||
|
Handles requests for setting service items in the slide controller
|
||||||
|
11
|
||||||
|
:param request: The http request object.
|
||||||
|
:param controller: the controller slides forward or backward.
|
||||||
|
:param action: the controller slides forward or backward.
|
||||||
|
"""
|
||||||
|
event = getattr(Registry().get('live_controller'), 'slidecontroller_{controller}_{action}'.
|
||||||
|
format(controller=controller, action=action))
|
||||||
|
event.emit()
|
||||||
|
|
||||||
|
|
||||||
|
@api_controller_endpoint.route('controller/{controller}/{action:next|previous}')
|
||||||
|
@requires_auth
|
||||||
|
def controller_direction_api(request, controller, action):
|
||||||
|
"""
|
||||||
|
Handles requests for setting service items in the slide controller
|
||||||
|
11
|
||||||
|
:param request: The http request object.
|
||||||
|
:param controller: the controller slides forward or backward.
|
||||||
|
:param action: the controller slides forward or backward.
|
||||||
|
"""
|
||||||
|
controller_direction(request, controller, action)
|
||||||
|
return {'results': {'success': True}}
|
182
openlp/core/api/endpoint/core.py
Normal file
182
openlp/core/api/endpoint/core.py
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# OpenLP - Open Source Lyrics Projection #
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# Copyright (c) 2008-2017 OpenLP Developers #
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# This program is free software; you can redistribute it and/or modify it #
|
||||||
|
# under the terms of the GNU General Public License as published by the Free #
|
||||||
|
# Software Foundation; version 2 of the License. #
|
||||||
|
# #
|
||||||
|
# This program is distributed in the hope that it will be useful, but WITHOUT #
|
||||||
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
|
||||||
|
# more details. #
|
||||||
|
# #
|
||||||
|
# You should have received a copy of the GNU General Public License along #
|
||||||
|
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||||
|
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||||
|
###############################################################################
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
|
from openlp.core.api.http.endpoint import Endpoint
|
||||||
|
from openlp.core.api.http import requires_auth
|
||||||
|
from openlp.core.common import Registry, UiStrings, translate
|
||||||
|
from openlp.core.lib import image_to_byte, PluginStatus, StringContent
|
||||||
|
|
||||||
|
|
||||||
|
template_dir = 'templates'
|
||||||
|
static_dir = 'static'
|
||||||
|
blank_dir = os.path.join(static_dir, 'index')
|
||||||
|
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
chords_endpoint = Endpoint('chords', template_dir=template_dir, static_dir=static_dir)
|
||||||
|
stage_endpoint = Endpoint('stage', template_dir=template_dir, static_dir=static_dir)
|
||||||
|
main_endpoint = Endpoint('main', template_dir=template_dir, static_dir=static_dir)
|
||||||
|
blank_endpoint = Endpoint('', template_dir=template_dir, static_dir=blank_dir)
|
||||||
|
|
||||||
|
FILE_TYPES = {
|
||||||
|
'.html': 'text/html',
|
||||||
|
'.css': 'text/css',
|
||||||
|
'.js': 'application/javascript',
|
||||||
|
'.jpg': 'image/jpeg',
|
||||||
|
'.gif': 'image/gif',
|
||||||
|
'.ico': 'image/x-icon',
|
||||||
|
'.png': 'image/png'
|
||||||
|
}
|
||||||
|
|
||||||
|
remote = translate('RemotePlugin.Mobile', 'Remote')
|
||||||
|
stage = translate('RemotePlugin.Mobile', 'Stage View')
|
||||||
|
live = translate('RemotePlugin.Mobile', 'Live View')
|
||||||
|
chords = translate('RemotePlugin.Mobile', 'Chords View')
|
||||||
|
|
||||||
|
TRANSLATED_STRINGS = {
|
||||||
|
'app_title': "{main} {remote}".format(main=UiStrings().OpenLP, remote=remote),
|
||||||
|
'stage_title': "{main} {stage}".format(main=UiStrings().OpenLP, stage=stage),
|
||||||
|
'live_title': "{main} {live}".format(main=UiStrings().OpenLP, live=live),
|
||||||
|
'chords_title': "{main} {chords}".format(main=UiStrings().OpenLP, chords=chords),
|
||||||
|
'service_manager': translate('RemotePlugin.Mobile', 'Service Manager'),
|
||||||
|
'slide_controller': translate('RemotePlugin.Mobile', 'Slide Controller'),
|
||||||
|
'alerts': translate('RemotePlugin.Mobile', 'Alerts'),
|
||||||
|
'search': translate('RemotePlugin.Mobile', 'Search'),
|
||||||
|
'home': translate('RemotePlugin.Mobile', 'Home'),
|
||||||
|
'refresh': translate('RemotePlugin.Mobile', 'Refresh'),
|
||||||
|
'blank': translate('RemotePlugin.Mobile', 'Blank'),
|
||||||
|
'theme': translate('RemotePlugin.Mobile', 'Theme'),
|
||||||
|
'desktop': translate('RemotePlugin.Mobile', 'Desktop'),
|
||||||
|
'show': translate('RemotePlugin.Mobile', 'Show'),
|
||||||
|
'prev': translate('RemotePlugin.Mobile', 'Prev'),
|
||||||
|
'next': translate('RemotePlugin.Mobile', 'Next'),
|
||||||
|
'text': translate('RemotePlugin.Mobile', 'Text'),
|
||||||
|
'show_alert': translate('RemotePlugin.Mobile', 'Show Alert'),
|
||||||
|
'go_live': translate('RemotePlugin.Mobile', 'Go Live'),
|
||||||
|
'add_to_service': translate('RemotePlugin.Mobile', 'Add to Service'),
|
||||||
|
'add_and_go_to_service': translate('RemotePlugin.Mobile', 'Add & Go to Service'),
|
||||||
|
'no_results': translate('RemotePlugin.Mobile', 'No Results'),
|
||||||
|
'options': translate('RemotePlugin.Mobile', 'Options'),
|
||||||
|
'service': translate('RemotePlugin.Mobile', 'Service'),
|
||||||
|
'slides': translate('RemotePlugin.Mobile', 'Slides'),
|
||||||
|
'settings': translate('RemotePlugin.Mobile', 'Settings'),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@stage_endpoint.route('')
|
||||||
|
def stage_index(request):
|
||||||
|
"""
|
||||||
|
Deliver the page for the /stage url
|
||||||
|
"""
|
||||||
|
return stage_endpoint.render_template('stage.mako', **TRANSLATED_STRINGS)
|
||||||
|
|
||||||
|
|
||||||
|
@chords_endpoint.route('')
|
||||||
|
def chords_index(request):
|
||||||
|
"""
|
||||||
|
Deliver the page for the /chords url
|
||||||
|
"""
|
||||||
|
return chords_endpoint.render_template('chords.mako', **TRANSLATED_STRINGS)
|
||||||
|
|
||||||
|
|
||||||
|
@main_endpoint.route('')
|
||||||
|
def main_index(request):
|
||||||
|
"""
|
||||||
|
Deliver the page for the /main url
|
||||||
|
"""
|
||||||
|
return main_endpoint.render_template('main.mako', **TRANSLATED_STRINGS)
|
||||||
|
|
||||||
|
|
||||||
|
@blank_endpoint.route('')
|
||||||
|
def index(request):
|
||||||
|
"""
|
||||||
|
Deliver the page for the / url
|
||||||
|
:param request:
|
||||||
|
"""
|
||||||
|
return blank_endpoint.render_template('index.mako', **TRANSLATED_STRINGS)
|
||||||
|
|
||||||
|
|
||||||
|
@blank_endpoint.route('api/poll')
|
||||||
|
@blank_endpoint.route('poll')
|
||||||
|
def poll(request):
|
||||||
|
"""
|
||||||
|
Deliver the page for the /poll url
|
||||||
|
|
||||||
|
:param request:
|
||||||
|
"""
|
||||||
|
return Registry().get('poller').poll()
|
||||||
|
|
||||||
|
|
||||||
|
@blank_endpoint.route('api/display/{display:hide|show|blank|theme|desktop}')
|
||||||
|
@blank_endpoint.route('display/{display:hide|show|blank|theme|desktop}')
|
||||||
|
@requires_auth
|
||||||
|
def toggle_display(request, display):
|
||||||
|
"""
|
||||||
|
Deliver the functions for the /display url
|
||||||
|
:param request: the http request - not used
|
||||||
|
:param display: the display function to be triggered
|
||||||
|
"""
|
||||||
|
Registry().get('live_controller').slidecontroller_toggle_display.emit(display)
|
||||||
|
return {'results': {'success': True}}
|
||||||
|
|
||||||
|
|
||||||
|
@blank_endpoint.route('api/plugin/search')
|
||||||
|
@blank_endpoint.route('plugin/search')
|
||||||
|
def plugin_search_list(request):
|
||||||
|
"""
|
||||||
|
Deliver a list of active plugins that support search
|
||||||
|
:param request: the http request - not used
|
||||||
|
"""
|
||||||
|
searches = []
|
||||||
|
for plugin in Registry().get('plugin_manager').plugins:
|
||||||
|
if plugin.status == PluginStatus.Active and plugin.media_item and plugin.media_item.has_search:
|
||||||
|
searches.append([plugin.name, str(plugin.text_strings[StringContent.Name]['plural'])])
|
||||||
|
return {'results': {'items': searches}}
|
||||||
|
|
||||||
|
|
||||||
|
@main_endpoint.route('image')
|
||||||
|
def main_image(request):
|
||||||
|
"""
|
||||||
|
Return the latest display image as a byte stream.
|
||||||
|
:param request: base path of the URL. Not used but passed by caller
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
live_controller = Registry().get('live_controller')
|
||||||
|
result = {
|
||||||
|
'slide_image': 'data:image/png;base64,' + str(image_to_byte(live_controller.slide_image))
|
||||||
|
}
|
||||||
|
return {'results': result}
|
||||||
|
|
||||||
|
|
||||||
|
def get_content_type(file_name):
|
||||||
|
"""
|
||||||
|
Examines the extension of the file and determines what the content_type should be, defaults to text/plain
|
||||||
|
Returns the extension and the content_type
|
||||||
|
|
||||||
|
:param file_name: name of file
|
||||||
|
"""
|
||||||
|
ext = os.path.splitext(file_name)[1]
|
||||||
|
content_type = FILE_TYPES.get(ext, 'text/plain')
|
||||||
|
return ext, content_type
|
135
openlp/core/api/endpoint/pluginhelpers.py
Normal file
135
openlp/core/api/endpoint/pluginhelpers.py
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# OpenLP - Open Source Lyrics Projection #
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# Copyright (c) 2008-2017 OpenLP Developers #
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# This program is free software; you can redistribute it and/or modify it #
|
||||||
|
# under the terms of the GNU General Public License as published by the Free #
|
||||||
|
# Software Foundation; version 2 of the License. #
|
||||||
|
# #
|
||||||
|
# This program is distributed in the hope that it will be useful, but WITHOUT #
|
||||||
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
|
||||||
|
# more details. #
|
||||||
|
# #
|
||||||
|
# You should have received a copy of the GNU General Public License along #
|
||||||
|
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||||
|
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||||
|
###############################################################################
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
import urllib
|
||||||
|
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
from webob import Response
|
||||||
|
|
||||||
|
from openlp.core.api.http.errors import NotFound
|
||||||
|
from openlp.core.common import Registry, AppLocation
|
||||||
|
from openlp.core.lib import PluginStatus, image_to_byte
|
||||||
|
|
||||||
|
|
||||||
|
def search(request, plugin_name, log):
|
||||||
|
"""
|
||||||
|
Handles requests for searching the plugins
|
||||||
|
|
||||||
|
:param request: The http request object.
|
||||||
|
:param plugin_name: The plugin name.
|
||||||
|
:param log: The class log object.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
json_data = request.GET.get('data')
|
||||||
|
text = json.loads(json_data)['request']['text']
|
||||||
|
except KeyError:
|
||||||
|
log.error("Endpoint {text} search request text not found".format(text=plugin_name))
|
||||||
|
text = ""
|
||||||
|
text = urllib.parse.unquote(text)
|
||||||
|
plugin = Registry().get('plugin_manager').get_plugin_by_name(plugin_name)
|
||||||
|
if plugin.status == PluginStatus.Active and plugin.media_item and plugin.media_item.has_search:
|
||||||
|
results = plugin.media_item.search(text, False)
|
||||||
|
return {'results': {'items': results}}
|
||||||
|
else:
|
||||||
|
raise NotFound()
|
||||||
|
|
||||||
|
|
||||||
|
def live(request, plugin_name, log):
|
||||||
|
"""
|
||||||
|
Handles requests for making live of the plugins
|
||||||
|
|
||||||
|
:param request: The http request object.
|
||||||
|
:param plugin_name: The plugin name.
|
||||||
|
:param log: The class log object.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
json_data = request.GET.get('data')
|
||||||
|
request_id = json.loads(json_data)['request']['id']
|
||||||
|
except KeyError:
|
||||||
|
log.error("Endpoint {text} search request text not found".format(text=plugin_name))
|
||||||
|
return []
|
||||||
|
plugin = Registry().get('plugin_manager').get_plugin_by_name(plugin_name)
|
||||||
|
if plugin.status == PluginStatus.Active and plugin.media_item:
|
||||||
|
getattr(plugin.media_item, '{name}_go_live'.format(name=plugin_name)).emit([request_id, True])
|
||||||
|
|
||||||
|
|
||||||
|
def service(request, plugin_name, log):
|
||||||
|
"""
|
||||||
|
Handles requests for adding to a service of the plugins
|
||||||
|
|
||||||
|
:param request: The http request object.
|
||||||
|
:param plugin_name: The plugin name.
|
||||||
|
:param log: The class log object.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
json_data = request.GET.get('data')
|
||||||
|
request_id = json.loads(json_data)['request']['id']
|
||||||
|
except KeyError:
|
||||||
|
log.error("Endpoint {plugin} search request text not found".format(plugin=plugin_name))
|
||||||
|
return []
|
||||||
|
plugin = Registry().get('plugin_manager').get_plugin_by_name(plugin_name)
|
||||||
|
if plugin.status == PluginStatus.Active and plugin.media_item:
|
||||||
|
item_id = plugin.media_item.create_item_from_id(request_id)
|
||||||
|
getattr(plugin.media_item, '{name}_add_to_service'.format(name=plugin_name)).emit([item_id, True])
|
||||||
|
|
||||||
|
|
||||||
|
def display_thumbnails(request, controller_name, log, dimensions, file_name, slide=None):
|
||||||
|
"""
|
||||||
|
Handles requests for adding a song to the service
|
||||||
|
|
||||||
|
Return an image to a web page based on a URL
|
||||||
|
:param request: Request object
|
||||||
|
:param controller_name: which controller is requesting the image
|
||||||
|
:param log: the logger object
|
||||||
|
:param dimensions: the image size eg 88x88
|
||||||
|
:param file_name: the file name of the image
|
||||||
|
:param slide: the individual image name
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
log.debug('serve thumbnail {cname}/thumbnails{dim}/{fname}/{slide}'.format(cname=controller_name,
|
||||||
|
dim=dimensions,
|
||||||
|
fname=file_name,
|
||||||
|
slide=slide))
|
||||||
|
# -1 means use the default dimension in ImageManager
|
||||||
|
width = -1
|
||||||
|
height = -1
|
||||||
|
image = None
|
||||||
|
if dimensions:
|
||||||
|
match = re.search('(\d+)x(\d+)', dimensions)
|
||||||
|
if match:
|
||||||
|
# let's make sure that the dimensions are within reason
|
||||||
|
width = sorted([10, int(match.group(1)), 1000])[1]
|
||||||
|
height = sorted([10, int(match.group(2)), 1000])[1]
|
||||||
|
if controller_name and file_name:
|
||||||
|
file_name = urllib.parse.unquote(file_name)
|
||||||
|
if '..' not in file_name: # no hacking please
|
||||||
|
if slide:
|
||||||
|
full_path = str(AppLocation.get_section_data_path(controller_name) / 'thumbnails' / file_name / slide)
|
||||||
|
else:
|
||||||
|
full_path = str(AppLocation.get_section_data_path(controller_name) / 'thumbnails' / file_name)
|
||||||
|
if os.path.exists(full_path):
|
||||||
|
path, just_file_name = os.path.split(full_path)
|
||||||
|
Registry().get('image_manager').add_image(full_path, just_file_name, None, width, height)
|
||||||
|
image = Registry().get('image_manager').get_image(full_path, just_file_name, width, height)
|
||||||
|
return Response(body=image_to_byte(image, False), status=200, content_type='image/png', charset='utf8')
|
100
openlp/core/api/endpoint/service.py
Normal file
100
openlp/core/api/endpoint/service.py
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# OpenLP - Open Source Lyrics Projection #
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# Copyright (c) 2008-2017 OpenLP Developers #
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# This program is free software; you can redistribute it and/or modify it #
|
||||||
|
# under the terms of the GNU General Public License as published by the Free #
|
||||||
|
# Software Foundation; version 2 of the License. #
|
||||||
|
# #
|
||||||
|
# This program is distributed in the hope that it will be useful, but WITHOUT #
|
||||||
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
|
||||||
|
# more details. #
|
||||||
|
# #
|
||||||
|
# You should have received a copy of the GNU General Public License along #
|
||||||
|
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||||
|
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||||
|
###############################################################################
|
||||||
|
import logging
|
||||||
|
import json
|
||||||
|
|
||||||
|
from openlp.core.api.http.endpoint import Endpoint
|
||||||
|
from openlp.core.api.http import register_endpoint, requires_auth
|
||||||
|
from openlp.core.common import Registry
|
||||||
|
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
service_endpoint = Endpoint('service')
|
||||||
|
api_service_endpoint = Endpoint('api/service')
|
||||||
|
|
||||||
|
|
||||||
|
@api_service_endpoint.route('list')
|
||||||
|
@service_endpoint.route('list')
|
||||||
|
def list_service(request):
|
||||||
|
"""
|
||||||
|
Handles requests for service items in the service manager
|
||||||
|
|
||||||
|
:param request: The http request object.
|
||||||
|
"""
|
||||||
|
return {'results': {'items': get_service_items()}}
|
||||||
|
|
||||||
|
|
||||||
|
@api_service_endpoint.route('set')
|
||||||
|
@service_endpoint.route('set')
|
||||||
|
@requires_auth
|
||||||
|
def service_set(request):
|
||||||
|
"""
|
||||||
|
Handles requests for setting service items in the service manager
|
||||||
|
|
||||||
|
:param request: The http request object.
|
||||||
|
"""
|
||||||
|
event = getattr(Registry().get('service_manager'), 'servicemanager_set_item')
|
||||||
|
try:
|
||||||
|
json_data = request.GET.get('data')
|
||||||
|
data = int(json.loads(json_data)['request']['id'])
|
||||||
|
event.emit(data)
|
||||||
|
except KeyError:
|
||||||
|
log.error("Endpoint service/set request id not found")
|
||||||
|
return {'results': {'success': True}}
|
||||||
|
|
||||||
|
|
||||||
|
@api_service_endpoint.route('{action:next|previous}')
|
||||||
|
@service_endpoint.route('{action:next|previous}')
|
||||||
|
@requires_auth
|
||||||
|
def service_direction(request, action):
|
||||||
|
"""
|
||||||
|
Handles requests for setting service items in the service manager
|
||||||
|
|
||||||
|
:param request: The http request object.
|
||||||
|
:param action: the the service slides forward or backward.
|
||||||
|
"""
|
||||||
|
event = getattr(Registry().get('service_manager'), 'servicemanager_{action}_item'.format(action=action))
|
||||||
|
event.emit()
|
||||||
|
return {'results': {'success': True}}
|
||||||
|
|
||||||
|
|
||||||
|
def get_service_items():
|
||||||
|
"""
|
||||||
|
Read the service item in use and return the data as a json object
|
||||||
|
"""
|
||||||
|
live_controller = Registry().get('live_controller')
|
||||||
|
service_items = []
|
||||||
|
if live_controller.service_item:
|
||||||
|
current_unique_identifier = live_controller.service_item.unique_identifier
|
||||||
|
else:
|
||||||
|
current_unique_identifier = None
|
||||||
|
for item in Registry().get('service_manager').service_items:
|
||||||
|
service_item = item['service_item']
|
||||||
|
service_items.append({
|
||||||
|
'id': str(service_item.unique_identifier),
|
||||||
|
'title': str(service_item.get_display_title()),
|
||||||
|
'plugin': str(service_item.name),
|
||||||
|
'notes': str(service_item.notes),
|
||||||
|
'selected': (service_item.unique_identifier == current_unique_identifier)
|
||||||
|
})
|
||||||
|
return service_items
|
110
openlp/core/api/http/__init__.py
Normal file
110
openlp/core/api/http/__init__.py
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# OpenLP - Open Source Lyrics Projection #
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# Copyright (c) 2008-2017 OpenLP Developers #
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# This program is free software; you can redistribute it and/or modify it #
|
||||||
|
# under the terms of the GNU General Public License as published by the Free #
|
||||||
|
# Software Foundation; version 2 of the License. #
|
||||||
|
# #
|
||||||
|
# This program is distributed in the hope that it will be useful, but WITHOUT #
|
||||||
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
|
||||||
|
# more details. #
|
||||||
|
# #
|
||||||
|
# You should have received a copy of the GNU General Public License along #
|
||||||
|
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||||
|
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
import base64
|
||||||
|
from functools import wraps
|
||||||
|
from webob import Response
|
||||||
|
|
||||||
|
from openlp.core.common.settings import Settings
|
||||||
|
from openlp.core.api.http.wsgiapp import WSGIApplication
|
||||||
|
from .errors import NotFound, ServerError, HttpError
|
||||||
|
|
||||||
|
application = WSGIApplication('api')
|
||||||
|
|
||||||
|
|
||||||
|
def _route_from_url(url_prefix, url):
|
||||||
|
"""
|
||||||
|
Create a route from the URL
|
||||||
|
"""
|
||||||
|
url_prefix = '/{prefix}/'.format(prefix=url_prefix.strip('/'))
|
||||||
|
if not url:
|
||||||
|
url = url_prefix[:-1]
|
||||||
|
else:
|
||||||
|
url = url_prefix + url
|
||||||
|
url = url.replace('//', '/')
|
||||||
|
return url
|
||||||
|
|
||||||
|
|
||||||
|
def register_endpoint(end_point):
|
||||||
|
"""
|
||||||
|
Register an endpoint with the app
|
||||||
|
"""
|
||||||
|
for url, view_func, method in end_point.routes:
|
||||||
|
# Set the view functions
|
||||||
|
route = _route_from_url(end_point.url_prefix, url)
|
||||||
|
application.add_route(route, view_func, method)
|
||||||
|
# Add a static route if necessary
|
||||||
|
if end_point.static_dir:
|
||||||
|
static_route = _route_from_url(end_point.url_prefix, 'static')
|
||||||
|
static_route += '(.*)'
|
||||||
|
application.add_static_route(static_route, end_point.static_dir)
|
||||||
|
|
||||||
|
|
||||||
|
def check_auth(auth):
|
||||||
|
"""
|
||||||
|
This function is called to check if a username password combination is valid.
|
||||||
|
|
||||||
|
:param auth: the authorisation object which needs to be tested
|
||||||
|
:return Whether authentication have been successful
|
||||||
|
"""
|
||||||
|
auth_code = "{user}:{password}".format(user=Settings().value('api/user id'),
|
||||||
|
password=Settings().value('api/password'))
|
||||||
|
try:
|
||||||
|
auth_base = base64.b64encode(auth_code)
|
||||||
|
except TypeError:
|
||||||
|
auth_base = base64.b64encode(auth_code.encode()).decode()
|
||||||
|
if auth[1] == auth_base:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def authenticate():
|
||||||
|
"""
|
||||||
|
Sends a 401 response that enables basic auth to be triggered
|
||||||
|
"""
|
||||||
|
resp = Response(status=401)
|
||||||
|
resp.www_authenticate = 'Basic realm="OpenLP Login Required"'
|
||||||
|
return resp
|
||||||
|
|
||||||
|
|
||||||
|
def requires_auth(f):
|
||||||
|
"""
|
||||||
|
Decorates a function which needs to be authenticated before it can be used from the remote.
|
||||||
|
|
||||||
|
:param f: The function which has been wrapped
|
||||||
|
:return: the called function or a request to authenticate
|
||||||
|
"""
|
||||||
|
@wraps(f)
|
||||||
|
def decorated(*args, **kwargs):
|
||||||
|
if not Settings().value('api/authentication enabled'):
|
||||||
|
return f(*args, **kwargs)
|
||||||
|
req = args[0]
|
||||||
|
if not hasattr(req, 'authorization'):
|
||||||
|
return authenticate()
|
||||||
|
else:
|
||||||
|
auth = req.authorization
|
||||||
|
if auth and check_auth(auth):
|
||||||
|
return f(*args, **kwargs)
|
||||||
|
else:
|
||||||
|
return authenticate()
|
||||||
|
return decorated
|
79
openlp/core/api/http/endpoint.py
Normal file
79
openlp/core/api/http/endpoint.py
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# OpenLP - Open Source Lyrics Projection #
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# Copyright (c) 2008-2017 OpenLP Developers #
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# This program is free software; you can redistribute it and/or modify it #
|
||||||
|
# under the terms of the GNU General Public License as published by the Free #
|
||||||
|
# Software Foundation; version 2 of the License. #
|
||||||
|
# #
|
||||||
|
# This program is distributed in the hope that it will be useful, but WITHOUT #
|
||||||
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
|
||||||
|
# more details. #
|
||||||
|
# #
|
||||||
|
# You should have received a copy of the GNU General Public License along #
|
||||||
|
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||||
|
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||||
|
###############################################################################
|
||||||
|
"""
|
||||||
|
The Endpoint class, which provides plugins with a way to serve their own portion of the API
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from openlp.core.common import AppLocation
|
||||||
|
from mako.template import Template
|
||||||
|
|
||||||
|
|
||||||
|
class Endpoint(object):
|
||||||
|
"""
|
||||||
|
This is an endpoint for the HTTP API
|
||||||
|
"""
|
||||||
|
def __init__(self, url_prefix, template_dir=None, static_dir=None, assets_dir=None):
|
||||||
|
"""
|
||||||
|
Create an endpoint with a URL prefix
|
||||||
|
"""
|
||||||
|
self.url_prefix = url_prefix
|
||||||
|
self.static_dir = static_dir
|
||||||
|
self.template_dir = template_dir
|
||||||
|
if assets_dir:
|
||||||
|
self.assets_dir = assets_dir
|
||||||
|
else:
|
||||||
|
self.assets_dir = None
|
||||||
|
self.routes = []
|
||||||
|
|
||||||
|
def add_url_route(self, url, view_func, method):
|
||||||
|
"""
|
||||||
|
Add a url route to the list of routes
|
||||||
|
"""
|
||||||
|
self.routes.append((url, view_func, method))
|
||||||
|
|
||||||
|
def route(self, rule, method='GET'):
|
||||||
|
"""
|
||||||
|
Set up a URL route
|
||||||
|
"""
|
||||||
|
def decorator(func):
|
||||||
|
"""
|
||||||
|
Make this a decorator
|
||||||
|
"""
|
||||||
|
self.add_url_route(rule, func, method)
|
||||||
|
return func
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
def render_template(self, filename, **kwargs):
|
||||||
|
"""
|
||||||
|
Render a mako template
|
||||||
|
"""
|
||||||
|
root = str(AppLocation.get_section_data_path('remotes'))
|
||||||
|
if not self.template_dir:
|
||||||
|
raise Exception('No template directory specified')
|
||||||
|
path = os.path.join(root, self.template_dir, filename)
|
||||||
|
if self.static_dir:
|
||||||
|
kwargs['static_url'] = '/{prefix}/static'.format(prefix=self.url_prefix)
|
||||||
|
kwargs['static_url'] = kwargs['static_url'].replace('//', '/')
|
||||||
|
kwargs['assets_url'] = '/assets'
|
||||||
|
return Template(filename=path, input_encoding='utf-8').render(**kwargs)
|
65
openlp/core/api/http/errors.py
Normal file
65
openlp/core/api/http/errors.py
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# OpenLP - Open Source Lyrics Projection #
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# Copyright (c) 2008-2017 OpenLP Developers #
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# This program is free software; you can redistribute it and/or modify it #
|
||||||
|
# under the terms of the GNU General Public License as published by the Free #
|
||||||
|
# Software Foundation; version 2 of the License. #
|
||||||
|
# #
|
||||||
|
# This program is distributed in the hope that it will be useful, but WITHOUT #
|
||||||
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
|
||||||
|
# more details. #
|
||||||
|
# #
|
||||||
|
# You should have received a copy of the GNU General Public License along #
|
||||||
|
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||||
|
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||||
|
###############################################################################
|
||||||
|
"""
|
||||||
|
HTTP Error classes
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class HttpError(Exception):
|
||||||
|
"""
|
||||||
|
A base HTTP error (aka status code)
|
||||||
|
"""
|
||||||
|
def __init__(self, status, message):
|
||||||
|
"""
|
||||||
|
Initialise the exception
|
||||||
|
"""
|
||||||
|
super(HttpError, self).__init__(message)
|
||||||
|
self.status = status
|
||||||
|
self.message = message
|
||||||
|
|
||||||
|
def to_response(self):
|
||||||
|
"""
|
||||||
|
Convert this exception to a Response object
|
||||||
|
"""
|
||||||
|
return self.message, self.status
|
||||||
|
|
||||||
|
|
||||||
|
class NotFound(HttpError):
|
||||||
|
"""
|
||||||
|
A 404
|
||||||
|
"""
|
||||||
|
def __init__(self):
|
||||||
|
"""
|
||||||
|
Make this a 404
|
||||||
|
"""
|
||||||
|
super(NotFound, self).__init__(404, 'Not Found')
|
||||||
|
|
||||||
|
|
||||||
|
class ServerError(HttpError):
|
||||||
|
"""
|
||||||
|
A 500
|
||||||
|
"""
|
||||||
|
def __init__(self):
|
||||||
|
"""
|
||||||
|
Make this a 500
|
||||||
|
"""
|
||||||
|
super(ServerError, self).__init__(500, 'Server Error')
|
97
openlp/core/api/http/server.py
Normal file
97
openlp/core/api/http/server.py
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# OpenLP - Open Source Lyrics Projection #
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# Copyright (c) 2008-2017 OpenLP Developers #
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# This program is free software; you can redistribute it and/or modify it #
|
||||||
|
# under the terms of the GNU General Public License as published by the Free #
|
||||||
|
# Software Foundation; version 2 of the License. #
|
||||||
|
# #
|
||||||
|
# This program is distributed in the hope that it will be useful, but WITHOUT #
|
||||||
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
|
||||||
|
# more details. #
|
||||||
|
# #
|
||||||
|
# You should have received a copy of the GNU General Public License along #
|
||||||
|
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||||
|
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
"""
|
||||||
|
The :mod:`http` module contains the API web server. This is a lightweight web server used by remotes to interact
|
||||||
|
with OpenLP. It uses JSON to communicate with the remotes.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from PyQt5 import QtCore
|
||||||
|
from waitress import serve
|
||||||
|
|
||||||
|
from openlp.core.api.http import register_endpoint
|
||||||
|
from openlp.core.api.http import application
|
||||||
|
from openlp.core.common import RegistryMixin, RegistryProperties, OpenLPMixin, Settings, Registry
|
||||||
|
from openlp.core.api.poll import Poller
|
||||||
|
from openlp.core.api.endpoint.controller import controller_endpoint, api_controller_endpoint
|
||||||
|
from openlp.core.api.endpoint.core import chords_endpoint, stage_endpoint, blank_endpoint, main_endpoint
|
||||||
|
from openlp.core.api.endpoint.service import service_endpoint, api_service_endpoint
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class HttpWorker(QtCore.QObject):
|
||||||
|
"""
|
||||||
|
A special Qt thread class to allow the HTTP server to run at the same time as the UI.
|
||||||
|
"""
|
||||||
|
def __init__(self):
|
||||||
|
"""
|
||||||
|
Constructor for the thread class.
|
||||||
|
|
||||||
|
:param server: The http server class.
|
||||||
|
"""
|
||||||
|
super(HttpWorker, self).__init__()
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
"""
|
||||||
|
Run the thread.
|
||||||
|
"""
|
||||||
|
address = Settings().value('api/ip address')
|
||||||
|
port = Settings().value('api/port')
|
||||||
|
serve(application, host=address, port=port)
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class HttpServer(RegistryMixin, RegistryProperties, OpenLPMixin):
|
||||||
|
"""
|
||||||
|
Wrapper round a server instance
|
||||||
|
"""
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
"""
|
||||||
|
Initialise the http server, and start the http server
|
||||||
|
"""
|
||||||
|
super(HttpServer, self).__init__(parent)
|
||||||
|
self.worker = HttpWorker()
|
||||||
|
self.thread = QtCore.QThread()
|
||||||
|
self.worker.moveToThread(self.thread)
|
||||||
|
self.thread.started.connect(self.worker.run)
|
||||||
|
self.thread.start()
|
||||||
|
|
||||||
|
def bootstrap_post_set_up(self):
|
||||||
|
"""
|
||||||
|
Register the poll return service and start the servers.
|
||||||
|
"""
|
||||||
|
self.poller = Poller()
|
||||||
|
Registry().register('poller', self.poller)
|
||||||
|
application.initialise()
|
||||||
|
register_endpoint(controller_endpoint)
|
||||||
|
register_endpoint(api_controller_endpoint)
|
||||||
|
register_endpoint(chords_endpoint)
|
||||||
|
register_endpoint(stage_endpoint)
|
||||||
|
register_endpoint(blank_endpoint)
|
||||||
|
register_endpoint(main_endpoint)
|
||||||
|
register_endpoint(service_endpoint)
|
||||||
|
register_endpoint(api_service_endpoint)
|
185
openlp/core/api/http/wsgiapp.py
Normal file
185
openlp/core/api/http/wsgiapp.py
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||||
|
# pylint: disable=logging-format-interpolation
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# OpenLP - Open Source Lyrics Projection #
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# Copyright (c) 2008-2017 OpenLP Developers #
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# This program is free software; you can redistribute it and/or modify it #
|
||||||
|
# under the terms of the GNU General Public License as published by the Free #
|
||||||
|
# Software Foundation; version 2 of the License. #
|
||||||
|
# #
|
||||||
|
# This program is distributed in the hope that it will be useful, but WITHOUT #
|
||||||
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
|
||||||
|
# more details. #
|
||||||
|
# #
|
||||||
|
# You should have received a copy of the GNU General Public License along #
|
||||||
|
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||||
|
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||||
|
###############################################################################
|
||||||
|
"""
|
||||||
|
App stuff
|
||||||
|
"""
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
|
||||||
|
from webob import Request, Response
|
||||||
|
from webob.static import DirectoryApp
|
||||||
|
|
||||||
|
from openlp.core.common import AppLocation
|
||||||
|
from openlp.core.api.http.errors import HttpError, NotFound, ServerError
|
||||||
|
|
||||||
|
|
||||||
|
ARGS_REGEX = re.compile(r'''\{(\w+)(?::([^}]+))?\}''', re.VERBOSE)
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def _route_to_regex(route):
|
||||||
|
"""
|
||||||
|
Convert a route to a regular expression
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
'songs/{song_id}' becomes 'songs/(?P<song_id>[^/]+)'
|
||||||
|
|
||||||
|
and
|
||||||
|
|
||||||
|
'songs/{song_id:\d+}' becomes 'songs/(?P<song_id>\d+)'
|
||||||
|
|
||||||
|
"""
|
||||||
|
route_regex = ''
|
||||||
|
last_pos = 0
|
||||||
|
for match in ARGS_REGEX.finditer(route):
|
||||||
|
route_regex += re.escape(route[last_pos:match.start()])
|
||||||
|
arg_name = match.group(1)
|
||||||
|
expr = match.group(2) or '[^/]+'
|
||||||
|
expr = '(?P<%s>%s)' % (arg_name, expr)
|
||||||
|
route_regex += expr
|
||||||
|
last_pos = match.end()
|
||||||
|
route_regex += re.escape(route[last_pos:])
|
||||||
|
route_regex = '^%s$' % route_regex
|
||||||
|
return route_regex
|
||||||
|
|
||||||
|
|
||||||
|
def _make_response(view_result):
|
||||||
|
"""
|
||||||
|
Create a Response object from response
|
||||||
|
"""
|
||||||
|
if isinstance(view_result, Response):
|
||||||
|
return view_result
|
||||||
|
elif isinstance(view_result, tuple):
|
||||||
|
content_type = 'text/html'
|
||||||
|
body = view_result[0]
|
||||||
|
if isinstance(body, dict):
|
||||||
|
content_type = 'application/json'
|
||||||
|
body = json.dumps(body)
|
||||||
|
response = Response(body=body, status=view_result[1],
|
||||||
|
content_type=content_type, charset='utf8')
|
||||||
|
if len(view_result) >= 3:
|
||||||
|
response.headers.update(view_result[2])
|
||||||
|
return response
|
||||||
|
elif isinstance(view_result, dict):
|
||||||
|
return Response(body=json.dumps(view_result), status=200,
|
||||||
|
content_type='application/json', charset='utf8')
|
||||||
|
elif isinstance(view_result, str):
|
||||||
|
return Response(body=view_result, status=200,
|
||||||
|
content_type='text/html', charset='utf8')
|
||||||
|
|
||||||
|
|
||||||
|
def _handle_exception(error):
|
||||||
|
"""
|
||||||
|
Handle exceptions
|
||||||
|
"""
|
||||||
|
log.exception(error)
|
||||||
|
if isinstance(error, HttpError):
|
||||||
|
return error.to_response()
|
||||||
|
else:
|
||||||
|
return ServerError().to_response()
|
||||||
|
|
||||||
|
|
||||||
|
class WSGIApplication(object):
|
||||||
|
"""
|
||||||
|
This is the core of the API, the WSGI app
|
||||||
|
"""
|
||||||
|
def __init__(self, name):
|
||||||
|
"""
|
||||||
|
Create the app object
|
||||||
|
"""
|
||||||
|
self.name = name
|
||||||
|
self.static_routes = {}
|
||||||
|
self.route_map = {}
|
||||||
|
|
||||||
|
def initialise(self):
|
||||||
|
"""
|
||||||
|
Set up generic roots for the whole application
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
self.add_static_route('/assets(.*)', '')
|
||||||
|
self.add_static_route('/images(.*)', '')
|
||||||
|
pass
|
||||||
|
|
||||||
|
def add_route(self, route, view_func, method):
|
||||||
|
"""
|
||||||
|
Add a route
|
||||||
|
"""
|
||||||
|
route_regex = _route_to_regex(route)
|
||||||
|
if route_regex not in self.route_map:
|
||||||
|
self.route_map[route_regex] = {}
|
||||||
|
self.route_map[route_regex][method.upper()] = view_func
|
||||||
|
|
||||||
|
def add_static_route(self, route, static_dir):
|
||||||
|
"""
|
||||||
|
Add a static directory as a route
|
||||||
|
"""
|
||||||
|
if route not in self.static_routes:
|
||||||
|
root = str(AppLocation.get_section_data_path('remotes'))
|
||||||
|
static_path = os.path.abspath(os.path.join(root, static_dir))
|
||||||
|
if not os.path.exists(static_path):
|
||||||
|
log.error('Static path "%s" does not exist. Skipping creating static route/', static_path)
|
||||||
|
return
|
||||||
|
self.static_routes[route] = DirectoryApp(static_path)
|
||||||
|
|
||||||
|
def dispatch(self, request):
|
||||||
|
"""
|
||||||
|
Find the appropriate URL and run the view function
|
||||||
|
"""
|
||||||
|
# If not a static route, try the views
|
||||||
|
for route, views in self.route_map.items():
|
||||||
|
match = re.match(route, request.path)
|
||||||
|
if match and request.method.upper() in views:
|
||||||
|
kwargs = match.groupdict()
|
||||||
|
log.debug('Found {method} {url}'.format(method=request.method, url=request.path))
|
||||||
|
view_func = views[request.method.upper()]
|
||||||
|
return _make_response(view_func(request, **kwargs))
|
||||||
|
# Look to see if this is a static file request
|
||||||
|
for route, static_app in self.static_routes.items():
|
||||||
|
if re.match(route, request.path):
|
||||||
|
return request.get_response(static_app)
|
||||||
|
log.error('URL {url} - Not found'.format(url=request.path))
|
||||||
|
raise NotFound()
|
||||||
|
|
||||||
|
def wsgi_app(self, environ, start_response):
|
||||||
|
"""
|
||||||
|
The actual WSGI application.
|
||||||
|
"""
|
||||||
|
request = Request(environ)
|
||||||
|
try:
|
||||||
|
response = self.dispatch(request)
|
||||||
|
except Exception as e:
|
||||||
|
response = _make_response(_handle_exception(e))
|
||||||
|
response.headers.add("cache-control", "no-cache, no-store, must-revalidate")
|
||||||
|
response.headers.add("pragma", "no-cache")
|
||||||
|
response.headers.add("expires", "0")
|
||||||
|
return response(environ, start_response)
|
||||||
|
|
||||||
|
def __call__(self, environ, start_response):
|
||||||
|
"""
|
||||||
|
Shortcut for wsgi_app.
|
||||||
|
"""
|
||||||
|
return self.wsgi_app(environ, start_response)
|
130
openlp/core/api/poll.py
Normal file
130
openlp/core/api/poll.py
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# OpenLP - Open Source Lyrics Projection #
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# Copyright (c) 2008-2017 OpenLP Developers #
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# This program is free software; you can redistribute it and/or modify it #
|
||||||
|
# under the terms of the GNU General Public License as published by the Free #
|
||||||
|
# Software Foundation; version 2 of the License. #
|
||||||
|
# #
|
||||||
|
# This program is distributed in the hope that it will be useful, but WITHOUT #
|
||||||
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
|
||||||
|
# more details. #
|
||||||
|
# #
|
||||||
|
# You should have received a copy of the GNU General Public License along #
|
||||||
|
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||||
|
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
from openlp.core.common import RegistryProperties, Settings
|
||||||
|
from openlp.core.common.httputils import get_web_page
|
||||||
|
|
||||||
|
|
||||||
|
class Poller(RegistryProperties):
|
||||||
|
"""
|
||||||
|
Accessed by the web layer to get status type information from the application
|
||||||
|
"""
|
||||||
|
def __init__(self):
|
||||||
|
"""
|
||||||
|
Constructor for the poll builder class.
|
||||||
|
"""
|
||||||
|
super(Poller, self).__init__()
|
||||||
|
self.live_cache = None
|
||||||
|
self.stage_cache = None
|
||||||
|
self.chords_cache = None
|
||||||
|
|
||||||
|
def raw_poll(self):
|
||||||
|
return {
|
||||||
|
'service': self.service_manager.service_id,
|
||||||
|
'slide': self.live_controller.selected_row or 0,
|
||||||
|
'item': self.live_controller.service_item.unique_identifier if self.live_controller.service_item else '',
|
||||||
|
'twelve': Settings().value('api/twelve hour'),
|
||||||
|
'blank': self.live_controller.blank_screen.isChecked(),
|
||||||
|
'theme': self.live_controller.theme_screen.isChecked(),
|
||||||
|
'display': self.live_controller.desktop_screen.isChecked(),
|
||||||
|
'version': 3,
|
||||||
|
'isSecure': Settings().value('api/authentication enabled'),
|
||||||
|
'isAuthorised': False,
|
||||||
|
'chordNotation': Settings().value('songs/chord notation'),
|
||||||
|
'isStagedActive': self.is_stage_active(),
|
||||||
|
'isLiveActive': self.is_live_active(),
|
||||||
|
'isChordsActive': self.is_chords_active()
|
||||||
|
}
|
||||||
|
|
||||||
|
def poll(self):
|
||||||
|
"""
|
||||||
|
Poll OpenLP to determine the current slide number and item name.
|
||||||
|
"""
|
||||||
|
return {'results': self.raw_poll()}
|
||||||
|
|
||||||
|
def main_poll(self):
|
||||||
|
"""
|
||||||
|
Poll OpenLP to determine the current slide count.
|
||||||
|
"""
|
||||||
|
result = {
|
||||||
|
'slide_count': self.live_controller.slide_count
|
||||||
|
}
|
||||||
|
return json.dumps({'results': result}).encode()
|
||||||
|
|
||||||
|
def reset_cache(self):
|
||||||
|
"""
|
||||||
|
Reset the caches as the web has changed
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
self.stage_cache = None
|
||||||
|
self.live_cache = None
|
||||||
|
self.chords.cache = None
|
||||||
|
|
||||||
|
def is_stage_active(self):
|
||||||
|
"""
|
||||||
|
Is stage active - call it and see but only once
|
||||||
|
:return: if stage is active or not
|
||||||
|
"""
|
||||||
|
if self.stage_cache is None:
|
||||||
|
try:
|
||||||
|
page = get_web_page("http://localhost:4316/stage")
|
||||||
|
except:
|
||||||
|
page = None
|
||||||
|
if page:
|
||||||
|
self.stage_cache = True
|
||||||
|
else:
|
||||||
|
self.stage_cache = False
|
||||||
|
return self.stage_cache
|
||||||
|
|
||||||
|
def is_live_active(self):
|
||||||
|
"""
|
||||||
|
Is main active - call it and see but only once
|
||||||
|
:return: if live is active or not
|
||||||
|
"""
|
||||||
|
if self.live_cache is None:
|
||||||
|
try:
|
||||||
|
page = get_web_page("http://localhost:4316/main")
|
||||||
|
except:
|
||||||
|
page = None
|
||||||
|
if page:
|
||||||
|
self.live_cache = True
|
||||||
|
else:
|
||||||
|
self.live_cache = False
|
||||||
|
return self.live_cache
|
||||||
|
|
||||||
|
def is_chords_active(self):
|
||||||
|
"""
|
||||||
|
Is chords active - call it and see but only once
|
||||||
|
:return: if live is active or not
|
||||||
|
"""
|
||||||
|
if self.chords_cache is None:
|
||||||
|
try:
|
||||||
|
page = get_web_page("http://localhost:4316/chords")
|
||||||
|
except:
|
||||||
|
page = None
|
||||||
|
if page:
|
||||||
|
self.chords_cache = True
|
||||||
|
else:
|
||||||
|
self.chords_cache = False
|
||||||
|
return self.chords_cache
|
@ -20,26 +20,28 @@
|
|||||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
import os.path
|
|
||||||
|
|
||||||
from PyQt5 import QtCore, QtGui, QtNetwork, QtWidgets
|
from PyQt5 import QtCore, QtGui, QtNetwork, QtWidgets
|
||||||
|
|
||||||
from openlp.core.common import AppLocation, Settings, translate
|
from openlp.core.common import UiStrings, Registry, Settings, translate
|
||||||
from openlp.core.lib import SettingsTab, build_icon
|
from openlp.core.lib import SettingsTab
|
||||||
|
|
||||||
ZERO_URL = '0.0.0.0'
|
ZERO_URL = '0.0.0.0'
|
||||||
|
|
||||||
|
|
||||||
class RemoteTab(SettingsTab):
|
class ApiTab(SettingsTab):
|
||||||
"""
|
"""
|
||||||
RemoteTab is the Remotes settings tab in the settings dialog.
|
RemoteTab is the Remotes settings tab in the settings dialog.
|
||||||
"""
|
"""
|
||||||
def __init__(self, parent, title, visible_title, icon_path):
|
def __init__(self, parent):
|
||||||
super(RemoteTab, self).__init__(parent, title, visible_title, icon_path)
|
self.icon_path = ':/plugins/plugin_remote.png'
|
||||||
|
advanced_translated = translate('OpenLP.AdvancedTab', 'Advanced')
|
||||||
|
super(ApiTab, self).__init__(parent, 'api', advanced_translated)
|
||||||
|
self.define_main_window_icon()
|
||||||
|
self.generate_icon()
|
||||||
|
|
||||||
def setupUi(self):
|
def setupUi(self):
|
||||||
self.setObjectName('RemoteTab')
|
self.setObjectName('ApiTab')
|
||||||
super(RemoteTab, self).setupUi()
|
super(ApiTab, self).setupUi()
|
||||||
self.server_settings_group_box = QtWidgets.QGroupBox(self.left_column)
|
self.server_settings_group_box = QtWidgets.QGroupBox(self.left_column)
|
||||||
self.server_settings_group_box.setObjectName('server_settings_group_box')
|
self.server_settings_group_box.setObjectName('server_settings_group_box')
|
||||||
self.server_settings_layout = QtWidgets.QFormLayout(self.server_settings_group_box)
|
self.server_settings_layout = QtWidgets.QFormLayout(self.server_settings_group_box)
|
||||||
@ -65,8 +67,7 @@ class RemoteTab(SettingsTab):
|
|||||||
self.http_setting_layout.setObjectName('http_setting_layout')
|
self.http_setting_layout.setObjectName('http_setting_layout')
|
||||||
self.port_label = QtWidgets.QLabel(self.http_settings_group_box)
|
self.port_label = QtWidgets.QLabel(self.http_settings_group_box)
|
||||||
self.port_label.setObjectName('port_label')
|
self.port_label.setObjectName('port_label')
|
||||||
self.port_spin_box = QtWidgets.QSpinBox(self.http_settings_group_box)
|
self.port_spin_box = QtWidgets.QLabel(self.http_settings_group_box)
|
||||||
self.port_spin_box.setMaximum(32767)
|
|
||||||
self.port_spin_box.setObjectName('port_spin_box')
|
self.port_spin_box.setObjectName('port_spin_box')
|
||||||
self.http_setting_layout.addRow(self.port_label, self.port_spin_box)
|
self.http_setting_layout.addRow(self.port_label, self.port_spin_box)
|
||||||
self.remote_url_label = QtWidgets.QLabel(self.http_settings_group_box)
|
self.remote_url_label = QtWidgets.QLabel(self.http_settings_group_box)
|
||||||
@ -111,6 +112,23 @@ class RemoteTab(SettingsTab):
|
|||||||
self.password.setObjectName('password')
|
self.password.setObjectName('password')
|
||||||
self.user_login_layout.addRow(self.password_label, self.password)
|
self.user_login_layout.addRow(self.password_label, self.password)
|
||||||
self.left_layout.addWidget(self.user_login_group_box)
|
self.left_layout.addWidget(self.user_login_group_box)
|
||||||
|
self.update_site_group_box = QtWidgets.QGroupBox(self.left_column)
|
||||||
|
self.update_site_group_box.setCheckable(True)
|
||||||
|
self.update_site_group_box.setChecked(False)
|
||||||
|
self.update_site_group_box.setObjectName('update_site_group_box')
|
||||||
|
self.update_site_layout = QtWidgets.QFormLayout(self.update_site_group_box)
|
||||||
|
self.update_site_layout.setObjectName('update_site_layout')
|
||||||
|
self.current_version_label = QtWidgets.QLabel(self.update_site_group_box)
|
||||||
|
self.current_version_label.setObjectName('current_version_label')
|
||||||
|
self.current_version_value = QtWidgets.QLabel(self.update_site_group_box)
|
||||||
|
self.current_version_value.setObjectName('current_version_value')
|
||||||
|
self.update_site_layout.addRow(self.current_version_label, self.current_version_value)
|
||||||
|
self.master_version_label = QtWidgets.QLabel(self.update_site_group_box)
|
||||||
|
self.master_version_label.setObjectName('master_version_label')
|
||||||
|
self.master_version_value = QtWidgets.QLabel(self.update_site_group_box)
|
||||||
|
self.master_version_value.setObjectName('master_version_value')
|
||||||
|
self.update_site_layout.addRow(self.master_version_label, self.master_version_value)
|
||||||
|
self.left_layout.addWidget(self.update_site_group_box)
|
||||||
self.android_app_group_box = QtWidgets.QGroupBox(self.right_column)
|
self.android_app_group_box = QtWidgets.QGroupBox(self.right_column)
|
||||||
self.android_app_group_box.setObjectName('android_app_group_box')
|
self.android_app_group_box.setObjectName('android_app_group_box')
|
||||||
self.right_layout.addWidget(self.android_app_group_box)
|
self.right_layout.addWidget(self.android_app_group_box)
|
||||||
@ -146,16 +164,34 @@ class RemoteTab(SettingsTab):
|
|||||||
self.twelve_hour_check_box.stateChanged.connect(self.on_twelve_hour_check_box_changed)
|
self.twelve_hour_check_box.stateChanged.connect(self.on_twelve_hour_check_box_changed)
|
||||||
self.thumbnails_check_box.stateChanged.connect(self.on_thumbnails_check_box_changed)
|
self.thumbnails_check_box.stateChanged.connect(self.on_thumbnails_check_box_changed)
|
||||||
self.address_edit.textChanged.connect(self.set_urls)
|
self.address_edit.textChanged.connect(self.set_urls)
|
||||||
self.port_spin_box.valueChanged.connect(self.set_urls)
|
|
||||||
|
def define_main_window_icon(self):
|
||||||
|
"""
|
||||||
|
Define an icon on the main window to show the state of the server
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
self.remote_server_icon = QtWidgets.QLabel(self.main_window.status_bar)
|
||||||
|
size_policy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
|
||||||
|
size_policy.setHorizontalStretch(0)
|
||||||
|
size_policy.setVerticalStretch(0)
|
||||||
|
size_policy.setHeightForWidth(self.remote_server_icon.sizePolicy().hasHeightForWidth())
|
||||||
|
self.remote_server_icon.setSizePolicy(size_policy)
|
||||||
|
self.remote_server_icon.setFrameShadow(QtWidgets.QFrame.Plain)
|
||||||
|
self.remote_server_icon.setLineWidth(1)
|
||||||
|
self.remote_server_icon.setScaledContents(True)
|
||||||
|
self.remote_server_icon.setFixedSize(20, 20)
|
||||||
|
self.remote_server_icon.setObjectName('remote_server_icon')
|
||||||
|
self.main_window.status_bar.insertPermanentWidget(2, self.remote_server_icon)
|
||||||
|
|
||||||
def retranslateUi(self):
|
def retranslateUi(self):
|
||||||
|
self.tab_title_visible = translate('RemotePlugin.RemoteTab', 'Remote Interface')
|
||||||
self.server_settings_group_box.setTitle(translate('RemotePlugin.RemoteTab', 'Server Settings'))
|
self.server_settings_group_box.setTitle(translate('RemotePlugin.RemoteTab', 'Server Settings'))
|
||||||
self.address_label.setText(translate('RemotePlugin.RemoteTab', 'Serve on IP address:'))
|
self.address_label.setText(translate('RemotePlugin.RemoteTab', 'Serve on IP address:'))
|
||||||
self.port_label.setText(translate('RemotePlugin.RemoteTab', 'Port number:'))
|
self.port_label.setText(translate('RemotePlugin.RemoteTab', 'Port number:'))
|
||||||
self.remote_url_label.setText(translate('RemotePlugin.RemoteTab', 'Remote URL:'))
|
self.remote_url_label.setText(translate('RemotePlugin.RemoteTab', 'Remote URL:'))
|
||||||
self.stage_url_label.setText(translate('RemotePlugin.RemoteTab', 'Stage view URL:'))
|
self.stage_url_label.setText(translate('RemotePlugin.RemoteTab', 'Stage view URL:'))
|
||||||
self.chords_url_label.setText(translate('RemotePlugin.RemoteTab', 'Chords view URL:'))
|
|
||||||
self.live_url_label.setText(translate('RemotePlugin.RemoteTab', 'Live view URL:'))
|
self.live_url_label.setText(translate('RemotePlugin.RemoteTab', 'Live view URL:'))
|
||||||
|
self.chords_url_label.setText(translate('RemotePlugin.RemoteTab', 'Chords view URL:'))
|
||||||
self.twelve_hour_check_box.setText(translate('RemotePlugin.RemoteTab', 'Display stage time in 12h format'))
|
self.twelve_hour_check_box.setText(translate('RemotePlugin.RemoteTab', 'Display stage time in 12h format'))
|
||||||
self.thumbnails_check_box.setText(translate('RemotePlugin.RemoteTab',
|
self.thumbnails_check_box.setText(translate('RemotePlugin.RemoteTab',
|
||||||
'Show thumbnails of non-text slides in remote and stage view.'))
|
'Show thumbnails of non-text slides in remote and stage view.'))
|
||||||
@ -170,22 +206,27 @@ class RemoteTab(SettingsTab):
|
|||||||
'Scan the QR code or click <a href="{qr}">download</a> to install the iOS app from the App '
|
'Scan the QR code or click <a href="{qr}">download</a> to install the iOS app from the App '
|
||||||
'Store.').format(qr='https://itunes.apple.com/app/id1096218725'))
|
'Store.').format(qr='https://itunes.apple.com/app/id1096218725'))
|
||||||
self.user_login_group_box.setTitle(translate('RemotePlugin.RemoteTab', 'User Authentication'))
|
self.user_login_group_box.setTitle(translate('RemotePlugin.RemoteTab', 'User Authentication'))
|
||||||
|
self.aa = UiStrings()
|
||||||
|
self.update_site_group_box.setTitle(UiStrings().WebDownloadText)
|
||||||
self.user_id_label.setText(translate('RemotePlugin.RemoteTab', 'User id:'))
|
self.user_id_label.setText(translate('RemotePlugin.RemoteTab', 'User id:'))
|
||||||
self.password_label.setText(translate('RemotePlugin.RemoteTab', 'Password:'))
|
self.password_label.setText(translate('RemotePlugin.RemoteTab', 'Password:'))
|
||||||
|
self.current_version_label.setText(translate('RemotePlugin.RemoteTab', 'Current Version number:'))
|
||||||
|
self.master_version_label.setText(translate('RemotePlugin.RemoteTab', 'Latest Version number:'))
|
||||||
|
|
||||||
def set_urls(self):
|
def set_urls(self):
|
||||||
"""
|
"""
|
||||||
Update the display based on the data input on the screen
|
Update the display based on the data input on the screen
|
||||||
"""
|
"""
|
||||||
ip_address = self.get_ip_address(self.address_edit.text())
|
ip_address = self.get_ip_address(self.address_edit.text())
|
||||||
http_url = 'http://{url}:{text}/'.format(url=ip_address, text=self.port_spin_box.value())
|
http_url = 'http://{url}:{text}/'.format(url=ip_address, text=self.port_spin_box.text())
|
||||||
self.remote_url.setText('<a href="{url}">{url}</a>'.format(url=http_url))
|
self.remote_url.setText('<a href="{url}">{url}</a>'.format(url=http_url))
|
||||||
http_url_temp = http_url + 'stage'
|
http_url_temp = http_url + 'stage'
|
||||||
self.stage_url.setText('<a href="{url}">{url}</a>'.format(url=http_url_temp))
|
self.stage_url.setText('<a href="{url}">{url}</a>'.format(url=http_url_temp))
|
||||||
http_url_temp = http_url + 'main'
|
http_url_temp = http_url + 'main'
|
||||||
self.live_url.setText('<a href="{url}">{url}</a>'.format(url=http_url_temp))
|
self.live_url.setText('<a href="{url}">{url}</a>'.format(url=http_url_temp))
|
||||||
|
|
||||||
def get_ip_address(self, ip_address):
|
@staticmethod
|
||||||
|
def get_ip_address(ip_address):
|
||||||
"""
|
"""
|
||||||
returns the IP address in dependency of the passed 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
|
ip_address == 0.0.0.0: return the IP address of the first valid interface
|
||||||
@ -209,7 +250,7 @@ class RemoteTab(SettingsTab):
|
|||||||
"""
|
"""
|
||||||
Load the configuration and update the server configuration if necessary
|
Load the configuration and update the server configuration if necessary
|
||||||
"""
|
"""
|
||||||
self.port_spin_box.setValue(Settings().value(self.settings_section + '/port'))
|
self.port_spin_box.setText(str(Settings().value(self.settings_section + '/port')))
|
||||||
self.address_edit.setText(Settings().value(self.settings_section + '/ip address'))
|
self.address_edit.setText(Settings().value(self.settings_section + '/ip address'))
|
||||||
self.twelve_hour = Settings().value(self.settings_section + '/twelve hour')
|
self.twelve_hour = Settings().value(self.settings_section + '/twelve hour')
|
||||||
self.twelve_hour_check_box.setChecked(self.twelve_hour)
|
self.twelve_hour_check_box.setChecked(self.twelve_hour)
|
||||||
@ -218,16 +259,18 @@ class RemoteTab(SettingsTab):
|
|||||||
self.user_login_group_box.setChecked(Settings().value(self.settings_section + '/authentication enabled'))
|
self.user_login_group_box.setChecked(Settings().value(self.settings_section + '/authentication enabled'))
|
||||||
self.user_id.setText(Settings().value(self.settings_section + '/user id'))
|
self.user_id.setText(Settings().value(self.settings_section + '/user id'))
|
||||||
self.password.setText(Settings().value(self.settings_section + '/password'))
|
self.password.setText(Settings().value(self.settings_section + '/password'))
|
||||||
|
self.current_version_value.setText(Settings().value('remotes/download version'))
|
||||||
|
self.master_version_value.setText(Registry().get_flag('website_version'))
|
||||||
|
if self.master_version_value.text() == self.current_version_value.text():
|
||||||
|
self.update_site_group_box.setEnabled(False)
|
||||||
self.set_urls()
|
self.set_urls()
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
"""
|
"""
|
||||||
Save the configuration and update the server configuration if necessary
|
Save the configuration and update the server configuration if necessary
|
||||||
"""
|
"""
|
||||||
if Settings().value(self.settings_section + '/ip address') != self.address_edit.text() or \
|
if Settings().value(self.settings_section + '/ip address') != self.address_edit.text():
|
||||||
Settings().value(self.settings_section + '/port') != self.port_spin_box.value():
|
|
||||||
self.settings_form.register_post_process('remotes_config_updated')
|
self.settings_form.register_post_process('remotes_config_updated')
|
||||||
Settings().setValue(self.settings_section + '/port', self.port_spin_box.value())
|
|
||||||
Settings().setValue(self.settings_section + '/ip address', self.address_edit.text())
|
Settings().setValue(self.settings_section + '/ip address', self.address_edit.text())
|
||||||
Settings().setValue(self.settings_section + '/twelve hour', self.twelve_hour)
|
Settings().setValue(self.settings_section + '/twelve hour', self.twelve_hour)
|
||||||
Settings().setValue(self.settings_section + '/thumbnails', self.thumbnails)
|
Settings().setValue(self.settings_section + '/thumbnails', self.thumbnails)
|
||||||
@ -235,6 +278,8 @@ class RemoteTab(SettingsTab):
|
|||||||
Settings().setValue(self.settings_section + '/user id', self.user_id.text())
|
Settings().setValue(self.settings_section + '/user id', self.user_id.text())
|
||||||
Settings().setValue(self.settings_section + '/password', self.password.text())
|
Settings().setValue(self.settings_section + '/password', self.password.text())
|
||||||
self.generate_icon()
|
self.generate_icon()
|
||||||
|
if self.update_site_group_box.isChecked():
|
||||||
|
self.settings_form.register_post_process('download_website')
|
||||||
|
|
||||||
def on_twelve_hour_check_box_changed(self, check_state):
|
def on_twelve_hour_check_box_changed(self, check_state):
|
||||||
"""
|
"""
|
142
openlp/core/api/websockets.py
Normal file
142
openlp/core/api/websockets.py
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# OpenLP - Open Source Lyrics Projection #
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# Copyright (c) 2008-2017 OpenLP Developers #
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# This program is free software; you can redistribute it and/or modify it #
|
||||||
|
# under the terms of the GNU General Public License as published by the Free #
|
||||||
|
# Software Foundation; version 2 of the License. #
|
||||||
|
# #
|
||||||
|
# This program is distributed in the hope that it will be useful, but WITHOUT #
|
||||||
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
|
||||||
|
# more details. #
|
||||||
|
# #
|
||||||
|
# You should have received a copy of the GNU General Public License along #
|
||||||
|
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||||
|
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
"""
|
||||||
|
The :mod:`http` module contains the API web server. This is a lightweight web server used by remotes to interact
|
||||||
|
with OpenLP. It uses JSON to communicate with the remotes.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import websockets
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import time
|
||||||
|
|
||||||
|
from PyQt5 import QtCore
|
||||||
|
|
||||||
|
from openlp.core.common import Settings, RegistryProperties, OpenLPMixin, Registry
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class WebSocketWorker(QtCore.QObject):
|
||||||
|
"""
|
||||||
|
A special Qt thread class to allow the WebSockets server to run at the same time as the UI.
|
||||||
|
"""
|
||||||
|
def __init__(self, server):
|
||||||
|
"""
|
||||||
|
Constructor for the thread class.
|
||||||
|
|
||||||
|
:param server: The http server class.
|
||||||
|
"""
|
||||||
|
self.ws_server = server
|
||||||
|
super(WebSocketWorker, self).__init__()
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
"""
|
||||||
|
Run the thread.
|
||||||
|
"""
|
||||||
|
self.ws_server.start_server()
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
self.ws_server.stop = True
|
||||||
|
|
||||||
|
|
||||||
|
class WebSocketServer(RegistryProperties, OpenLPMixin):
|
||||||
|
"""
|
||||||
|
Wrapper round a server instance
|
||||||
|
"""
|
||||||
|
def __init__(self):
|
||||||
|
"""
|
||||||
|
Initialise and start the WebSockets server
|
||||||
|
"""
|
||||||
|
super(WebSocketServer, self).__init__()
|
||||||
|
self.settings_section = 'api'
|
||||||
|
self.worker = WebSocketWorker(self)
|
||||||
|
self.thread = QtCore.QThread()
|
||||||
|
self.worker.moveToThread(self.thread)
|
||||||
|
self.thread.started.connect(self.worker.run)
|
||||||
|
self.thread.start()
|
||||||
|
|
||||||
|
def start_server(self):
|
||||||
|
"""
|
||||||
|
Start the correct server and save the handler
|
||||||
|
"""
|
||||||
|
address = Settings().value(self.settings_section + '/ip address')
|
||||||
|
port = Settings().value(self.settings_section + '/websocket port')
|
||||||
|
self.start_websocket_instance(address, port)
|
||||||
|
# If web socket server start listening
|
||||||
|
if hasattr(self, 'ws_server') and self.ws_server:
|
||||||
|
event_loop = asyncio.new_event_loop()
|
||||||
|
asyncio.set_event_loop(event_loop)
|
||||||
|
event_loop.run_until_complete(self.ws_server)
|
||||||
|
event_loop.run_forever()
|
||||||
|
else:
|
||||||
|
log.debug('Failed to start ws server on port {port}'.format(port=port))
|
||||||
|
|
||||||
|
def start_websocket_instance(self, address, port):
|
||||||
|
"""
|
||||||
|
Start the server
|
||||||
|
|
||||||
|
:param address: The server address
|
||||||
|
:param port: The run port
|
||||||
|
"""
|
||||||
|
loop = 1
|
||||||
|
while loop < 4:
|
||||||
|
try:
|
||||||
|
self.ws_server = websockets.serve(self.handle_websocket, address, port)
|
||||||
|
log.debug("Web Socket Server started for class {address} {port}".format(address=address, port=port))
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
log.error('Failed to start ws server {why}'.format(why=e))
|
||||||
|
loop += 1
|
||||||
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def handle_websocket(request, path):
|
||||||
|
"""
|
||||||
|
Handle web socket requests and return the poll information.
|
||||||
|
Check ever 0.2 seconds to get the latest position and send if changed.
|
||||||
|
Only gets triggered when 1st client attaches
|
||||||
|
|
||||||
|
:param request: request from client
|
||||||
|
:param path: determines the endpoints supported
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
log.debug("web socket handler registered with client")
|
||||||
|
previous_poll = None
|
||||||
|
previous_main_poll = None
|
||||||
|
poller = Registry().get('poller')
|
||||||
|
if path == '/state':
|
||||||
|
while True:
|
||||||
|
current_poll = poller.poll()
|
||||||
|
if current_poll != previous_poll:
|
||||||
|
await request.send(json.dumps(current_poll).encode())
|
||||||
|
previous_poll = current_poll
|
||||||
|
await asyncio.sleep(0.2)
|
||||||
|
elif path == '/live_changed':
|
||||||
|
while True:
|
||||||
|
main_poll = poller.main_poll()
|
||||||
|
if main_poll != previous_main_poll:
|
||||||
|
await request.send(main_poll)
|
||||||
|
previous_main_poll = main_poll
|
||||||
|
await asyncio.sleep(0.2)
|
@ -32,7 +32,6 @@ import sys
|
|||||||
import traceback
|
import traceback
|
||||||
from chardet.universaldetector import UniversalDetector
|
from chardet.universaldetector import UniversalDetector
|
||||||
from ipaddress import IPv4Address, IPv6Address, AddressValueError
|
from ipaddress import IPv4Address, IPv6Address, AddressValueError
|
||||||
from pathlib import Path
|
|
||||||
from shutil import which
|
from shutil import which
|
||||||
from subprocess import check_output, CalledProcessError, STDOUT
|
from subprocess import check_output, CalledProcessError, STDOUT
|
||||||
|
|
||||||
@ -65,17 +64,18 @@ def trace_error_handler(logger):
|
|||||||
|
|
||||||
def check_directory_exists(directory, do_not_log=False):
|
def check_directory_exists(directory, do_not_log=False):
|
||||||
"""
|
"""
|
||||||
Check a theme directory exists and if not create it
|
Check a directory exists and if not create it
|
||||||
|
|
||||||
:param directory: The directory to make sure exists
|
:param openlp.core.common.path.Path directory: The directory to make sure exists
|
||||||
:param do_not_log: To not log anything. This is need for the start up, when the log isn't ready.
|
:param bool do_not_log: To not log anything. This is need for the start up, when the log isn't ready.
|
||||||
|
:rtype: None
|
||||||
"""
|
"""
|
||||||
if not do_not_log:
|
if not do_not_log:
|
||||||
log.debug('check_directory_exists {text}'.format(text=directory))
|
log.debug('check_directory_exists {text}'.format(text=directory))
|
||||||
try:
|
try:
|
||||||
if not os.path.exists(directory):
|
if not directory.exists():
|
||||||
os.makedirs(directory)
|
directory.mkdir(parents=True)
|
||||||
except IOError as e:
|
except IOError:
|
||||||
if not do_not_log:
|
if not do_not_log:
|
||||||
log.exception('failed to check if directory exists or create directory')
|
log.exception('failed to check if directory exists or create directory')
|
||||||
|
|
||||||
@ -85,17 +85,12 @@ def extension_loader(glob_pattern, excluded_files=[]):
|
|||||||
A utility function to find and load OpenLP extensions, such as plugins, presentation and media controllers and
|
A utility function to find and load OpenLP extensions, such as plugins, presentation and media controllers and
|
||||||
importers.
|
importers.
|
||||||
|
|
||||||
:param glob_pattern: A glob pattern used to find the extension(s) to be imported. Should be relative to the
|
: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. openlp/plugins/*/*plugin.py
|
application directory. i.e. plugins/*/*plugin.py
|
||||||
:type glob_pattern: str
|
:param list[str] excluded_files: A list of file names to exclude that the glob pattern may find.
|
||||||
|
|
||||||
:param excluded_files: A list of file names to exclude that the glob pattern may find.
|
|
||||||
:type excluded_files: list of strings
|
|
||||||
|
|
||||||
:return: None
|
|
||||||
:rtype: None
|
:rtype: None
|
||||||
"""
|
"""
|
||||||
app_dir = Path(AppLocation.get_directory(AppLocation.AppDir)).parent
|
app_dir = AppLocation.get_directory(AppLocation.AppDir)
|
||||||
for extension_path in app_dir.glob(glob_pattern):
|
for extension_path in app_dir.glob(glob_pattern):
|
||||||
extension_path = extension_path.relative_to(app_dir)
|
extension_path = extension_path.relative_to(app_dir)
|
||||||
if extension_path.name in excluded_files:
|
if extension_path.name in excluded_files:
|
||||||
@ -106,21 +101,19 @@ def extension_loader(glob_pattern, excluded_files=[]):
|
|||||||
except (ImportError, OSError):
|
except (ImportError, OSError):
|
||||||
# On some platforms importing vlc.py might cause OSError exceptions. (e.g. Mac OS X)
|
# On some platforms importing vlc.py might cause OSError exceptions. (e.g. Mac OS X)
|
||||||
log.warning('Failed to import {module_name} on path {extension_path}'
|
log.warning('Failed to import {module_name} on path {extension_path}'
|
||||||
.format(module_name=module_name, extension_path=str(extension_path)))
|
.format(module_name=module_name, extension_path=extension_path))
|
||||||
|
|
||||||
|
|
||||||
def path_to_module(path):
|
def path_to_module(path):
|
||||||
"""
|
"""
|
||||||
Convert a path to a module name (i.e openlp.core.common)
|
Convert a path to a module name (i.e openlp.core.common)
|
||||||
|
|
||||||
:param path: The path to convert to a module name.
|
:param openlp.core.common.path.Path path: The path to convert to a module name.
|
||||||
:type path: Path
|
|
||||||
|
|
||||||
:return: The module name.
|
:return: The module name.
|
||||||
:rtype: str
|
:rtype: str
|
||||||
"""
|
"""
|
||||||
module_path = path.with_suffix('')
|
module_path = path.with_suffix('')
|
||||||
return '.'.join(module_path.parts)
|
return 'openlp.' + '.'.join(module_path.parts)
|
||||||
|
|
||||||
|
|
||||||
def get_frozen_path(frozen_option, non_frozen_option):
|
def get_frozen_path(frozen_option, non_frozen_option):
|
||||||
@ -378,20 +371,22 @@ def split_filename(path):
|
|||||||
return os.path.split(path)
|
return os.path.split(path)
|
||||||
|
|
||||||
|
|
||||||
def delete_file(file_path_name):
|
def delete_file(file_path):
|
||||||
"""
|
"""
|
||||||
Deletes a file from the system.
|
Deletes a file from the system.
|
||||||
|
|
||||||
:param file_path_name: The file, including path, to delete.
|
:param openlp.core.common.path.Path file_path: The file, including path, to delete.
|
||||||
|
:return: True if the deletion was successful, or the file never existed. False otherwise.
|
||||||
|
:rtype: bool
|
||||||
"""
|
"""
|
||||||
if not file_path_name:
|
if not file_path:
|
||||||
return False
|
return False
|
||||||
try:
|
try:
|
||||||
if os.path.exists(file_path_name):
|
if file_path.exists():
|
||||||
os.remove(file_path_name)
|
file_path.unlink()
|
||||||
return True
|
return True
|
||||||
except (IOError, OSError):
|
except (IOError, OSError):
|
||||||
log.exception("Unable to delete file {text}".format(text=file_path_name))
|
log.exception('Unable to delete file {file_path}'.format(file_path=file_path))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
@ -411,18 +406,19 @@ def get_images_filter():
|
|||||||
return IMAGES_FILTER
|
return IMAGES_FILTER
|
||||||
|
|
||||||
|
|
||||||
def is_not_image_file(file_name):
|
def is_not_image_file(file_path):
|
||||||
"""
|
"""
|
||||||
Validate that the file is not an image file.
|
Validate that the file is not an image file.
|
||||||
|
|
||||||
:param file_name: File name to be checked.
|
:param openlp.core.common.path.Path file_path: The file to be checked.
|
||||||
|
:return: If the file is not an image
|
||||||
|
:rtype: bool
|
||||||
"""
|
"""
|
||||||
if not file_name:
|
if not (file_path and file_path.exists()):
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
formats = [bytes(fmt).decode().lower() for fmt in QtGui.QImageReader.supportedImageFormats()]
|
formats = [bytes(fmt).decode().lower() for fmt in QtGui.QImageReader.supportedImageFormats()]
|
||||||
file_part, file_extension = os.path.splitext(str(file_name))
|
if file_path.suffix[1:].lower() in formats:
|
||||||
if file_extension[1:].lower() in formats and os.path.exists(file_name):
|
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@ -431,10 +427,10 @@ def clean_filename(filename):
|
|||||||
"""
|
"""
|
||||||
Removes invalid characters from the given ``filename``.
|
Removes invalid characters from the given ``filename``.
|
||||||
|
|
||||||
:param filename: The "dirty" file name to clean.
|
:param str filename: The "dirty" file name to clean.
|
||||||
|
:return: The cleaned string
|
||||||
|
:rtype: str
|
||||||
"""
|
"""
|
||||||
if not isinstance(filename, str):
|
|
||||||
filename = str(filename, 'utf-8')
|
|
||||||
return INVALID_FILE_CHARS.sub('_', CONTROL_CHARS.sub('', filename))
|
return INVALID_FILE_CHARS.sub('_', CONTROL_CHARS.sub('', filename))
|
||||||
|
|
||||||
|
|
||||||
@ -442,8 +438,9 @@ def check_binary_exists(program_path):
|
|||||||
"""
|
"""
|
||||||
Function that checks whether a binary exists.
|
Function that checks whether a binary exists.
|
||||||
|
|
||||||
:param program_path: The full path to the binary to check.
|
:param openlp.core.common.path.Path program_path: The full path to the binary to check.
|
||||||
:return: program output to be parsed
|
:return: program output to be parsed
|
||||||
|
:rtype: bytes
|
||||||
"""
|
"""
|
||||||
log.debug('testing program_path: {text}'.format(text=program_path))
|
log.debug('testing program_path: {text}'.format(text=program_path))
|
||||||
try:
|
try:
|
||||||
@ -453,26 +450,27 @@ def check_binary_exists(program_path):
|
|||||||
startupinfo.dwFlags |= STARTF_USESHOWWINDOW
|
startupinfo.dwFlags |= STARTF_USESHOWWINDOW
|
||||||
else:
|
else:
|
||||||
startupinfo = None
|
startupinfo = None
|
||||||
runlog = check_output([program_path, '--help'], stderr=STDOUT, startupinfo=startupinfo)
|
run_log = check_output([str(program_path), '--help'], stderr=STDOUT, startupinfo=startupinfo)
|
||||||
except CalledProcessError as e:
|
except CalledProcessError as e:
|
||||||
runlog = e.output
|
run_log = e.output
|
||||||
except Exception:
|
except Exception:
|
||||||
trace_error_handler(log)
|
trace_error_handler(log)
|
||||||
runlog = ''
|
run_log = ''
|
||||||
log.debug('check_output returned: {text}'.format(text=runlog))
|
log.debug('check_output returned: {text}'.format(text=run_log))
|
||||||
return runlog
|
return run_log
|
||||||
|
|
||||||
|
|
||||||
def get_file_encoding(filename):
|
def get_file_encoding(file_path):
|
||||||
"""
|
"""
|
||||||
Utility function to incrementally detect the file encoding.
|
Utility function to incrementally detect the file encoding.
|
||||||
|
|
||||||
:param filename: Filename for the file to determine the encoding for. Str
|
:param openlp.core.common.path.Path file_path: Filename for the file to determine the encoding for.
|
||||||
:return: A dict with the keys 'encoding' and 'confidence'
|
:return: A dict with the keys 'encoding' and 'confidence'
|
||||||
|
:rtype: dict[str, float]
|
||||||
"""
|
"""
|
||||||
detector = UniversalDetector()
|
detector = UniversalDetector()
|
||||||
try:
|
try:
|
||||||
with open(filename, 'rb') as detect_file:
|
with file_path.open('rb') as detect_file:
|
||||||
while not detector.done:
|
while not detector.done:
|
||||||
chunk = detect_file.read(1024)
|
chunk = detect_file.read(1024)
|
||||||
if not chunk:
|
if not chunk:
|
||||||
|
@ -27,6 +27,7 @@ import os
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
from openlp.core.common import Settings, is_win, is_macosx
|
from openlp.core.common import Settings, is_win, is_macosx
|
||||||
|
from openlp.core.common.path import Path
|
||||||
|
|
||||||
|
|
||||||
if not is_win() and not is_macosx():
|
if not is_win() and not is_macosx():
|
||||||
@ -42,6 +43,9 @@ from openlp.core.common import check_directory_exists, get_frozen_path
|
|||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
FROZEN_APP_PATH = Path(sys.argv[0]).parent
|
||||||
|
APP_PATH = Path(openlp.__file__).parent
|
||||||
|
|
||||||
|
|
||||||
class AppLocation(object):
|
class AppLocation(object):
|
||||||
"""
|
"""
|
||||||
@ -54,29 +58,21 @@ class AppLocation(object):
|
|||||||
CacheDir = 5
|
CacheDir = 5
|
||||||
LanguageDir = 6
|
LanguageDir = 6
|
||||||
|
|
||||||
# Base path where data/config/cache dir is located
|
|
||||||
BaseDir = None
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_directory(dir_type=AppDir):
|
def get_directory(dir_type=AppDir):
|
||||||
"""
|
"""
|
||||||
Return the appropriate directory according to the directory type.
|
Return the appropriate directory according to the directory type.
|
||||||
|
|
||||||
:param dir_type: The directory type you want, for instance the data directory. Default *AppLocation.AppDir*
|
:param dir_type: The directory type you want, for instance the data directory. Default *AppLocation.AppDir*
|
||||||
|
:return: The requested path
|
||||||
|
:rtype: openlp.core.common.path.Path
|
||||||
"""
|
"""
|
||||||
if dir_type == AppLocation.AppDir:
|
if dir_type == AppLocation.AppDir or dir_type == AppLocation.VersionDir:
|
||||||
return get_frozen_path(os.path.abspath(os.path.dirname(sys.argv[0])), os.path.dirname(openlp.__file__))
|
return get_frozen_path(FROZEN_APP_PATH, APP_PATH)
|
||||||
elif dir_type == AppLocation.PluginsDir:
|
elif dir_type == AppLocation.PluginsDir:
|
||||||
app_path = os.path.abspath(os.path.dirname(sys.argv[0]))
|
return get_frozen_path(FROZEN_APP_PATH, APP_PATH) / 'plugins'
|
||||||
return get_frozen_path(os.path.join(app_path, 'plugins'),
|
|
||||||
os.path.join(os.path.dirname(openlp.__file__), 'plugins'))
|
|
||||||
elif dir_type == AppLocation.VersionDir:
|
|
||||||
return get_frozen_path(os.path.abspath(os.path.dirname(sys.argv[0])), os.path.dirname(openlp.__file__))
|
|
||||||
elif dir_type == AppLocation.LanguageDir:
|
elif dir_type == AppLocation.LanguageDir:
|
||||||
app_path = get_frozen_path(os.path.abspath(os.path.dirname(sys.argv[0])), _get_os_dir_path(dir_type))
|
return get_frozen_path(FROZEN_APP_PATH, _get_os_dir_path(dir_type)) / 'i18n'
|
||||||
return os.path.join(app_path, 'i18n')
|
|
||||||
elif dir_type == AppLocation.DataDir and AppLocation.BaseDir:
|
|
||||||
return os.path.join(AppLocation.BaseDir, 'data')
|
|
||||||
else:
|
else:
|
||||||
return _get_os_dir_path(dir_type)
|
return _get_os_dir_path(dir_type)
|
||||||
|
|
||||||
@ -84,6 +80,9 @@ class AppLocation(object):
|
|||||||
def get_data_path():
|
def get_data_path():
|
||||||
"""
|
"""
|
||||||
Return the path OpenLP stores all its data under.
|
Return the path OpenLP stores all its data under.
|
||||||
|
|
||||||
|
:return: The data path to use.
|
||||||
|
:rtype: openlp.core.common.path.Path
|
||||||
"""
|
"""
|
||||||
# Check if we have a different data location.
|
# Check if we have a different data location.
|
||||||
if Settings().contains('advanced/data path'):
|
if Settings().contains('advanced/data path'):
|
||||||
@ -91,40 +90,38 @@ class AppLocation(object):
|
|||||||
else:
|
else:
|
||||||
path = AppLocation.get_directory(AppLocation.DataDir)
|
path = AppLocation.get_directory(AppLocation.DataDir)
|
||||||
check_directory_exists(path)
|
check_directory_exists(path)
|
||||||
return os.path.normpath(path)
|
return path
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_files(section=None, extension=None):
|
def get_files(section=None, extension=''):
|
||||||
"""
|
"""
|
||||||
Get a list of files from the data files path.
|
Get a list of files from the data files path.
|
||||||
|
|
||||||
:param section: Defaults to *None*. The section of code getting the files - used to load from a section's
|
:param None | str section: Defaults to *None*. The section of code getting the files - used to load from a
|
||||||
data subdirectory.
|
section's data subdirectory.
|
||||||
:param extension:
|
:param str extension: Defaults to ''. The extension to search for. For example::
|
||||||
Defaults to *None*. The extension to search for. For example::
|
'.png'
|
||||||
|
:return: List of files found.
|
||||||
'.png'
|
:rtype: list[openlp.core.common.path.Path]
|
||||||
"""
|
"""
|
||||||
path = AppLocation.get_data_path()
|
path = AppLocation.get_data_path()
|
||||||
if section:
|
if section:
|
||||||
path = os.path.join(path, section)
|
path = path / section
|
||||||
try:
|
try:
|
||||||
files = os.listdir(path)
|
file_paths = path.glob('*' + extension)
|
||||||
|
return [file_path.relative_to(path) for file_path in file_paths]
|
||||||
except OSError:
|
except OSError:
|
||||||
return []
|
return []
|
||||||
if extension:
|
|
||||||
return [filename for filename in files if extension == os.path.splitext(filename)[1]]
|
|
||||||
else:
|
|
||||||
# no filtering required
|
|
||||||
return files
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_section_data_path(section):
|
def get_section_data_path(section):
|
||||||
"""
|
"""
|
||||||
Return the path a particular module stores its data under.
|
Return the path a particular module stores its data under.
|
||||||
|
|
||||||
|
:param str section:
|
||||||
|
:rtype: openlp.core.common.path.Path
|
||||||
"""
|
"""
|
||||||
data_path = AppLocation.get_data_path()
|
path = AppLocation.get_data_path() / section
|
||||||
path = os.path.join(data_path, section)
|
|
||||||
check_directory_exists(path)
|
check_directory_exists(path)
|
||||||
return path
|
return path
|
||||||
|
|
||||||
@ -132,36 +129,41 @@ class AppLocation(object):
|
|||||||
def _get_os_dir_path(dir_type):
|
def _get_os_dir_path(dir_type):
|
||||||
"""
|
"""
|
||||||
Return a path based on which OS and environment we are running in.
|
Return a path based on which OS and environment we are running in.
|
||||||
|
|
||||||
|
:param dir_type: AppLocation Enum of the requested path type
|
||||||
|
:return: The requested path
|
||||||
|
:rtype: openlp.core.common.path.Path
|
||||||
"""
|
"""
|
||||||
# If running from source, return the language directory from the source directory
|
# If running from source, return the language directory from the source directory
|
||||||
if dir_type == AppLocation.LanguageDir:
|
if dir_type == AppLocation.LanguageDir:
|
||||||
directory = os.path.abspath(os.path.join(os.path.dirname(openlp.__file__), '..', 'resources'))
|
directory = Path(openlp.__file__, '..', '..').resolve() / 'resources'
|
||||||
if os.path.exists(directory):
|
if directory.exists():
|
||||||
return directory
|
return directory
|
||||||
if is_win():
|
if is_win():
|
||||||
|
openlp_folder_path = Path(os.getenv('APPDATA'), 'openlp')
|
||||||
if dir_type == AppLocation.DataDir:
|
if dir_type == AppLocation.DataDir:
|
||||||
return os.path.join(str(os.getenv('APPDATA')), 'openlp', 'data')
|
return openlp_folder_path / 'data'
|
||||||
elif dir_type == AppLocation.LanguageDir:
|
elif dir_type == AppLocation.LanguageDir:
|
||||||
return os.path.dirname(openlp.__file__)
|
return Path(openlp.__file__).parent
|
||||||
return os.path.join(str(os.getenv('APPDATA')), 'openlp')
|
return openlp_folder_path
|
||||||
elif is_macosx():
|
elif is_macosx():
|
||||||
|
openlp_folder_path = Path(os.getenv('HOME'), 'Library', 'Application Support', 'openlp')
|
||||||
if dir_type == AppLocation.DataDir:
|
if dir_type == AppLocation.DataDir:
|
||||||
return os.path.join(str(os.getenv('HOME')), 'Library', 'Application Support', 'openlp', 'Data')
|
return openlp_folder_path / 'Data'
|
||||||
elif dir_type == AppLocation.LanguageDir:
|
elif dir_type == AppLocation.LanguageDir:
|
||||||
return os.path.dirname(openlp.__file__)
|
return Path(openlp.__file__).parent
|
||||||
return os.path.join(str(os.getenv('HOME')), 'Library', 'Application Support', 'openlp')
|
return openlp_folder_path
|
||||||
else:
|
else:
|
||||||
if dir_type == AppLocation.LanguageDir:
|
if dir_type == AppLocation.LanguageDir:
|
||||||
for prefix in ['/usr/local', '/usr']:
|
directory = Path('/usr', 'local', 'share', 'openlp')
|
||||||
directory = os.path.join(prefix, 'share', 'openlp')
|
if directory.exists():
|
||||||
if os.path.exists(directory):
|
return directory
|
||||||
return directory
|
return Path('/usr', 'share', 'openlp')
|
||||||
return os.path.join('/usr', 'share', 'openlp')
|
|
||||||
if XDG_BASE_AVAILABLE:
|
if XDG_BASE_AVAILABLE:
|
||||||
if dir_type == AppLocation.DataDir:
|
if dir_type == AppLocation.DataDir or dir_type == AppLocation.CacheDir:
|
||||||
return os.path.join(str(BaseDirectory.xdg_data_home), 'openlp')
|
return Path(BaseDirectory.xdg_data_home, 'openlp')
|
||||||
elif dir_type == AppLocation.CacheDir:
|
elif dir_type == AppLocation.CacheDir:
|
||||||
return os.path.join(str(BaseDirectory.xdg_cache_home), 'openlp')
|
return Path(BaseDirectory.xdg_cache_home, 'openlp')
|
||||||
if dir_type == AppLocation.DataDir:
|
if dir_type == AppLocation.DataDir:
|
||||||
return os.path.join(str(os.getenv('HOME')), '.openlp', 'data')
|
return Path(os.getenv('HOME'), '.openlp', 'data')
|
||||||
return os.path.join(str(os.getenv('HOME')), '.openlp')
|
return Path(os.getenv('HOME'), '.openlp')
|
||||||
|
@ -25,8 +25,10 @@ The :mod:`openlp.core.utils` module provides the utility libraries for OpenLP.
|
|||||||
import hashlib
|
import hashlib
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
import platform
|
||||||
import socket
|
import socket
|
||||||
import sys
|
import sys
|
||||||
|
import subprocess
|
||||||
import time
|
import time
|
||||||
import urllib.error
|
import urllib.error
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
@ -215,6 +217,7 @@ def url_get_file(callback, url, f_path, sha256=None):
|
|||||||
block_count = 0
|
block_count = 0
|
||||||
block_size = 4096
|
block_size = 4096
|
||||||
retries = 0
|
retries = 0
|
||||||
|
log.debug("url_get_file: " + url)
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
filename = open(f_path, "wb")
|
filename = open(f_path, "wb")
|
||||||
@ -253,4 +256,17 @@ def url_get_file(callback, url, f_path, sha256=None):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def ping(host):
|
||||||
|
"""
|
||||||
|
Returns True if host responds to a ping request
|
||||||
|
"""
|
||||||
|
# Ping parameters as function of OS
|
||||||
|
ping_str = "-n 1" if platform.system().lower() == "windows" else "-c 1"
|
||||||
|
args = "ping " + " " + ping_str + " " + host
|
||||||
|
need_sh = False if platform.system().lower() == "windows" else True
|
||||||
|
|
||||||
|
# Ping
|
||||||
|
return subprocess.call(args, shell=need_sh) == 0
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['get_web_page']
|
__all__ = ['get_web_page']
|
||||||
|
86
openlp/core/common/json.py
Normal file
86
openlp/core/common/json.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-2017 OpenLP Developers #
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# This program is free software; you can redistribute it and/or modify it #
|
||||||
|
# under the terms of the GNU General Public License as published by the Free #
|
||||||
|
# Software Foundation; version 2 of the License. #
|
||||||
|
# #
|
||||||
|
# This program is distributed in the hope that it will be useful, but WITHOUT #
|
||||||
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
|
||||||
|
# more details. #
|
||||||
|
# #
|
||||||
|
# You should have received a copy of the GNU General Public License along #
|
||||||
|
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||||
|
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||||
|
###############################################################################
|
||||||
|
from json import JSONDecoder, JSONEncoder
|
||||||
|
|
||||||
|
from openlp.core.common.path import Path
|
||||||
|
|
||||||
|
|
||||||
|
class OpenLPJsonDecoder(JSONDecoder):
|
||||||
|
"""
|
||||||
|
Implement a custom JSONDecoder to handle Path objects
|
||||||
|
|
||||||
|
Example Usage:
|
||||||
|
object = json.loads(json_string, cls=OpenLPJsonDecoder)
|
||||||
|
"""
|
||||||
|
def __init__(self, object_hook=None, parse_float=None, parse_int=None, parse_constant=None, strict=True,
|
||||||
|
object_pairs_hook=None, **kwargs):
|
||||||
|
"""
|
||||||
|
Re-implement __init__ so that we can pass in our object_hook method. Any additional kwargs, are stored in the
|
||||||
|
instance and are passed to custom objects upon encoding or decoding.
|
||||||
|
"""
|
||||||
|
self.kwargs = kwargs
|
||||||
|
if object_hook is None:
|
||||||
|
object_hook = self.custom_object_hook
|
||||||
|
super().__init__(object_hook, parse_float, parse_int, parse_constant, strict, object_pairs_hook)
|
||||||
|
|
||||||
|
def custom_object_hook(self, obj):
|
||||||
|
"""
|
||||||
|
Implement a custom Path object decoder.
|
||||||
|
|
||||||
|
:param dict obj: A decoded JSON object
|
||||||
|
:return: The original object literal, or a Path object if the object literal contains a key '__Path__'
|
||||||
|
:rtype: dict | openlp.core.common.path.Path
|
||||||
|
"""
|
||||||
|
if '__Path__' in obj:
|
||||||
|
obj = Path.encode_json(obj, **self.kwargs)
|
||||||
|
return obj
|
||||||
|
|
||||||
|
|
||||||
|
class OpenLPJsonEncoder(JSONEncoder):
|
||||||
|
"""
|
||||||
|
Implement a custom JSONEncoder to handle Path objects
|
||||||
|
|
||||||
|
Example Usage:
|
||||||
|
json_string = json.dumps(object, cls=OpenLPJsonEncoder)
|
||||||
|
"""
|
||||||
|
def __init__(self, skipkeys=False, ensure_ascii=True, check_circular=True, allow_nan=True, sort_keys=False,
|
||||||
|
indent=None, separators=None, default=None, **kwargs):
|
||||||
|
"""
|
||||||
|
Re-implement __init__ so that we can pass in additional kwargs, which are stored in the instance and are passed
|
||||||
|
to custom objects upon encoding or decoding.
|
||||||
|
"""
|
||||||
|
self.kwargs = kwargs
|
||||||
|
if default is None:
|
||||||
|
default = self.custom_default
|
||||||
|
super().__init__(skipkeys, ensure_ascii, check_circular, allow_nan, sort_keys, indent, separators, default)
|
||||||
|
|
||||||
|
def custom_default(self, obj):
|
||||||
|
"""
|
||||||
|
Convert any Path objects into a dictionary object which can be serialized.
|
||||||
|
|
||||||
|
:param object obj: The object to convert
|
||||||
|
:return: The serializable object
|
||||||
|
:rtype: dict
|
||||||
|
"""
|
||||||
|
if isinstance(obj, Path):
|
||||||
|
return obj.json_object(**self.kwargs)
|
||||||
|
return super().default(obj)
|
@ -53,7 +53,7 @@ class LanguageManager(object):
|
|||||||
"""
|
"""
|
||||||
if LanguageManager.auto_language:
|
if LanguageManager.auto_language:
|
||||||
language = QtCore.QLocale.system().name()
|
language = QtCore.QLocale.system().name()
|
||||||
lang_path = AppLocation.get_directory(AppLocation.LanguageDir)
|
lang_path = str(AppLocation.get_directory(AppLocation.LanguageDir))
|
||||||
app_translator = QtCore.QTranslator()
|
app_translator = QtCore.QTranslator()
|
||||||
app_translator.load(language, lang_path)
|
app_translator.load(language, lang_path)
|
||||||
# A translator for buttons and other default strings provided by Qt.
|
# A translator for buttons and other default strings provided by Qt.
|
||||||
@ -72,7 +72,7 @@ class LanguageManager(object):
|
|||||||
Find all available language files in this OpenLP install
|
Find all available language files in this OpenLP install
|
||||||
"""
|
"""
|
||||||
log.debug('Translation files: {files}'.format(files=AppLocation.get_directory(AppLocation.LanguageDir)))
|
log.debug('Translation files: {files}'.format(files=AppLocation.get_directory(AppLocation.LanguageDir)))
|
||||||
trans_dir = QtCore.QDir(AppLocation.get_directory(AppLocation.LanguageDir))
|
trans_dir = QtCore.QDir(str(AppLocation.get_directory(AppLocation.LanguageDir)))
|
||||||
file_names = trans_dir.entryList(['*.qm'], QtCore.QDir.Files, QtCore.QDir.Name)
|
file_names = trans_dir.entryList(['*.qm'], QtCore.QDir.Files, QtCore.QDir.Name)
|
||||||
# Remove qm files from the list which start with "qt".
|
# Remove qm files from the list which start with "qt".
|
||||||
file_names = [file_ for file_ in file_names if not file_.startswith('qt')]
|
file_names = [file_ for file_ in file_names if not file_.startswith('qt')]
|
||||||
@ -140,8 +140,8 @@ class LanguageManager(object):
|
|||||||
reg_ex = QtCore.QRegExp("^.*i18n/(.*).qm")
|
reg_ex = QtCore.QRegExp("^.*i18n/(.*).qm")
|
||||||
if reg_ex.exactMatch(qmf):
|
if reg_ex.exactMatch(qmf):
|
||||||
name = '{regex}'.format(regex=reg_ex.cap(1))
|
name = '{regex}'.format(regex=reg_ex.cap(1))
|
||||||
# TODO: Test before converting to python3 string format
|
LanguageManager.__qm_list__[
|
||||||
LanguageManager.__qm_list__['%#2i %s' % (counter + 1, LanguageManager.language_name(qmf))] = name
|
'{count:>2i} {name}'.format(count=counter + 1, name=LanguageManager.language_name(qmf))] = name
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_qm_list():
|
def get_qm_list():
|
||||||
|
100
openlp/core/common/path.py
Normal file
100
openlp/core/common/path.py
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# OpenLP - Open Source Lyrics Projection #
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# Copyright (c) 2008-2017 OpenLP Developers #
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# This program is free software; you can redistribute it and/or modify it #
|
||||||
|
# under the terms of the GNU General Public License as published by the Free #
|
||||||
|
# Software Foundation; version 2 of the License. #
|
||||||
|
# #
|
||||||
|
# This program is distributed in the hope that it will be useful, but WITHOUT #
|
||||||
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
|
||||||
|
# more details. #
|
||||||
|
# #
|
||||||
|
# You should have received a copy of the GNU General Public License along #
|
||||||
|
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||||
|
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||||
|
###############################################################################
|
||||||
|
from contextlib import suppress
|
||||||
|
|
||||||
|
from openlp.core.common import is_win
|
||||||
|
|
||||||
|
if is_win():
|
||||||
|
from pathlib import WindowsPath as PathVariant
|
||||||
|
else:
|
||||||
|
from pathlib import PosixPath as PathVariant
|
||||||
|
|
||||||
|
|
||||||
|
def path_to_str(path=None):
|
||||||
|
"""
|
||||||
|
A utility function to convert a Path object or NoneType to a string equivalent.
|
||||||
|
|
||||||
|
:param openlp.core.common.path.Path | None path: The value to convert to a string
|
||||||
|
:return: An empty string if :param:`path` is None, else a string representation of the :param:`path`
|
||||||
|
:rtype: str
|
||||||
|
"""
|
||||||
|
if not isinstance(path, Path) and path is not None:
|
||||||
|
raise TypeError('parameter \'path\' must be of type Path or NoneType')
|
||||||
|
if path is None:
|
||||||
|
return ''
|
||||||
|
else:
|
||||||
|
return str(path)
|
||||||
|
|
||||||
|
|
||||||
|
def str_to_path(string):
|
||||||
|
"""
|
||||||
|
A utility function to convert a str object to a Path or NoneType.
|
||||||
|
|
||||||
|
This function is of particular use because initating a Path object with an empty string causes the Path object to
|
||||||
|
point to the current working directory.
|
||||||
|
|
||||||
|
:param str string: The string to convert
|
||||||
|
:return: None if :param:`string` is empty, or a Path object representation of :param:`string`
|
||||||
|
:rtype: openlp.core.common.path.Path | None
|
||||||
|
"""
|
||||||
|
if not isinstance(string, str):
|
||||||
|
raise TypeError('parameter \'string\' must be of type str')
|
||||||
|
if string == '':
|
||||||
|
return None
|
||||||
|
return Path(string)
|
||||||
|
|
||||||
|
|
||||||
|
class Path(PathVariant):
|
||||||
|
"""
|
||||||
|
Subclass pathlib.Path, so we can add json conversion methods
|
||||||
|
"""
|
||||||
|
@staticmethod
|
||||||
|
def encode_json(obj, base_path=None, **kwargs):
|
||||||
|
"""
|
||||||
|
Create a Path object from a dictionary representation. The dictionary has been constructed by JSON encoding of
|
||||||
|
a JSON reprensation of a Path object.
|
||||||
|
|
||||||
|
:param dict[str] obj: The dictionary representation
|
||||||
|
:param openlp.core.common.path.Path base_path: If specified, an absolute path to base the relative path off of.
|
||||||
|
:param kwargs: Contains any extra parameters. Not used!
|
||||||
|
:return: The reconstructed Path object
|
||||||
|
:rtype: openlp.core.common.path.Path
|
||||||
|
"""
|
||||||
|
path = Path(*obj['__Path__'])
|
||||||
|
if base_path and not path.is_absolute():
|
||||||
|
return base_path / path
|
||||||
|
return path
|
||||||
|
|
||||||
|
def json_object(self, base_path=None, **kwargs):
|
||||||
|
"""
|
||||||
|
Create a dictionary that can be JSON decoded.
|
||||||
|
|
||||||
|
:param openlp.core.common.path.Path base_path: If specified, an absolute path to make a relative path from.
|
||||||
|
:param kwargs: Contains any extra parameters. Not used!
|
||||||
|
:return: The dictionary representation of this Path object.
|
||||||
|
:rtype: dict[tuple]
|
||||||
|
"""
|
||||||
|
path = self
|
||||||
|
if base_path:
|
||||||
|
with suppress(ValueError):
|
||||||
|
path = path.relative_to(base_path)
|
||||||
|
return {'__Path__': path.parts}
|
@ -24,15 +24,19 @@ This class contains the core default settings.
|
|||||||
"""
|
"""
|
||||||
import datetime
|
import datetime
|
||||||
import logging
|
import logging
|
||||||
|
import json
|
||||||
import os
|
import os
|
||||||
|
from tempfile import gettempdir
|
||||||
|
|
||||||
from PyQt5 import QtCore, QtGui
|
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||||
|
|
||||||
from openlp.core.common import ThemeLevel, SlideLimits, UiStrings, is_win, is_linux
|
|
||||||
|
|
||||||
|
from openlp.core.common import SlideLimits, ThemeLevel, UiStrings, is_linux, is_win, translate
|
||||||
|
from openlp.core.common.json import OpenLPJsonDecoder, OpenLPJsonEncoder
|
||||||
|
from openlp.core.common.path import Path, str_to_path
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
__version__ = 2
|
||||||
|
|
||||||
# Fix for bug #1014422.
|
# Fix for bug #1014422.
|
||||||
X11_BYPASS_DEFAULT = True
|
X11_BYPASS_DEFAULT = True
|
||||||
@ -44,21 +48,6 @@ if is_linux():
|
|||||||
X11_BYPASS_DEFAULT = False
|
X11_BYPASS_DEFAULT = False
|
||||||
|
|
||||||
|
|
||||||
def recent_files_conv(value):
|
|
||||||
"""
|
|
||||||
If the value is not a list convert it to a list
|
|
||||||
:param value: Value to convert
|
|
||||||
:return: value as a List
|
|
||||||
"""
|
|
||||||
if isinstance(value, list):
|
|
||||||
return value
|
|
||||||
elif isinstance(value, str):
|
|
||||||
return [value]
|
|
||||||
elif isinstance(value, bytes):
|
|
||||||
return [value.decode()]
|
|
||||||
return []
|
|
||||||
|
|
||||||
|
|
||||||
def media_players_conv(string):
|
def media_players_conv(string):
|
||||||
"""
|
"""
|
||||||
If phonon is in the setting string replace it with system
|
If phonon is in the setting string replace it with system
|
||||||
@ -73,14 +62,25 @@ def media_players_conv(string):
|
|||||||
return string
|
return string
|
||||||
|
|
||||||
|
|
||||||
|
def file_names_conv(file_names):
|
||||||
|
"""
|
||||||
|
Convert a list of file names in to a list of file paths.
|
||||||
|
|
||||||
|
:param list[str] file_names: The list of file names to convert.
|
||||||
|
:return: The list converted to file paths
|
||||||
|
:rtype: openlp.core.common.path.Path
|
||||||
|
"""
|
||||||
|
if file_names:
|
||||||
|
return [str_to_path(file_name) for file_name in file_names]
|
||||||
|
|
||||||
|
|
||||||
class Settings(QtCore.QSettings):
|
class Settings(QtCore.QSettings):
|
||||||
"""
|
"""
|
||||||
Class to wrap QSettings.
|
Class to wrap QSettings.
|
||||||
|
|
||||||
* Exposes all the methods of QSettings.
|
* Exposes all the methods of QSettings.
|
||||||
* Adds functionality for OpenLP Portable. If the ``defaultFormat`` is set to
|
* Adds functionality for OpenLP Portable. If the ``defaultFormat`` is set to ``IniFormat``, and the path to the Ini
|
||||||
``IniFormat``, and the path to the Ini file is set using ``set_filename``,
|
file is set using ``set_filename``, then the Settings constructor (without any arguments) will create a Settings
|
||||||
then the Settings constructor (without any arguments) will create a Settings
|
|
||||||
object for accessing settings stored in that Ini file.
|
object for accessing settings stored in that Ini file.
|
||||||
|
|
||||||
``__default_settings__``
|
``__default_settings__``
|
||||||
@ -91,7 +91,7 @@ class Settings(QtCore.QSettings):
|
|||||||
|
|
||||||
('general/enable slide loop', 'advanced/slide limits', [(SlideLimits.Wrap, True), (SlideLimits.End, False)])
|
('general/enable slide loop', 'advanced/slide limits', [(SlideLimits.Wrap, True), (SlideLimits.End, False)])
|
||||||
|
|
||||||
The first entry is the *old key*; it will be removed.
|
The first entry is the *old key*; if it is different from the *new key* it will be removed.
|
||||||
|
|
||||||
The second entry is the *new key*; we will add it to the config. If this is just an empty string, we just remove
|
The second entry is the *new key*; we will add it to the config. If this is just an empty string, we just remove
|
||||||
the old key. The last entry is a list containing two-pair tuples. If the list is empty, no conversion is made.
|
the old key. The last entry is a list containing two-pair tuples. If the list is empty, no conversion is made.
|
||||||
@ -105,11 +105,12 @@ class Settings(QtCore.QSettings):
|
|||||||
So, if the type of the old value is bool, then there must be two rules.
|
So, if the type of the old value is bool, then there must be two rules.
|
||||||
"""
|
"""
|
||||||
__default_settings__ = {
|
__default_settings__ = {
|
||||||
|
'settings/version': 0,
|
||||||
'advanced/add page break': False,
|
'advanced/add page break': False,
|
||||||
'advanced/alternate rows': not is_win(),
|
'advanced/alternate rows': not is_win(),
|
||||||
'advanced/autoscrolling': {'dist': 1, 'pos': 0},
|
'advanced/autoscrolling': {'dist': 1, 'pos': 0},
|
||||||
'advanced/current media plugin': -1,
|
'advanced/current media plugin': -1,
|
||||||
'advanced/data path': '',
|
'advanced/data path': None,
|
||||||
# 7 stands for now, 0 to 6 is Monday to Sunday.
|
# 7 stands for now, 0 to 6 is Monday to Sunday.
|
||||||
'advanced/default service day': 7,
|
'advanced/default service day': 7,
|
||||||
'advanced/default service enabled': True,
|
'advanced/default service enabled': True,
|
||||||
@ -121,6 +122,7 @@ class Settings(QtCore.QSettings):
|
|||||||
'advanced/enable exit confirmation': True,
|
'advanced/enable exit confirmation': True,
|
||||||
'advanced/expand service item': False,
|
'advanced/expand service item': False,
|
||||||
'advanced/hide mouse': True,
|
'advanced/hide mouse': True,
|
||||||
|
'advanced/ignore aspect ratio': False,
|
||||||
'advanced/is portable': False,
|
'advanced/is portable': False,
|
||||||
'advanced/max recent files': 20,
|
'advanced/max recent files': 20,
|
||||||
'advanced/print file meta data': False,
|
'advanced/print file meta data': False,
|
||||||
@ -134,7 +136,15 @@ class Settings(QtCore.QSettings):
|
|||||||
'advanced/single click service preview': False,
|
'advanced/single click service preview': False,
|
||||||
'advanced/x11 bypass wm': X11_BYPASS_DEFAULT,
|
'advanced/x11 bypass wm': X11_BYPASS_DEFAULT,
|
||||||
'advanced/search as type': True,
|
'advanced/search as type': True,
|
||||||
'crashreport/last directory': '',
|
'api/twelve hour': True,
|
||||||
|
'api/port': 4316,
|
||||||
|
'api/websocket port': 4317,
|
||||||
|
'api/user id': 'openlp',
|
||||||
|
'api/password': 'password',
|
||||||
|
'api/authentication enabled': False,
|
||||||
|
'api/ip address': '0.0.0.0',
|
||||||
|
'api/thumbnails': True,
|
||||||
|
'crashreport/last directory': None,
|
||||||
'formattingTags/html_tags': '',
|
'formattingTags/html_tags': '',
|
||||||
'core/audio repeat list': False,
|
'core/audio repeat list': False,
|
||||||
'core/auto open': False,
|
'core/auto open': False,
|
||||||
@ -153,7 +163,7 @@ class Settings(QtCore.QSettings):
|
|||||||
'core/screen blank': False,
|
'core/screen blank': False,
|
||||||
'core/show splash': True,
|
'core/show splash': True,
|
||||||
'core/logo background color': '#ffffff',
|
'core/logo background color': '#ffffff',
|
||||||
'core/logo file': ':/graphics/openlp-splash-screen.png',
|
'core/logo file': Path(':/graphics/openlp-splash-screen.png'),
|
||||||
'core/logo hide on startup': False,
|
'core/logo hide on startup': False,
|
||||||
'core/songselect password': '',
|
'core/songselect password': '',
|
||||||
'core/songselect username': '',
|
'core/songselect username': '',
|
||||||
@ -168,17 +178,17 @@ class Settings(QtCore.QSettings):
|
|||||||
'media/players': 'system,webkit',
|
'media/players': 'system,webkit',
|
||||||
'media/override player': QtCore.Qt.Unchecked,
|
'media/override player': QtCore.Qt.Unchecked,
|
||||||
'players/background color': '#000000',
|
'players/background color': '#000000',
|
||||||
'servicemanager/last directory': '',
|
'servicemanager/last directory': None,
|
||||||
'servicemanager/last file': '',
|
'servicemanager/last file': None,
|
||||||
'servicemanager/service theme': '',
|
'servicemanager/service theme': None,
|
||||||
'SettingsImport/file_date_created': datetime.datetime.now().strftime("%Y-%m-%d %H:%M"),
|
'SettingsImport/file_date_created': datetime.datetime.now().strftime("%Y-%m-%d %H:%M"),
|
||||||
'SettingsImport/Make_Changes': 'At_Own_RISK',
|
'SettingsImport/Make_Changes': 'At_Own_RISK',
|
||||||
'SettingsImport/type': 'OpenLP_settings_export',
|
'SettingsImport/type': 'OpenLP_settings_export',
|
||||||
'SettingsImport/version': '',
|
'SettingsImport/version': '',
|
||||||
'themes/global theme': '',
|
'themes/global theme': '',
|
||||||
'themes/last directory': '',
|
'themes/last directory': None,
|
||||||
'themes/last directory export': '',
|
'themes/last directory export': None,
|
||||||
'themes/last directory import': '',
|
'themes/last directory import': None,
|
||||||
'themes/theme level': ThemeLevel.Song,
|
'themes/theme level': ThemeLevel.Song,
|
||||||
'themes/wrap footer': False,
|
'themes/wrap footer': False,
|
||||||
'user interface/live panel': True,
|
'user interface/live panel': True,
|
||||||
@ -199,27 +209,60 @@ class Settings(QtCore.QSettings):
|
|||||||
'projector/db database': '',
|
'projector/db database': '',
|
||||||
'projector/enable': True,
|
'projector/enable': True,
|
||||||
'projector/connect on start': False,
|
'projector/connect on start': False,
|
||||||
'projector/last directory import': '',
|
'projector/last directory import': None,
|
||||||
'projector/last directory export': '',
|
'projector/last directory export': None,
|
||||||
'projector/poll time': 20, # PJLink timeout is 30 seconds
|
'projector/poll time': 20, # PJLink timeout is 30 seconds
|
||||||
'projector/socket timeout': 5, # 5 second socket timeout
|
'projector/socket timeout': 5, # 5 second socket timeout
|
||||||
'projector/source dialog type': 0 # Source select dialog box type
|
'projector/source dialog type': 0 # Source select dialog box type
|
||||||
}
|
}
|
||||||
__file_path__ = ''
|
__file_path__ = ''
|
||||||
__obsolete_settings__ = [
|
__setting_upgrade_1__ = [
|
||||||
# Changed during 2.2.x development.
|
# Changed during 2.2.x development.
|
||||||
# ('advanced/stylesheet fix', '', []),
|
|
||||||
# ('general/recent files', 'core/recent files', [(recent_files_conv, None)]),
|
|
||||||
('songs/search as type', 'advanced/search as type', []),
|
('songs/search as type', 'advanced/search as type', []),
|
||||||
('media/players', 'media/players_temp', [(media_players_conv, None)]), # Convert phonon to system
|
('media/players', 'media/players_temp', [(media_players_conv, None)]), # Convert phonon to system
|
||||||
('media/players_temp', 'media/players', []), # Move temp setting from above to correct setting
|
('media/players_temp', 'media/players', []), # Move temp setting from above to correct setting
|
||||||
('advanced/default color', 'core/logo background color', []), # Default image renamed + moved to general > 2.4.
|
('advanced/default color', 'core/logo background color', []), # Default image renamed + moved to general > 2.4.
|
||||||
('advanced/default image', 'core/logo file', []), # Default image renamed + moved to general after 2.4.
|
('advanced/default image', 'core/logo file', []), # Default image renamed + moved to general after 2.4.
|
||||||
|
('remotes/https enabled', '', []),
|
||||||
|
('remotes/https port', '', []),
|
||||||
|
('remotes/twelve hour', 'api/twelve hour', []),
|
||||||
|
('remotes/port', 'api/port', []),
|
||||||
|
('remotes/websocket port', 'api/websocket port', []),
|
||||||
|
('remotes/user id', 'api/user id', []),
|
||||||
|
('remotes/password', 'api/password', []),
|
||||||
|
('remotes/authentication enabled', 'api/authentication enabled', []),
|
||||||
|
('remotes/ip address', 'api/ip address', []),
|
||||||
|
('remotes/thumbnails', 'api/thumbnails', []),
|
||||||
('shortcuts/escapeItem', 'shortcuts/desktopScreenEnable', []), # Escape item was removed in 2.6.
|
('shortcuts/escapeItem', 'shortcuts/desktopScreenEnable', []), # Escape item was removed in 2.6.
|
||||||
('shortcuts/offlineHelpItem', 'shortcuts/userManualItem', []), # Online and Offline help were combined in 2.6.
|
('shortcuts/offlineHelpItem', 'shortcuts/userManualItem', []), # Online and Offline help were combined in 2.6.
|
||||||
('shortcuts/onlineHelpItem', 'shortcuts/userManualItem', []), # Online and Offline help were combined in 2.6.
|
('shortcuts/onlineHelpItem', 'shortcuts/userManualItem', []), # Online and Offline help were combined in 2.6.
|
||||||
('bibles/advanced bible', '', []), # Common bible search widgets combined in 2.6
|
('bibles/advanced bible', '', []), # Common bible search widgets combined in 2.6
|
||||||
('bibles/quick bible', 'bibles/primary bible', []) # Common bible search widgets combined in 2.6
|
('bibles/quick bible', 'bibles/primary bible', []), # Common bible search widgets combined in 2.6
|
||||||
|
# Last search type was renamed to last used search type in 2.6 since Bible search value type changed in 2.6.
|
||||||
|
('songs/last search type', 'songs/last used search type', []),
|
||||||
|
('bibles/last search type', '', []),
|
||||||
|
('custom/last search type', 'custom/last used search type', [])]
|
||||||
|
|
||||||
|
__setting_upgrade_2__ = [
|
||||||
|
# The following changes are being made for the conversion to using Path objects made in 2.6 development
|
||||||
|
('advanced/data path', 'advanced/data path', [(str_to_path, None)]),
|
||||||
|
('crashreport/last directory', 'crashreport/last directory', [(str_to_path, None)]),
|
||||||
|
('servicemanager/last directory', 'servicemanager/last directory', [(str_to_path, None)]),
|
||||||
|
('servicemanager/last file', 'servicemanager/last file', [(str_to_path, None)]),
|
||||||
|
('themes/last directory', 'themes/last directory', [(str_to_path, None)]),
|
||||||
|
('themes/last directory export', 'themes/last directory export', [(str_to_path, None)]),
|
||||||
|
('themes/last directory import', 'themes/last directory import', [(str_to_path, None)]),
|
||||||
|
('projector/last directory import', 'projector/last directory import', [(str_to_path, None)]),
|
||||||
|
('projector/last directory export', 'projector/last directory export', [(str_to_path, None)]),
|
||||||
|
('bibles/last directory import', 'bibles/last directory import', [(str_to_path, None)]),
|
||||||
|
('presentations/pdf_program', 'presentations/pdf_program', [(str_to_path, None)]),
|
||||||
|
('songs/last directory import', 'songs/last directory import', [(str_to_path, None)]),
|
||||||
|
('songs/last directory export', 'songs/last directory export', [(str_to_path, None)]),
|
||||||
|
('songusage/last directory export', 'songusage/last directory export', [(str_to_path, None)]),
|
||||||
|
('core/recent files', 'core/recent files', [(file_names_conv, None)]),
|
||||||
|
('media/media files', 'media/media files', [(file_names_conv, None)]),
|
||||||
|
('presentations/presentations files', 'presentations/presentations files', [(file_names_conv, None)]),
|
||||||
|
('core/logo file', 'core/logo file', [(str_to_path, None)])
|
||||||
]
|
]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -232,13 +275,16 @@ class Settings(QtCore.QSettings):
|
|||||||
Settings.__default_settings__.update(default_values)
|
Settings.__default_settings__.update(default_values)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def set_filename(ini_file):
|
def set_filename(ini_path):
|
||||||
"""
|
"""
|
||||||
Sets the complete path to an Ini file to be used by Settings objects.
|
Sets the complete path to an Ini file to be used by Settings objects.
|
||||||
|
|
||||||
Does not affect existing Settings objects.
|
Does not affect existing Settings objects.
|
||||||
|
|
||||||
|
:param openlp.core.common.path.Path ini_path: ini file path
|
||||||
|
:rtype: None
|
||||||
"""
|
"""
|
||||||
Settings.__file_path__ = ini_file
|
Settings.__file_path__ = str(ini_path)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def set_up_default_values():
|
def set_up_default_values():
|
||||||
@ -407,14 +453,28 @@ class Settings(QtCore.QSettings):
|
|||||||
key = self.group() + '/' + key
|
key = self.group() + '/' + key
|
||||||
return Settings.__default_settings__[key]
|
return Settings.__default_settings__[key]
|
||||||
|
|
||||||
def remove_obsolete_settings(self):
|
def can_upgrade(self):
|
||||||
|
"""
|
||||||
|
Can / should the settings be upgraded
|
||||||
|
|
||||||
|
:rtype: bool
|
||||||
|
"""
|
||||||
|
return __version__ != self.value('settings/version')
|
||||||
|
|
||||||
|
def upgrade_settings(self):
|
||||||
"""
|
"""
|
||||||
This method is only called to clean up the config. It removes old settings and it renames settings. See
|
This method is only called to clean up the config. It removes old settings and it renames settings. See
|
||||||
``__obsolete_settings__`` for more details.
|
``__obsolete_settings__`` for more details.
|
||||||
"""
|
"""
|
||||||
for old_key, new_key, rules in Settings.__obsolete_settings__:
|
current_version = self.value('settings/version')
|
||||||
# Once removed we don't have to do this again.
|
for version in range(current_version, __version__):
|
||||||
if self.contains(old_key):
|
version += 1
|
||||||
|
upgrade_list = getattr(self, '__setting_upgrade_{version}__'.format(version=version))
|
||||||
|
for old_key, new_key, rules in upgrade_list:
|
||||||
|
# Once removed we don't have to do this again. - Can be removed once fully switched to the versioning
|
||||||
|
# system.
|
||||||
|
if not self.contains(old_key):
|
||||||
|
continue
|
||||||
if new_key:
|
if new_key:
|
||||||
# Get the value of the old_key.
|
# Get the value of the old_key.
|
||||||
old_value = super(Settings, self).value(old_key)
|
old_value = super(Settings, self).value(old_key)
|
||||||
@ -433,14 +493,17 @@ class Settings(QtCore.QSettings):
|
|||||||
old_value = new
|
old_value = new
|
||||||
break
|
break
|
||||||
self.setValue(new_key, old_value)
|
self.setValue(new_key, old_value)
|
||||||
self.remove(old_key)
|
if new_key != old_key:
|
||||||
|
self.remove(old_key)
|
||||||
|
self.setValue('settings/version', version)
|
||||||
|
|
||||||
def value(self, key):
|
def value(self, key):
|
||||||
"""
|
"""
|
||||||
Returns the value for the given ``key``. The returned ``value`` is of the same type as the default value in the
|
Returns the value for the given ``key``. The returned ``value`` is of the same type as the default value in the
|
||||||
*Settings.__default_settings__* dict.
|
*Settings.__default_settings__* dict.
|
||||||
|
|
||||||
:param key: The key to return the value from.
|
:param str key: The key to return the value from.
|
||||||
|
:return: The value stored by the setting.
|
||||||
"""
|
"""
|
||||||
# if group() is not empty the group has not been specified together with the key.
|
# if group() is not empty the group has not been specified together with the key.
|
||||||
if self.group():
|
if self.group():
|
||||||
@ -450,6 +513,18 @@ class Settings(QtCore.QSettings):
|
|||||||
setting = super(Settings, self).value(key, default_value)
|
setting = super(Settings, self).value(key, default_value)
|
||||||
return self._convert_value(setting, default_value)
|
return self._convert_value(setting, default_value)
|
||||||
|
|
||||||
|
def setValue(self, key, value):
|
||||||
|
"""
|
||||||
|
Reimplement the setValue method to handle Path objects.
|
||||||
|
|
||||||
|
:param str key: The key of the setting to save
|
||||||
|
:param value: The value to save
|
||||||
|
:rtype: None
|
||||||
|
"""
|
||||||
|
if isinstance(value, Path) or (isinstance(value, list) and value and isinstance(value[0], Path)):
|
||||||
|
value = json.dumps(value, cls=OpenLPJsonEncoder)
|
||||||
|
super().setValue(key, value)
|
||||||
|
|
||||||
def _convert_value(self, setting, default_value):
|
def _convert_value(self, setting, default_value):
|
||||||
"""
|
"""
|
||||||
This converts the given ``setting`` to the type of the given ``default_value``.
|
This converts the given ``setting`` to the type of the given ``default_value``.
|
||||||
@ -467,8 +542,11 @@ class Settings(QtCore.QSettings):
|
|||||||
if isinstance(default_value, str):
|
if isinstance(default_value, str):
|
||||||
return ''
|
return ''
|
||||||
# An empty list saved to the settings results in a None type being returned.
|
# An empty list saved to the settings results in a None type being returned.
|
||||||
else:
|
elif isinstance(default_value, list):
|
||||||
return []
|
return []
|
||||||
|
elif isinstance(setting, str):
|
||||||
|
if '__Path__' in setting:
|
||||||
|
return json.loads(setting, cls=OpenLPJsonDecoder)
|
||||||
# Convert the setting to the correct type.
|
# Convert the setting to the correct type.
|
||||||
if isinstance(default_value, bool):
|
if isinstance(default_value, bool):
|
||||||
if isinstance(setting, bool):
|
if isinstance(setting, bool):
|
||||||
@ -479,30 +557,58 @@ class Settings(QtCore.QSettings):
|
|||||||
return int(setting)
|
return int(setting)
|
||||||
return setting
|
return setting
|
||||||
|
|
||||||
def get_files_from_config(self, plugin):
|
def export(self, dest_path):
|
||||||
"""
|
"""
|
||||||
This removes the settings needed for old way we saved files (e. g. the image paths for the image plugin). A list
|
Export the settings to file.
|
||||||
of file paths are returned.
|
|
||||||
|
|
||||||
**Note**: Only a list of paths is returned; this does not convert anything!
|
:param openlp.core.common.path.Path dest_path: The file path to create the export file.
|
||||||
|
:return: Success
|
||||||
:param plugin: The Plugin object.The caller has to convert/save the list himself; o
|
:rtype: bool
|
||||||
"""
|
"""
|
||||||
files_list = []
|
temp_path = Path(gettempdir(), 'openlp', 'exportConf.tmp')
|
||||||
# We need QSettings instead of Settings here to bypass our central settings dict.
|
# Delete old files if found.
|
||||||
# Do NOT do this anywhere else!
|
if temp_path.exists():
|
||||||
settings = QtCore.QSettings(self.fileName(), Settings.IniFormat)
|
temp_path.unlink()
|
||||||
settings.beginGroup(plugin.settings_section)
|
if dest_path.exists():
|
||||||
if settings.contains('{name} count'.format(name=plugin.name)):
|
dest_path.unlink()
|
||||||
# Get the count.
|
self.remove('SettingsImport')
|
||||||
list_count = int(settings.value('{name} count'.format(name=plugin.name), 0))
|
# Get the settings.
|
||||||
if list_count:
|
keys = self.allKeys()
|
||||||
for counter in range(list_count):
|
export_settings = QtCore.QSettings(str(temp_path), Settings.IniFormat)
|
||||||
# The keys were named e. g.: "image 0"
|
# Add a header section.
|
||||||
item = settings.value('{name} {counter:d}'.format(name=plugin.name, counter=counter), '')
|
# This is to insure it's our conf file for import.
|
||||||
if item:
|
now = datetime.datetime.now()
|
||||||
files_list.append(item)
|
# Write INI format using QSettings.
|
||||||
settings.remove('{name} {counter:d}'.format(name=plugin.name, counter=counter))
|
# Write our header.
|
||||||
settings.remove('{name} count'.format(name=plugin.name))
|
export_settings.beginGroup('SettingsImport')
|
||||||
settings.endGroup()
|
export_settings.setValue('Make_Changes', 'At_Own_RISK')
|
||||||
return files_list
|
export_settings.setValue('type', 'OpenLP_settings_export')
|
||||||
|
export_settings.setValue('file_date_created', now.strftime("%Y-%m-%d %H:%M"))
|
||||||
|
export_settings.endGroup()
|
||||||
|
# Write all the sections and keys.
|
||||||
|
for section_key in keys:
|
||||||
|
# FIXME: We are conflicting with the standard "General" section.
|
||||||
|
if 'eneral' in section_key:
|
||||||
|
section_key = section_key.lower()
|
||||||
|
key_value = super().value(section_key)
|
||||||
|
if key_value is not None:
|
||||||
|
export_settings.setValue(section_key, key_value)
|
||||||
|
export_settings.sync()
|
||||||
|
# Temp CONF file has been written. Blanks in keys are now '%20'.
|
||||||
|
# Read the temp file and output the user's CONF file with blanks to
|
||||||
|
# make it more readable.
|
||||||
|
try:
|
||||||
|
with dest_path.open('w') as export_conf_file, temp_path.open('r') as temp_conf:
|
||||||
|
for file_record in temp_conf:
|
||||||
|
# Get rid of any invalid entries.
|
||||||
|
if file_record.find('@Invalid()') == -1:
|
||||||
|
file_record = file_record.replace('%20', ' ')
|
||||||
|
export_conf_file.write(file_record)
|
||||||
|
except OSError as ose:
|
||||||
|
QtWidgets.QMessageBox.critical(self, translate('OpenLP.MainWindow', 'Export setting error'),
|
||||||
|
translate('OpenLP.MainWindow',
|
||||||
|
'An error occurred while exporting the settings: {err}'
|
||||||
|
).format(err=ose.strerror),
|
||||||
|
QtWidgets.QMessageBox.StandardButtons(QtWidgets.QMessageBox.Ok))
|
||||||
|
finally:
|
||||||
|
temp_path.unlink()
|
||||||
|
@ -120,9 +120,8 @@ class UiStrings(object):
|
|||||||
self.NISs = translate('OpenLP.Ui', 'No Item Selected', 'Singular')
|
self.NISs = translate('OpenLP.Ui', 'No Item Selected', 'Singular')
|
||||||
self.NISp = translate('OpenLP.Ui', 'No Items Selected', 'Plural')
|
self.NISp = translate('OpenLP.Ui', 'No Items Selected', 'Plural')
|
||||||
self.NoResults = translate('OpenLP.Ui', 'No Search Results')
|
self.NoResults = translate('OpenLP.Ui', 'No Search Results')
|
||||||
self.OLP = translate('OpenLP.Ui', 'OpenLP')
|
self.OpenLP = translate('OpenLP.Ui', 'OpenLP')
|
||||||
self.OLPV2 = "{name} {version}".format(name=self.OLP, version="2")
|
self.OpenLPv2AndUp = translate('OpenLP.Ui', 'OpenLP 2.0 and up')
|
||||||
self.OLPV2x = "{name} {version}".format(name=self.OLP, version="2.4")
|
|
||||||
self.OpenLPStart = translate('OpenLP.Ui', 'OpenLP is already running. Do you wish to continue?')
|
self.OpenLPStart = translate('OpenLP.Ui', 'OpenLP is already running. Do you wish to continue?')
|
||||||
self.OpenService = translate('OpenLP.Ui', 'Open service.')
|
self.OpenService = translate('OpenLP.Ui', 'Open service.')
|
||||||
self.OptionalShowInFooter = translate('OpenLP.Ui', 'Optional, this will be displayed in footer.')
|
self.OptionalShowInFooter = translate('OpenLP.Ui', 'Optional, this will be displayed in footer.')
|
||||||
@ -154,8 +153,7 @@ class UiStrings(object):
|
|||||||
self.Split = translate('OpenLP.Ui', 'Optional &Split')
|
self.Split = translate('OpenLP.Ui', 'Optional &Split')
|
||||||
self.SplitToolTip = translate('OpenLP.Ui',
|
self.SplitToolTip = translate('OpenLP.Ui',
|
||||||
'Split a slide into two only if it does not fit on the screen as one slide.')
|
'Split a slide into two only if it does not fit on the screen as one slide.')
|
||||||
# TODO: WHERE is this used at? cannot find where it's used at in code.
|
self.StartingImport = translate('OpenLP.Ui', 'Starting import...')
|
||||||
self.StartTimeCode = translate('OpenLP.Ui', 'Start {code}')
|
|
||||||
self.StopPlaySlidesInLoop = translate('OpenLP.Ui', 'Stop Play Slides in Loop')
|
self.StopPlaySlidesInLoop = translate('OpenLP.Ui', 'Stop Play Slides in Loop')
|
||||||
self.StopPlaySlidesToEnd = translate('OpenLP.Ui', 'Stop Play Slides to End')
|
self.StopPlaySlidesToEnd = translate('OpenLP.Ui', 'Stop Play Slides to End')
|
||||||
self.Theme = translate('OpenLP.Ui', 'Theme', 'Singular')
|
self.Theme = translate('OpenLP.Ui', 'Theme', 'Singular')
|
||||||
@ -169,6 +167,7 @@ class UiStrings(object):
|
|||||||
self.View = translate('OpenLP.Ui', 'View')
|
self.View = translate('OpenLP.Ui', 'View')
|
||||||
self.ViewMode = translate('OpenLP.Ui', 'View Mode')
|
self.ViewMode = translate('OpenLP.Ui', 'View Mode')
|
||||||
self.Video = translate('OpenLP.Ui', 'Video')
|
self.Video = translate('OpenLP.Ui', 'Video')
|
||||||
|
self.WebDownloadText = translate('OpenLP.Ui', 'Web Interface, Download and Install latest Version')
|
||||||
book_chapter = translate('OpenLP.Ui', 'Book Chapter')
|
book_chapter = translate('OpenLP.Ui', 'Book Chapter')
|
||||||
chapter = translate('OpenLP.Ui', 'Chapter')
|
chapter = translate('OpenLP.Ui', 'Chapter')
|
||||||
verse = translate('OpenLP.Ui', 'Verse')
|
verse = translate('OpenLP.Ui', 'Verse')
|
||||||
|
@ -1,3 +1,27 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# OpenLP - Open Source Lyrics Projection #
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# Copyright (c) 2008-2017 OpenLP Developers #
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# This program is free software; you can redistribute it and/or modify it #
|
||||||
|
# under the terms of the GNU General Public License as published by the Free #
|
||||||
|
# Software Foundation; version 2 of the License. #
|
||||||
|
# #
|
||||||
|
# This program is distributed in the hope that it will be useful, but WITHOUT #
|
||||||
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
|
||||||
|
# more details. #
|
||||||
|
# #
|
||||||
|
# You should have received a copy of the GNU General Public License along #
|
||||||
|
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||||
|
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||||
|
###############################################################################
|
||||||
|
"""
|
||||||
|
The :mod:`openlp.core.common` module downloads the version details for OpenLP.
|
||||||
|
"""
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
@ -12,7 +36,8 @@ from subprocess import Popen, PIPE
|
|||||||
|
|
||||||
from PyQt5 import QtCore
|
from PyQt5 import QtCore
|
||||||
|
|
||||||
from openlp.core.common import AppLocation, Settings
|
from openlp.core.common import AppLocation, Registry, Settings
|
||||||
|
from openlp.core.common.httputils import ping
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -42,12 +67,18 @@ class VersionThread(QtCore.QThread):
|
|||||||
"""
|
"""
|
||||||
self.sleep(1)
|
self.sleep(1)
|
||||||
log.debug('Version thread - run')
|
log.debug('Version thread - run')
|
||||||
app_version = get_application_version()
|
found = ping("openlp.io")
|
||||||
version = check_latest_version(app_version)
|
Registry().set_flag('internet_present', found)
|
||||||
log.debug("Versions {version1} and {version2} ".format(version1=LooseVersion(str(version)),
|
update_check = Settings().value('core/update check')
|
||||||
version2=LooseVersion(str(app_version['full']))))
|
if found:
|
||||||
if LooseVersion(str(version)) > LooseVersion(str(app_version['full'])):
|
Registry().execute('get_website_version')
|
||||||
self.main_window.openlp_version_check.emit('{version}'.format(version=version))
|
if update_check:
|
||||||
|
app_version = get_application_version()
|
||||||
|
version = check_latest_version(app_version)
|
||||||
|
log.debug("Versions {version1} and {version2} ".format(version1=LooseVersion(str(version)),
|
||||||
|
version2=LooseVersion(str(app_version['full']))))
|
||||||
|
if LooseVersion(str(version)) > LooseVersion(str(app_version['full'])):
|
||||||
|
self.main_window.openlp_version_check.emit('{version}'.format(version=version))
|
||||||
|
|
||||||
|
|
||||||
def get_application_version():
|
def get_application_version():
|
||||||
@ -95,7 +126,7 @@ def get_application_version():
|
|||||||
full_version = '{tag}-bzr{tree}'.format(tag=tag_version.strip(), tree=tree_revision.strip())
|
full_version = '{tag}-bzr{tree}'.format(tag=tag_version.strip(), tree=tree_revision.strip())
|
||||||
else:
|
else:
|
||||||
# We're not running the development version, let's use the file.
|
# We're not running the development version, let's use the file.
|
||||||
file_path = AppLocation.get_directory(AppLocation.VersionDir)
|
file_path = str(AppLocation.get_directory(AppLocation.VersionDir))
|
||||||
file_path = os.path.join(file_path, '.version')
|
file_path = os.path.join(file_path, '.version')
|
||||||
version_file = None
|
version_file = None
|
||||||
try:
|
try:
|
||||||
|
@ -83,30 +83,28 @@ class ServiceItemAction(object):
|
|||||||
Next = 3
|
Next = 3
|
||||||
|
|
||||||
|
|
||||||
def get_text_file_string(text_file):
|
def get_text_file_string(text_file_path):
|
||||||
"""
|
"""
|
||||||
Open a file and return its content as unicode string. If the supplied file name is not a file then the function
|
Open a file and return its content as a string. If the supplied file path is not a file then the function
|
||||||
returns False. If there is an error loading the file or the content can't be decoded then the function will return
|
returns False. If there is an error loading the file or the content can't be decoded then the function will return
|
||||||
None.
|
None.
|
||||||
|
|
||||||
:param text_file: The name of the file.
|
:param openlp.core.common.path.Path text_file_path: The path to the file.
|
||||||
:return: The file as a single string
|
:return: The contents of the file, False if the file does not exist, or None if there is an Error reading or
|
||||||
|
decoding the file.
|
||||||
|
:rtype: str | False | None
|
||||||
"""
|
"""
|
||||||
if not os.path.isfile(text_file):
|
if not text_file_path.is_file():
|
||||||
return False
|
return False
|
||||||
file_handle = None
|
|
||||||
content = None
|
content = None
|
||||||
try:
|
try:
|
||||||
file_handle = open(text_file, 'r', encoding='utf-8')
|
with text_file_path.open('r', encoding='utf-8') as file_handle:
|
||||||
if file_handle.read(3) != '\xEF\xBB\xBF':
|
if file_handle.read(3) != '\xEF\xBB\xBF':
|
||||||
# no BOM was found
|
# no BOM was found
|
||||||
file_handle.seek(0)
|
file_handle.seek(0)
|
||||||
content = file_handle.read()
|
content = file_handle.read()
|
||||||
except (IOError, UnicodeError):
|
except (IOError, UnicodeError):
|
||||||
log.exception('Failed to open text file {text}'.format(text=text_file))
|
log.exception('Failed to open text file {text}'.format(text=text_file_path))
|
||||||
finally:
|
|
||||||
if file_handle:
|
|
||||||
file_handle.close()
|
|
||||||
return content
|
return content
|
||||||
|
|
||||||
|
|
||||||
@ -230,7 +228,7 @@ def validate_thumb(file_path, thumb_path):
|
|||||||
return image_date <= thumb_date
|
return image_date <= thumb_date
|
||||||
|
|
||||||
|
|
||||||
def resize_image(image_path, width, height, background='#000000'):
|
def resize_image(image_path, width, height, background='#000000', ignore_aspect_ratio=False):
|
||||||
"""
|
"""
|
||||||
Resize an image to fit on the current screen.
|
Resize an image to fit on the current screen.
|
||||||
|
|
||||||
@ -247,7 +245,7 @@ def resize_image(image_path, width, height, background='#000000'):
|
|||||||
image_ratio = reader.size().width() / reader.size().height()
|
image_ratio = reader.size().width() / reader.size().height()
|
||||||
resize_ratio = width / height
|
resize_ratio = width / height
|
||||||
# Figure out the size we want to resize the image to (keep aspect ratio).
|
# Figure out the size we want to resize the image to (keep aspect ratio).
|
||||||
if image_ratio == resize_ratio:
|
if image_ratio == resize_ratio or ignore_aspect_ratio:
|
||||||
size = QtCore.QSize(width, height)
|
size = QtCore.QSize(width, height)
|
||||||
elif image_ratio < resize_ratio:
|
elif image_ratio < resize_ratio:
|
||||||
# Use the image's height as reference for the new size.
|
# Use the image's height as reference for the new size.
|
||||||
@ -608,8 +606,36 @@ def create_separated_list(string_list):
|
|||||||
return list_to_string
|
return list_to_string
|
||||||
|
|
||||||
|
|
||||||
|
def replace_params(args, kwargs, params):
|
||||||
|
"""
|
||||||
|
Apply a transformation function to the specified args or kwargs
|
||||||
|
|
||||||
|
:param tuple args: Positional arguments
|
||||||
|
:param dict kwargs: Key Word arguments
|
||||||
|
:param params: A tuple of tuples with the position and the key word to replace.
|
||||||
|
:return: The modified positional and keyword arguments
|
||||||
|
:rtype: tuple[tuple, dict]
|
||||||
|
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
Take a method with the following signature, and assume we which to apply the str function to arg2:
|
||||||
|
def method(arg1=None, arg2=None, arg3=None)
|
||||||
|
|
||||||
|
As arg2 can be specified postitionally as the second argument (1 with a zero index) or as a keyword, the we
|
||||||
|
would call this function as follows:
|
||||||
|
|
||||||
|
replace_params(args, kwargs, ((1, 'arg2', str),))
|
||||||
|
"""
|
||||||
|
args = list(args)
|
||||||
|
for position, key_word, transform in params:
|
||||||
|
if len(args) > position:
|
||||||
|
args[position] = transform(args[position])
|
||||||
|
elif key_word in kwargs:
|
||||||
|
kwargs[key_word] = transform(kwargs[key_word])
|
||||||
|
return tuple(args), kwargs
|
||||||
|
|
||||||
|
|
||||||
from .exceptions import ValidationError
|
from .exceptions import ValidationError
|
||||||
from .filedialog import FileDialog
|
|
||||||
from .screen import ScreenList
|
from .screen import ScreenList
|
||||||
from .formattingtags import FormattingTags
|
from .formattingtags import FormattingTags
|
||||||
from .plugin import PluginStatus, StringContent, Plugin
|
from .plugin import PluginStatus, StringContent, Plugin
|
||||||
@ -621,5 +647,5 @@ from .imagemanager import ImageManager
|
|||||||
from .renderer import Renderer
|
from .renderer import Renderer
|
||||||
from .mediamanageritem import MediaManagerItem
|
from .mediamanageritem import MediaManagerItem
|
||||||
from .projector.db import ProjectorDB, Projector
|
from .projector.db import ProjectorDB, Projector
|
||||||
from .projector.pjlink1 import PJLink
|
from .projector.pjlink import PJLink
|
||||||
from .projector.constants import PJLINK_PORT, ERROR_MSG, ERROR_STRING
|
from .projector.constants import PJLINK_PORT, ERROR_MSG, ERROR_STRING
|
||||||
|
@ -25,12 +25,15 @@ The :mod:`db` module provides the core database functionality for OpenLP
|
|||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
from copy import copy
|
||||||
from urllib.parse import quote_plus as urlquote
|
from urllib.parse import quote_plus as urlquote
|
||||||
|
|
||||||
from sqlalchemy import Table, MetaData, Column, types, create_engine
|
from sqlalchemy import Table, MetaData, Column, types, create_engine
|
||||||
from sqlalchemy.exc import SQLAlchemyError, InvalidRequestError, DBAPIError, OperationalError
|
from sqlalchemy.engine.url import make_url
|
||||||
|
from sqlalchemy.exc import SQLAlchemyError, InvalidRequestError, DBAPIError, OperationalError, ProgrammingError
|
||||||
from sqlalchemy.orm import scoped_session, sessionmaker, mapper
|
from sqlalchemy.orm import scoped_session, sessionmaker, mapper
|
||||||
from sqlalchemy.pool import NullPool
|
from sqlalchemy.pool import NullPool
|
||||||
|
|
||||||
from alembic.migration import MigrationContext
|
from alembic.migration import MigrationContext
|
||||||
from alembic.operations import Operations
|
from alembic.operations import Operations
|
||||||
|
|
||||||
@ -40,6 +43,66 @@ from openlp.core.lib.ui import critical_error_message_box
|
|||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def database_exists(url):
|
||||||
|
"""Check if a database exists.
|
||||||
|
|
||||||
|
:param url: A SQLAlchemy engine URL.
|
||||||
|
|
||||||
|
Performs backend-specific testing to quickly determine if a database
|
||||||
|
exists on the server. ::
|
||||||
|
|
||||||
|
database_exists('postgres://postgres@localhost/name') #=> False
|
||||||
|
create_database('postgres://postgres@localhost/name')
|
||||||
|
database_exists('postgres://postgres@localhost/name') #=> True
|
||||||
|
|
||||||
|
Supports checking against a constructed URL as well. ::
|
||||||
|
|
||||||
|
engine = create_engine('postgres://postgres@localhost/name')
|
||||||
|
database_exists(engine.url) #=> False
|
||||||
|
create_database(engine.url)
|
||||||
|
database_exists(engine.url) #=> True
|
||||||
|
|
||||||
|
Borrowed from SQLAlchemy_Utils (v0.32.14 )since we only need this one function.
|
||||||
|
"""
|
||||||
|
|
||||||
|
url = copy(make_url(url))
|
||||||
|
database = url.database
|
||||||
|
if url.drivername.startswith('postgresql'):
|
||||||
|
url.database = 'template1'
|
||||||
|
else:
|
||||||
|
url.database = None
|
||||||
|
|
||||||
|
engine = create_engine(url)
|
||||||
|
|
||||||
|
if engine.dialect.name == 'postgresql':
|
||||||
|
text = "SELECT 1 FROM pg_database WHERE datname='{db}'".format(db=database)
|
||||||
|
return bool(engine.execute(text).scalar())
|
||||||
|
|
||||||
|
elif engine.dialect.name == 'mysql':
|
||||||
|
text = ("SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA "
|
||||||
|
"WHERE SCHEMA_NAME = '{db}'".format(db=database))
|
||||||
|
return bool(engine.execute(text).scalar())
|
||||||
|
|
||||||
|
elif engine.dialect.name == 'sqlite':
|
||||||
|
if database:
|
||||||
|
return database == ':memory:' or os.path.exists(database)
|
||||||
|
else:
|
||||||
|
# The default SQLAlchemy database is in memory,
|
||||||
|
# and :memory is not required, thus we should support that use-case
|
||||||
|
return True
|
||||||
|
|
||||||
|
else:
|
||||||
|
text = 'SELECT 1'
|
||||||
|
try:
|
||||||
|
url.database = database
|
||||||
|
engine = create_engine(url)
|
||||||
|
engine.execute(text)
|
||||||
|
return True
|
||||||
|
|
||||||
|
except (ProgrammingError, OperationalError):
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def init_db(url, auto_flush=True, auto_commit=False, base=None):
|
def init_db(url, auto_flush=True, auto_commit=False, base=None):
|
||||||
"""
|
"""
|
||||||
Initialise and return the session and metadata for a database
|
Initialise and return the session and metadata for a database
|
||||||
@ -144,6 +207,12 @@ def upgrade_db(url, upgrade):
|
|||||||
:param url: The url of the database to upgrade.
|
:param url: The url of the database to upgrade.
|
||||||
:param upgrade: The python module that contains the upgrade instructions.
|
:param upgrade: The python module that contains the upgrade instructions.
|
||||||
"""
|
"""
|
||||||
|
if not database_exists(url):
|
||||||
|
log.warn("Database {db} doesn't exist - skipping upgrade checks".format(db=url))
|
||||||
|
return (0, 0)
|
||||||
|
|
||||||
|
log.debug('Checking upgrades for DB {db}'.format(db=url))
|
||||||
|
|
||||||
session, metadata = init_db(url)
|
session, metadata = init_db(url)
|
||||||
|
|
||||||
class Metadata(BaseModel):
|
class Metadata(BaseModel):
|
||||||
@ -160,17 +229,15 @@ def upgrade_db(url, upgrade):
|
|||||||
metadata_table.create(checkfirst=True)
|
metadata_table.create(checkfirst=True)
|
||||||
mapper(Metadata, metadata_table)
|
mapper(Metadata, metadata_table)
|
||||||
version_meta = session.query(Metadata).get('version')
|
version_meta = session.query(Metadata).get('version')
|
||||||
if version_meta is None:
|
if version_meta:
|
||||||
# Tables have just been created - fill the version field with the most recent version
|
version = int(version_meta.value)
|
||||||
if session.query(Metadata).get('dbversion'):
|
else:
|
||||||
version = 0
|
# Due to issues with other checks, if the version is not set in the DB then default to 0
|
||||||
else:
|
# and let the upgrade function handle the checks
|
||||||
version = upgrade.__version__
|
version = 0
|
||||||
version_meta = Metadata.populate(key='version', value=version)
|
version_meta = Metadata.populate(key='version', value=version)
|
||||||
session.add(version_meta)
|
session.add(version_meta)
|
||||||
session.commit()
|
session.commit()
|
||||||
else:
|
|
||||||
version = int(version_meta.value)
|
|
||||||
if version > upgrade.__version__:
|
if version > upgrade.__version__:
|
||||||
session.remove()
|
session.remove()
|
||||||
return version, upgrade.__version__
|
return version, upgrade.__version__
|
||||||
@ -207,9 +274,9 @@ def delete_database(plugin_name, db_file_name=None):
|
|||||||
:param db_file_name: The database file name. Defaults to None resulting in the plugin_name being used.
|
:param db_file_name: The database file name. Defaults to None resulting in the plugin_name being used.
|
||||||
"""
|
"""
|
||||||
if db_file_name:
|
if db_file_name:
|
||||||
db_file_path = os.path.join(AppLocation.get_section_data_path(plugin_name), db_file_name)
|
db_file_path = AppLocation.get_section_data_path(plugin_name) / db_file_name
|
||||||
else:
|
else:
|
||||||
db_file_path = os.path.join(AppLocation.get_section_data_path(plugin_name), plugin_name)
|
db_file_path = AppLocation.get_section_data_path(plugin_name) / plugin_name
|
||||||
return delete_file(db_file_path)
|
return delete_file(db_file_path)
|
||||||
|
|
||||||
|
|
||||||
|
@ -24,7 +24,6 @@ The :mod:`~openlp.core.lib.exceptions` module contains custom exceptions
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
# TODO: Test __init__ & __str__
|
|
||||||
class ValidationError(Exception):
|
class ValidationError(Exception):
|
||||||
"""
|
"""
|
||||||
The :class:`~openlp.core.lib.exceptions.ValidationError` exception provides a custom exception for validating
|
The :class:`~openlp.core.lib.exceptions.ValidationError` exception provides a custom exception for validating
|
||||||
|
@ -31,7 +31,7 @@ import queue
|
|||||||
|
|
||||||
from PyQt5 import QtCore
|
from PyQt5 import QtCore
|
||||||
|
|
||||||
from openlp.core.common import Registry
|
from openlp.core.common import Registry, Settings
|
||||||
from openlp.core.lib import ScreenList, resize_image, image_to_byte
|
from openlp.core.lib import ScreenList, resize_image, image_to_byte
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
@ -56,7 +56,7 @@ class ImageThread(QtCore.QThread):
|
|||||||
"""
|
"""
|
||||||
Run the thread.
|
Run the thread.
|
||||||
"""
|
"""
|
||||||
self.image_manager._process()
|
self.image_manager.process()
|
||||||
|
|
||||||
|
|
||||||
class Priority(object):
|
class Priority(object):
|
||||||
@ -110,6 +110,8 @@ class Image(object):
|
|||||||
:param width: The width of the image, defaults to -1 meaning that the screen width will be used.
|
:param width: The width of the image, defaults to -1 meaning that the screen width will be used.
|
||||||
:param height: The height of the image, defaults to -1 meaning that the screen height will be used.
|
:param height: The height of the image, defaults to -1 meaning that the screen height will be used.
|
||||||
"""
|
"""
|
||||||
|
if not os.path.exists(path):
|
||||||
|
raise FileNotFoundError('{path} not found'.format(path=path))
|
||||||
self.path = path
|
self.path = path
|
||||||
self.image = None
|
self.image = None
|
||||||
self.image_bytes = None
|
self.image_bytes = None
|
||||||
@ -119,9 +121,7 @@ class Image(object):
|
|||||||
self.timestamp = 0
|
self.timestamp = 0
|
||||||
self.width = width
|
self.width = width
|
||||||
self.height = height
|
self.height = height
|
||||||
# FIXME: We assume that the path exist. The caller has to take care that it exists!
|
self.timestamp = os.stat(path).st_mtime
|
||||||
if os.path.exists(path):
|
|
||||||
self.timestamp = os.stat(path).st_mtime
|
|
||||||
self.secondary_priority = Image.secondary_priority
|
self.secondary_priority = Image.secondary_priority
|
||||||
Image.secondary_priority += 1
|
Image.secondary_priority += 1
|
||||||
|
|
||||||
@ -235,8 +235,15 @@ class ImageManager(QtCore.QObject):
|
|||||||
def get_image(self, path, source, width=-1, height=-1):
|
def get_image(self, path, source, width=-1, height=-1):
|
||||||
"""
|
"""
|
||||||
Return the ``QImage`` from the cache. If not present wait for the background thread to process it.
|
Return the ``QImage`` from the cache. If not present wait for the background thread to process it.
|
||||||
|
|
||||||
|
:param: path: The image path
|
||||||
|
:param: source: The source of the image
|
||||||
|
:param: background: The image background colour
|
||||||
|
:param: width: The processed image width
|
||||||
|
:param: height: The processed image height
|
||||||
"""
|
"""
|
||||||
log.debug('getImage {path}'.format(path=path))
|
log.debug('get_image {path} {source} {width} {height}'.format(path=path, source=source,
|
||||||
|
width=width, height=height))
|
||||||
image = self._cache[(path, source, width, height)]
|
image = self._cache[(path, source, width, height)]
|
||||||
if image.image is None:
|
if image.image is None:
|
||||||
self._conversion_queue.modify_priority(image, Priority.High)
|
self._conversion_queue.modify_priority(image, Priority.High)
|
||||||
@ -255,8 +262,15 @@ class ImageManager(QtCore.QObject):
|
|||||||
def get_image_bytes(self, path, source, width=-1, height=-1):
|
def get_image_bytes(self, path, source, width=-1, height=-1):
|
||||||
"""
|
"""
|
||||||
Returns the byte string for an image. If not present wait for the background thread to process it.
|
Returns the byte string for an image. If not present wait for the background thread to process it.
|
||||||
|
|
||||||
|
:param: path: The image path
|
||||||
|
:param: source: The source of the image
|
||||||
|
:param: background: The image background colour
|
||||||
|
:param: width: The processed image width
|
||||||
|
:param: height: The processed image height
|
||||||
"""
|
"""
|
||||||
log.debug('get_image_bytes {path}'.format(path=path))
|
log.debug('get_image_bytes {path} {source} {width} {height}'.format(path=path, source=source,
|
||||||
|
width=width, height=height))
|
||||||
image = self._cache[(path, source, width, height)]
|
image = self._cache[(path, source, width, height)]
|
||||||
if image.image_bytes is None:
|
if image.image_bytes is None:
|
||||||
self._conversion_queue.modify_priority(image, Priority.Urgent)
|
self._conversion_queue.modify_priority(image, Priority.Urgent)
|
||||||
@ -270,9 +284,16 @@ class ImageManager(QtCore.QObject):
|
|||||||
def add_image(self, path, source, background, width=-1, height=-1):
|
def add_image(self, path, source, background, width=-1, height=-1):
|
||||||
"""
|
"""
|
||||||
Add image to cache if it is not already there.
|
Add image to cache if it is not already there.
|
||||||
|
|
||||||
|
:param: path: The image path
|
||||||
|
:param: source: The source of the image
|
||||||
|
:param: background: The image background colour
|
||||||
|
:param: width: The processed image width
|
||||||
|
:param: height: The processed image height
|
||||||
"""
|
"""
|
||||||
log.debug('add_image {path}'.format(path=path))
|
log.debug('add_image {path} {source} {width} {height}'.format(path=path, source=source,
|
||||||
if (path, source, width, height) not in self._cache:
|
width=width, height=height))
|
||||||
|
if not (path, source, width, height) in self._cache:
|
||||||
image = Image(path, source, background, width, height)
|
image = Image(path, source, background, width, height)
|
||||||
self._cache[(path, source, width, height)] = image
|
self._cache[(path, source, width, height)] = image
|
||||||
self._conversion_queue.put((image.priority, image.secondary_priority, image))
|
self._conversion_queue.put((image.priority, image.secondary_priority, image))
|
||||||
@ -286,11 +307,11 @@ class ImageManager(QtCore.QObject):
|
|||||||
if not self.image_thread.isRunning():
|
if not self.image_thread.isRunning():
|
||||||
self.image_thread.start()
|
self.image_thread.start()
|
||||||
|
|
||||||
def _process(self):
|
def process(self):
|
||||||
"""
|
"""
|
||||||
Controls the processing called from a ``QtCore.QThread``.
|
Controls the processing called from a ``QtCore.QThread``.
|
||||||
"""
|
"""
|
||||||
log.debug('_process - started')
|
log.debug('process - started')
|
||||||
while not self._conversion_queue.empty() and not self.stop_manager:
|
while not self._conversion_queue.empty() and not self.stop_manager:
|
||||||
self._process_cache()
|
self._process_cache()
|
||||||
log.debug('_process - ended')
|
log.debug('_process - ended')
|
||||||
@ -306,7 +327,8 @@ class ImageManager(QtCore.QObject):
|
|||||||
# Let's see if the image was requested with specific dimensions
|
# Let's see if the image was requested with specific dimensions
|
||||||
width = self.width if image.width == -1 else image.width
|
width = self.width if image.width == -1 else image.width
|
||||||
height = self.height if image.height == -1 else image.height
|
height = self.height if image.height == -1 else image.height
|
||||||
image.image = resize_image(image.path, width, height, image.background)
|
image.image = resize_image(image.path, width, height, image.background,
|
||||||
|
Settings().value('advanced/ignore aspect ratio'))
|
||||||
# Set the priority to Lowest and stop here as we need to process more important images first.
|
# Set the priority to Lowest and stop here as we need to process more important images first.
|
||||||
if image.priority == Priority.Normal:
|
if image.priority == Priority.Normal:
|
||||||
self._conversion_queue.modify_priority(image, Priority.Lowest)
|
self._conversion_queue.modify_priority(image, Priority.Lowest)
|
||||||
|
@ -26,12 +26,14 @@ import logging
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
from PyQt5 import QtCore, QtWidgets
|
||||||
|
|
||||||
from openlp.core.common import Registry, RegistryProperties, Settings, UiStrings, translate
|
from openlp.core.common import Registry, RegistryProperties, Settings, UiStrings, translate
|
||||||
from openlp.core.lib import FileDialog, ServiceItem, StringContent, ServiceItemContext
|
from openlp.core.common.path import Path, path_to_str, str_to_path
|
||||||
|
from openlp.core.lib import ServiceItem, StringContent, ServiceItemContext
|
||||||
from openlp.core.lib.searchedit import SearchEdit
|
from openlp.core.lib.searchedit import SearchEdit
|
||||||
from openlp.core.lib.ui import create_widget_action, critical_error_message_box
|
from openlp.core.lib.ui import create_widget_action, critical_error_message_box
|
||||||
|
from openlp.core.ui.lib.filedialog import FileDialog
|
||||||
from openlp.core.ui.lib.listwidgetwithdnd import ListWidgetWithDnD
|
from openlp.core.ui.lib.listwidgetwithdnd import ListWidgetWithDnD
|
||||||
from openlp.core.ui.lib.toolbar import OpenLPToolbar
|
from openlp.core.ui.lib.toolbar import OpenLPToolbar
|
||||||
|
|
||||||
@ -309,13 +311,14 @@ class MediaManagerItem(QtWidgets.QWidget, RegistryProperties):
|
|||||||
"""
|
"""
|
||||||
Add a file to the list widget to make it available for showing
|
Add a file to the list widget to make it available for showing
|
||||||
"""
|
"""
|
||||||
files = FileDialog.getOpenFileNames(self, self.on_new_prompt,
|
file_paths, selected_filter = FileDialog.getOpenFileNames(
|
||||||
Settings().value(self.settings_section + '/last directory'),
|
self, self.on_new_prompt,
|
||||||
self.on_new_file_masks)
|
Settings().value(self.settings_section + '/last directory'),
|
||||||
log.info('New files(s) {files}'.format(files=files))
|
self.on_new_file_masks)
|
||||||
if files:
|
log.info('New files(s) {file_paths}'.format(file_paths=file_paths))
|
||||||
|
if file_paths:
|
||||||
self.application.set_busy_cursor()
|
self.application.set_busy_cursor()
|
||||||
self.validate_and_load(files)
|
self.validate_and_load([path_to_str(path) for path in file_paths])
|
||||||
self.application.set_normal_cursor()
|
self.application.set_normal_cursor()
|
||||||
|
|
||||||
def load_file(self, data):
|
def load_file(self, data):
|
||||||
@ -374,9 +377,8 @@ class MediaManagerItem(QtWidgets.QWidget, RegistryProperties):
|
|||||||
self.list_view.clear()
|
self.list_view.clear()
|
||||||
self.load_list(full_list, target_group)
|
self.load_list(full_list, target_group)
|
||||||
last_dir = os.path.split(files[0])[0]
|
last_dir = os.path.split(files[0])[0]
|
||||||
Settings().setValue(self.settings_section + '/last directory', last_dir)
|
Settings().setValue(self.settings_section + '/last directory', Path(last_dir))
|
||||||
Settings().setValue('{section}/{section} files'.format(section=self.settings_section),
|
Settings().setValue('{section}/{section} files'.format(section=self.settings_section), self.get_file_list())
|
||||||
self.get_file_list())
|
|
||||||
if duplicates_found:
|
if duplicates_found:
|
||||||
critical_error_message_box(UiStrings().Duplicate,
|
critical_error_message_box(UiStrings().Duplicate,
|
||||||
translate('OpenLP.MediaManagerItem',
|
translate('OpenLP.MediaManagerItem',
|
||||||
@ -397,13 +399,15 @@ class MediaManagerItem(QtWidgets.QWidget, RegistryProperties):
|
|||||||
def get_file_list(self):
|
def get_file_list(self):
|
||||||
"""
|
"""
|
||||||
Return the current list of files
|
Return the current list of files
|
||||||
|
|
||||||
|
:rtype: list[openlp.core.common.path.Path]
|
||||||
"""
|
"""
|
||||||
file_list = []
|
file_paths = []
|
||||||
for index in range(self.list_view.count()):
|
for index in range(self.list_view.count()):
|
||||||
list_item = self.list_view.item(index)
|
list_item = self.list_view.item(index)
|
||||||
filename = list_item.data(QtCore.Qt.UserRole)
|
filename = list_item.data(QtCore.Qt.UserRole)
|
||||||
file_list.append(filename)
|
file_paths.append(str_to_path(filename))
|
||||||
return file_list
|
return file_paths
|
||||||
|
|
||||||
def load_list(self, load_list, target_group):
|
def load_list(self, load_list, target_group):
|
||||||
"""
|
"""
|
||||||
|
@ -150,7 +150,7 @@ class Plugin(QtCore.QObject, RegistryProperties):
|
|||||||
self.status = PluginStatus.Inactive
|
self.status = PluginStatus.Inactive
|
||||||
# Add the default status to the default settings.
|
# Add the default status to the default settings.
|
||||||
default_settings[name + '/status'] = PluginStatus.Inactive
|
default_settings[name + '/status'] = PluginStatus.Inactive
|
||||||
default_settings[name + '/last directory'] = ''
|
default_settings[name + '/last directory'] = None
|
||||||
# Append a setting for files in the mediamanager (note not all plugins
|
# Append a setting for files in the mediamanager (note not all plugins
|
||||||
# which have a mediamanager need this).
|
# which have a mediamanager need this).
|
||||||
if media_item_class is not None:
|
if media_item_class is not None:
|
||||||
|
@ -40,7 +40,7 @@ class PluginManager(RegistryMixin, OpenLPMixin, RegistryProperties):
|
|||||||
"""
|
"""
|
||||||
super(PluginManager, self).__init__(parent)
|
super(PluginManager, self).__init__(parent)
|
||||||
self.log_info('Plugin manager Initialising')
|
self.log_info('Plugin manager Initialising')
|
||||||
self.base_path = os.path.abspath(AppLocation.get_directory(AppLocation.PluginsDir))
|
self.base_path = os.path.abspath(str(AppLocation.get_directory(AppLocation.PluginsDir)))
|
||||||
self.log_debug('Base path {path}'.format(path=self.base_path))
|
self.log_debug('Base path {path}'.format(path=self.base_path))
|
||||||
self.plugins = []
|
self.plugins = []
|
||||||
self.log_info('Plugin manager Initialised')
|
self.log_info('Plugin manager Initialised')
|
||||||
@ -69,7 +69,7 @@ class PluginManager(RegistryMixin, OpenLPMixin, RegistryProperties):
|
|||||||
"""
|
"""
|
||||||
Scan a directory for objects inheriting from the ``Plugin`` class.
|
Scan a directory for objects inheriting from the ``Plugin`` class.
|
||||||
"""
|
"""
|
||||||
glob_pattern = os.path.join('openlp', 'plugins', '*', '*plugin.py')
|
glob_pattern = os.path.join('plugins', '*', '*plugin.py')
|
||||||
extension_loader(glob_pattern)
|
extension_loader(glob_pattern)
|
||||||
plugin_classes = Plugin.__subclasses__()
|
plugin_classes = Plugin.__subclasses__()
|
||||||
plugin_objects = []
|
plugin_objects = []
|
||||||
|
@ -46,7 +46,7 @@ __all__ = ['S_OK', 'E_GENERAL', 'E_NOT_CONNECTED', 'E_FAN', 'E_LAMP', 'E_TEMP',
|
|||||||
'S_NOT_CONNECTED', 'S_CONNECTING', 'S_CONNECTED',
|
'S_NOT_CONNECTED', 'S_CONNECTING', 'S_CONNECTED',
|
||||||
'S_STATUS', 'S_OFF', 'S_INITIALIZE', 'S_STANDBY', 'S_WARMUP', 'S_ON', 'S_COOLDOWN',
|
'S_STATUS', 'S_OFF', 'S_INITIALIZE', 'S_STANDBY', 'S_WARMUP', 'S_ON', 'S_COOLDOWN',
|
||||||
'S_INFO', 'S_NETWORK_SENDING', 'S_NETWORK_RECEIVED',
|
'S_INFO', 'S_NETWORK_SENDING', 'S_NETWORK_RECEIVED',
|
||||||
'ERROR_STRING', 'CR', 'LF', 'PJLINK_ERST_STATUS', 'PJLINK_POWR_STATUS',
|
'ERROR_STRING', 'CR', 'LF', 'PJLINK_ERST_DATA', 'PJLINK_ERST_STATUS', 'PJLINK_POWR_STATUS',
|
||||||
'PJLINK_PORT', 'PJLINK_MAX_PACKET', 'TIMEOUT', 'ERROR_MSG', 'PJLINK_ERRORS',
|
'PJLINK_PORT', 'PJLINK_MAX_PACKET', 'TIMEOUT', 'ERROR_MSG', 'PJLINK_ERRORS',
|
||||||
'STATUS_STRING', 'PJLINK_VALID_CMD', 'CONNECTION_ERRORS',
|
'STATUS_STRING', 'PJLINK_VALID_CMD', 'CONNECTION_ERRORS',
|
||||||
'PJLINK_DEFAULT_SOURCES', 'PJLINK_DEFAULT_CODES', 'PJLINK_DEFAULT_ITEMS']
|
'PJLINK_DEFAULT_SOURCES', 'PJLINK_DEFAULT_CODES', 'PJLINK_DEFAULT_ITEMS']
|
||||||
@ -57,35 +57,115 @@ LF = chr(0x0A) # \n
|
|||||||
PJLINK_PORT = 4352
|
PJLINK_PORT = 4352
|
||||||
TIMEOUT = 30.0
|
TIMEOUT = 30.0
|
||||||
PJLINK_MAX_PACKET = 136
|
PJLINK_MAX_PACKET = 136
|
||||||
# NOTE: Change format to account for some commands are both class 1 and 2
|
# NOTE: Changed format to account for some commands are both class 1 and 2
|
||||||
PJLINK_VALID_CMD = {
|
PJLINK_VALID_CMD = {
|
||||||
'ACKN': ['2', ], # UDP Reply to 'SRCH'
|
'ACKN': {'version': ['2', ],
|
||||||
'AVMT': ['1', ], # Shutter option
|
'description': translate('OpenLP.PJLinkConstants',
|
||||||
'CLSS': ['1', ], # PJLink class support query
|
'Acknowledge a PJLink SRCH command - returns MAC address.')
|
||||||
'ERST': ['1', '2'], # Error status option
|
},
|
||||||
'FILT': ['2', ], # Get current filter usage time
|
'AVMT': {'version': ['1', ],
|
||||||
'FREZ': ['2', ], # Set freeze/unfreeze picture being projected
|
'description': translate('OpenLP.PJLinkConstants',
|
||||||
'INF1': ['1', ], # Manufacturer name query
|
'Blank/unblank video and/or mute audio.')
|
||||||
'INF2': ['1', ], # Product name query
|
},
|
||||||
'INFO': ['1', ], # Other information query
|
'CLSS': {'version': ['1', ],
|
||||||
'INNM': ['2', ], # Get Video source input terminal name
|
'description': translate('OpenLP.PJLinkConstants',
|
||||||
'INPT': ['1', ], # Video sources option
|
'Query projector PJLink class support.')
|
||||||
'INST': ['1', ], # Input sources available query
|
},
|
||||||
'IRES': ['2', ], # Get Video source resolution
|
'ERST': {'version': ['1', '2'],
|
||||||
'LAMP': ['1', ], # Lamp(s) query (Includes fans)
|
'description': translate('OpenLP.PJLinkConstants',
|
||||||
'LKUP': ['2', ], # UPD Linkup status notification
|
'Query error status from projector. '
|
||||||
'MVOL': ['2', ], # Set microphone volume
|
'Returns fan/lamp/temp/cover/filter/other error status.')
|
||||||
'NAME': ['1', ], # Projector name query
|
},
|
||||||
'PJLINK': ['1', ], # Initial connection
|
'FILT': {'version': ['2', ],
|
||||||
'POWR': ['1', ], # Power option
|
'description': translate('OpenLP.PJLinkConstants',
|
||||||
'RFIL': ['2', ], # Get replacement air filter model number
|
'Query number of hours on filter.')
|
||||||
'RLMP': ['2', ], # Get lamp replacement model number
|
},
|
||||||
'RRES': ['2', ], # Get projector recommended video resolution
|
'FREZ': {'version': ['2', ],
|
||||||
'SNUM': ['2', ], # Get projector serial number
|
'description': translate('OpenLP.PJLinkConstants',
|
||||||
'SRCH': ['2', ], # UDP broadcast search for available projectors on local network
|
'Freeze or unfreeze current image being projected.')
|
||||||
'SVER': ['2', ], # Get projector software version
|
},
|
||||||
'SVOL': ['2', ] # Set speaker volume
|
'INF1': {'version': ['1', ],
|
||||||
|
'description': translate('OpenLP.PJLinkConstants',
|
||||||
|
'Query projector manufacturer name.')
|
||||||
|
},
|
||||||
|
'INF2': {'version': ['1', ],
|
||||||
|
'description': translate('OpenLP.PJLinkConstants',
|
||||||
|
'Query projector product name.')
|
||||||
|
},
|
||||||
|
'INFO': {'version': ['1', ],
|
||||||
|
'description': translate('OpenLP.PJLinkConstants',
|
||||||
|
'Query projector for other information set by manufacturer.')
|
||||||
|
},
|
||||||
|
'INNM': {'version': ['2', ],
|
||||||
|
'description': translate('OpenLP.PJLinkConstants',
|
||||||
|
'Query specified input source name')
|
||||||
|
},
|
||||||
|
'INPT': {'version': ['1', ],
|
||||||
|
'description': translate('OpenLP.PJLinkConstants',
|
||||||
|
'Switch to specified video source.')
|
||||||
|
},
|
||||||
|
'INST': {'version': ['1', ],
|
||||||
|
'description': translate('OpenLP.PJLinkConstants',
|
||||||
|
'Query available input sources.')
|
||||||
|
},
|
||||||
|
'IRES': {'version:': ['2', ],
|
||||||
|
'description': translate('OpenLP.PJLinkConstants',
|
||||||
|
'Query current input resolution.')
|
||||||
|
},
|
||||||
|
'LAMP': {'version': ['1', ],
|
||||||
|
'description': translate('OpenLP.PJLinkConstants',
|
||||||
|
'Query lamp time and on/off status. Multiple lamps supported.')
|
||||||
|
},
|
||||||
|
'LKUP': {'version': ['2', ],
|
||||||
|
'description': translate('OpenLP.PJLinkConstants',
|
||||||
|
'UDP Status - Projector is now available on network. Includes MAC address.')
|
||||||
|
},
|
||||||
|
'MVOL': {'version': ['2', ],
|
||||||
|
'description': translate('OpenLP.PJLinkConstants',
|
||||||
|
'Adjust microphone volume by 1 step.')
|
||||||
|
},
|
||||||
|
'NAME': {'version': ['1', ],
|
||||||
|
'description': translate('OpenLP.PJLinkConstants',
|
||||||
|
'Query customer-set projector name.')
|
||||||
|
},
|
||||||
|
'PJLINK': {'version': ['1', ],
|
||||||
|
'description': translate('OpenLP.PJLinkConstants',
|
||||||
|
'Initial connection with authentication/no authentication request.')
|
||||||
|
},
|
||||||
|
'POWR': {'version': ['1', ],
|
||||||
|
'description': translate('OpenLP.PJLinkConstants',
|
||||||
|
'Turn lamp on or off/standby.')
|
||||||
|
},
|
||||||
|
'RFIL': {'version': ['2', ],
|
||||||
|
'description': translate('OpenLP.PJLinkConstants',
|
||||||
|
'Query replacement air filter model number.')
|
||||||
|
},
|
||||||
|
'RLMP': {'version': ['2', ],
|
||||||
|
'description': translate('OpenLP.PJLinkConstants',
|
||||||
|
'Query replacement lamp model number.')
|
||||||
|
},
|
||||||
|
'RRES': {'version': ['2', ],
|
||||||
|
'description': translate('OpenLP.PJLinkConstants',
|
||||||
|
'Query recommended resolution.')
|
||||||
|
},
|
||||||
|
'SNUM': {'version': ['2', ],
|
||||||
|
'description': translate('OpenLP.PJLinkConstants',
|
||||||
|
'Query projector serial number.')
|
||||||
|
},
|
||||||
|
'SRCH': {'version': ['2', ],
|
||||||
|
'description': translate('OpenLP.PJLinkConstants',
|
||||||
|
'UDP broadcast search request for available projectors. Reply is ACKN.')
|
||||||
|
},
|
||||||
|
'SVER': {'version': ['2', ],
|
||||||
|
'description': translate('OpenLP.PJLinkConstants',
|
||||||
|
'Query projector software version number.')
|
||||||
|
},
|
||||||
|
'SVOL': {'version': ['2', ],
|
||||||
|
'description': translate('OpenLP.PJLinkConstants',
|
||||||
|
'Adjust speaker volume by 1 step.')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# Error and status codes
|
# Error and status codes
|
||||||
S_OK = E_OK = 0 # E_OK included since I sometimes forget
|
S_OK = E_OK = 0 # E_OK included since I sometimes forget
|
||||||
# Error codes. Start at 200 so we don't duplicate system error codes.
|
# Error codes. Start at 200 so we don't duplicate system error codes.
|
||||||
@ -313,11 +393,32 @@ ERROR_MSG = {
|
|||||||
S_NETWORK_RECEIVED: translate('OpenLP.ProjectorConstants', 'Received data')
|
S_NETWORK_RECEIVED: translate('OpenLP.ProjectorConstants', 'Received data')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Map ERST return code positions to equipment
|
||||||
|
PJLINK_ERST_DATA = {
|
||||||
|
'DATA_LENGTH': 6,
|
||||||
|
0: 'FAN',
|
||||||
|
1: 'LAMP',
|
||||||
|
2: 'TEMP',
|
||||||
|
3: 'COVER',
|
||||||
|
4: 'FILTER',
|
||||||
|
5: 'OTHER',
|
||||||
|
'FAN': 0,
|
||||||
|
'LAMP': 1,
|
||||||
|
'TEMP': 2,
|
||||||
|
'COVER': 3,
|
||||||
|
'FILTER': 4,
|
||||||
|
'OTHER': 5
|
||||||
|
}
|
||||||
|
|
||||||
# Map for ERST return codes to string
|
# Map for ERST return codes to string
|
||||||
PJLINK_ERST_STATUS = {
|
PJLINK_ERST_STATUS = {
|
||||||
'0': ERROR_STRING[E_OK],
|
'0': 'OK',
|
||||||
'1': ERROR_STRING[E_WARN],
|
'1': ERROR_STRING[E_WARN],
|
||||||
'2': ERROR_STRING[E_ERROR]
|
'2': ERROR_STRING[E_ERROR],
|
||||||
|
'OK': '0',
|
||||||
|
E_OK: '0',
|
||||||
|
E_WARN: '1',
|
||||||
|
E_ERROR: '2'
|
||||||
}
|
}
|
||||||
|
|
||||||
# Map for POWR return codes to status code
|
# Map for POWR return codes to status code
|
||||||
|
@ -44,6 +44,7 @@ from sqlalchemy.orm import relationship
|
|||||||
|
|
||||||
from openlp.core.lib.db import Manager, init_db, init_url
|
from openlp.core.lib.db import Manager, init_db, init_url
|
||||||
from openlp.core.lib.projector.constants import PJLINK_DEFAULT_CODES
|
from openlp.core.lib.projector.constants import PJLINK_DEFAULT_CODES
|
||||||
|
from openlp.core.lib.projector import upgrade
|
||||||
|
|
||||||
Base = declarative_base(MetaData())
|
Base = declarative_base(MetaData())
|
||||||
|
|
||||||
@ -166,13 +167,14 @@ class Projector(CommonBase, Base):
|
|||||||
"""
|
"""
|
||||||
Return basic representation of Source table entry.
|
Return basic representation of Source table entry.
|
||||||
"""
|
"""
|
||||||
return '< Projector(id="{data}", ip="{ip}", port="{port}", pin="{pin}", name="{name}", ' \
|
return '< Projector(id="{data}", ip="{ip}", port="{port}", mac_adx="{mac}", pin="{pin}", name="{name}", ' \
|
||||||
'location="{location}", notes="{notes}", pjlink_name="{pjlink_name}", ' \
|
'location="{location}", notes="{notes}", pjlink_name="{pjlink_name}", ' \
|
||||||
'manufacturer="{manufacturer}", model="{model}", serial_no="{serial}", other="{other}", ' \
|
'manufacturer="{manufacturer}", model="{model}", serial_no="{serial}", other="{other}", ' \
|
||||||
'sources="{sources}", source_list="{source_list}", model_filter="{mfilter}", ' \
|
'sources="{sources}", source_list="{source_list}", model_filter="{mfilter}", ' \
|
||||||
'model_lamp="{mlamp}", sw_version="{sw_ver}") >'.format(data=self.id,
|
'model_lamp="{mlamp}", sw_version="{sw_ver}") >'.format(data=self.id,
|
||||||
ip=self.ip,
|
ip=self.ip,
|
||||||
port=self.port,
|
port=self.port,
|
||||||
|
mac=self.mac_adx,
|
||||||
pin=self.pin,
|
pin=self.pin,
|
||||||
name=self.name,
|
name=self.name,
|
||||||
location=self.location,
|
location=self.location,
|
||||||
@ -189,6 +191,7 @@ class Projector(CommonBase, Base):
|
|||||||
sw_ver=self.sw_version)
|
sw_ver=self.sw_version)
|
||||||
ip = Column(String(100))
|
ip = Column(String(100))
|
||||||
port = Column(String(8))
|
port = Column(String(8))
|
||||||
|
mac_adx = Column(String(18))
|
||||||
pin = Column(String(20))
|
pin = Column(String(20))
|
||||||
name = Column(String(20))
|
name = Column(String(20))
|
||||||
location = Column(String(30))
|
location = Column(String(30))
|
||||||
@ -243,7 +246,9 @@ class ProjectorDB(Manager):
|
|||||||
"""
|
"""
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
log.debug('ProjectorDB().__init__(args="{arg}", kwargs="{kwarg}")'.format(arg=args, kwarg=kwargs))
|
log.debug('ProjectorDB().__init__(args="{arg}", kwargs="{kwarg}")'.format(arg=args, kwarg=kwargs))
|
||||||
super().__init__(plugin_name='projector', init_schema=self.init_schema)
|
super().__init__(plugin_name='projector',
|
||||||
|
init_schema=self.init_schema,
|
||||||
|
upgrade_mod=upgrade)
|
||||||
log.debug('ProjectorDB() Initialized using db url {db}'.format(db=self.db_url))
|
log.debug('ProjectorDB() Initialized using db url {db}'.format(db=self.db_url))
|
||||||
log.debug('Session: {session}'.format(session=self.session))
|
log.debug('Session: {session}'.format(session=self.session))
|
||||||
|
|
||||||
@ -298,7 +303,7 @@ class ProjectorDB(Manager):
|
|||||||
:param ip: Host IP/Name
|
:param ip: Host IP/Name
|
||||||
:returns: Projector() instance
|
:returns: Projector() instance
|
||||||
"""
|
"""
|
||||||
log.debug('get_projector_by_ip(ip="%s")' % ip)
|
log.debug('get_projector_by_ip(ip="{ip}")'.format(ip=ip))
|
||||||
projector = self.get_object_filtered(Projector, Projector.ip == ip)
|
projector = self.get_object_filtered(Projector, Projector.ip == ip)
|
||||||
if projector is None:
|
if projector is None:
|
||||||
# Not found
|
# Not found
|
||||||
|
File diff suppressed because it is too large
Load Diff
85
openlp/core/lib/projector/pjlink2.py
Normal file
85
openlp/core/lib/projector/pjlink2.py
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# OpenLP - Open Source Lyrics Projection #
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# Copyright (c) 2008-2017 OpenLP Developers #
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# This program is free software; you can redistribute it and/or modify it #
|
||||||
|
# under the terms of the GNU General Public License as published by the Free #
|
||||||
|
# Software Foundation; version 2 of the License. #
|
||||||
|
# #
|
||||||
|
# This program is distributed in the hope that it will be useful, but WITHOUT #
|
||||||
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
|
||||||
|
# more details. #
|
||||||
|
# #
|
||||||
|
# You should have received a copy of the GNU General Public License along #
|
||||||
|
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||||
|
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||||
|
###############################################################################
|
||||||
|
"""
|
||||||
|
:mod:`openlp.core.lib.projector.pjlink2` module provides the PJLink Class 2
|
||||||
|
updates from PJLink Class 1.
|
||||||
|
|
||||||
|
This module only handles the UDP socket functionality. Command/query/status
|
||||||
|
change messages will still be processed by the PJLink 1 module.
|
||||||
|
|
||||||
|
Currently, the only variance is the addition of a UDP "search" command to
|
||||||
|
query the local network for Class 2 capable projectors,
|
||||||
|
and UDP "notify" messages from projectors to connected software of status
|
||||||
|
changes (i.e., power change, input change, error changes).
|
||||||
|
|
||||||
|
Differences between Class 1 and Class 2 PJLink specifications are as follows.
|
||||||
|
|
||||||
|
New Functionality:
|
||||||
|
* Search - UDP Query local network for Class 2 capabable projector(s).
|
||||||
|
* Status - UDP Status change with connected projector(s). Status change
|
||||||
|
messages consist of:
|
||||||
|
* Initial projector power up when network communication becomes available
|
||||||
|
* Lamp off/standby to warmup or on
|
||||||
|
* Lamp on to cooldown or off/standby
|
||||||
|
* Input source select change completed
|
||||||
|
* Error status change (i.e., fan/lamp/temp/cover open/filter/other error(s))
|
||||||
|
|
||||||
|
New Commands:
|
||||||
|
* Query serial number of projector
|
||||||
|
* Query version number of projector software
|
||||||
|
* Query model number of replacement lamp
|
||||||
|
* Query model number of replacement air filter
|
||||||
|
* Query current projector screen resolution
|
||||||
|
* Query recommended screen resolution
|
||||||
|
* Query name of specific input terminal (video source)
|
||||||
|
* Adjust projector microphone in 1-step increments
|
||||||
|
* Adjust projector speacker in 1-step increments
|
||||||
|
|
||||||
|
Extended Commands:
|
||||||
|
* Addition of INTERNAL terminal (video source) for a total of 6 types of terminals.
|
||||||
|
* Number of terminals (video source) has been expanded from [1-9]
|
||||||
|
to [1-9a-z] (Addition of 26 terminals for each type of input).
|
||||||
|
|
||||||
|
See PJLink Class 2 Specifications for details.
|
||||||
|
http://pjlink.jbmia.or.jp/english/dl_class2.html
|
||||||
|
|
||||||
|
Section 5-1 PJLink Specifications
|
||||||
|
|
||||||
|
Section 5-5 Guidelines for Input Terminals
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
log.debug('pjlink2 loaded')
|
||||||
|
|
||||||
|
from PyQt5 import QtNetwork
|
||||||
|
|
||||||
|
|
||||||
|
class PJLinkUDP(QtNetwork.QUdpSocket):
|
||||||
|
"""
|
||||||
|
Socket service for handling datagram (UDP) sockets.
|
||||||
|
"""
|
||||||
|
log.debug('PJLinkUDP loaded')
|
||||||
|
# Class varialbe for projector list. Should be replaced by ProjectorManager's
|
||||||
|
# projector list after being loaded there.
|
||||||
|
projector_list = None
|
||||||
|
projectors_found = None # UDP search found list
|
73
openlp/core/lib/projector/upgrade.py
Normal file
73
openlp/core/lib/projector/upgrade.py
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# OpenLP - Open Source Lyrics Projection #
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# Copyright (c) 2008-2017 OpenLP Developers #
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# This program is free software; you can redistribute it and/or modify it #
|
||||||
|
# under the terms of the GNU General Public License as published by the Free #
|
||||||
|
# Software Foundation; version 2 of the License. #
|
||||||
|
# #
|
||||||
|
# This program is distributed in the hope that it will be useful, but WITHOUT #
|
||||||
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
|
||||||
|
# more details. #
|
||||||
|
# #
|
||||||
|
# You should have received a copy of the GNU General Public License along #
|
||||||
|
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||||
|
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||||
|
###############################################################################
|
||||||
|
"""
|
||||||
|
The :mod:`upgrade` module provides a way for the database and schema that is the
|
||||||
|
backend for the projector setup.
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from sqlalchemy import Table, Column, types
|
||||||
|
from sqlalchemy.sql.expression import null
|
||||||
|
|
||||||
|
from openlp.core.lib.db import get_upgrade_op
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# Initial projector DB was unversioned
|
||||||
|
__version__ = 2
|
||||||
|
|
||||||
|
log.debug('Projector DB upgrade module loading')
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade_1(session, metadata):
|
||||||
|
"""
|
||||||
|
Version 1 upgrade - old db might/might not be versioned.
|
||||||
|
"""
|
||||||
|
log.debug('Skipping projector DB upgrade to version 1 - not used')
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade_2(session, metadata):
|
||||||
|
"""
|
||||||
|
Version 2 upgrade.
|
||||||
|
|
||||||
|
Update Projector() table to include new data defined in PJLink version 2 changes
|
||||||
|
|
||||||
|
mac_adx: Column(String(18))
|
||||||
|
serial_no: Column(String(30))
|
||||||
|
sw_version: Column(String(30))
|
||||||
|
model_filter: Column(String(30))
|
||||||
|
model_lamp: Column(String(30))
|
||||||
|
|
||||||
|
:param session: DB session instance
|
||||||
|
:param metadata: Metadata of current DB
|
||||||
|
"""
|
||||||
|
log.debug('Checking projector DB upgrade to version 2')
|
||||||
|
projector_table = Table('projector', metadata, autoload=True)
|
||||||
|
upgrade_db = 'mac_adx' not in [col.name for col in projector_table.c.values()]
|
||||||
|
if upgrade_db:
|
||||||
|
new_op = get_upgrade_op(session)
|
||||||
|
new_op.add_column('projector', Column('mac_adx', types.String(18), server_default=null()))
|
||||||
|
new_op.add_column('projector', Column('serial_no', types.String(30), server_default=null()))
|
||||||
|
new_op.add_column('projector', Column('sw_version', types.String(30), server_default=null()))
|
||||||
|
new_op.add_column('projector', Column('model_filter', types.String(30), server_default=null()))
|
||||||
|
new_op.add_column('projector', Column('model_lamp', types.String(30), server_default=null()))
|
||||||
|
log.debug('{status} projector DB upgrade to version 2'.format(status='Updated' if upgrade_db else 'Skipping'))
|
@ -105,7 +105,7 @@ class SearchEdit(QtWidgets.QLineEdit):
|
|||||||
self.setPlaceholderText(action.placeholder_text)
|
self.setPlaceholderText(action.placeholder_text)
|
||||||
self.menu_button.setDefaultAction(action)
|
self.menu_button.setDefaultAction(action)
|
||||||
self._current_search_type = identifier
|
self._current_search_type = identifier
|
||||||
Settings().setValue('{section}/last search type'.format(section=self.settings_section), identifier)
|
Settings().setValue('{section}/last used search type'.format(section=self.settings_section), identifier)
|
||||||
self.searchTypeChanged.emit(identifier)
|
self.searchTypeChanged.emit(identifier)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@ -141,7 +141,7 @@ class SearchEdit(QtWidgets.QLineEdit):
|
|||||||
self.menu_button.resize(QtCore.QSize(28, 18))
|
self.menu_button.resize(QtCore.QSize(28, 18))
|
||||||
self.menu_button.setMenu(menu)
|
self.menu_button.setMenu(menu)
|
||||||
self.set_current_search_type(
|
self.set_current_search_type(
|
||||||
Settings().value('{section}/last search type'.format(section=self.settings_section)))
|
Settings().value('{section}/last used search type'.format(section=self.settings_section)))
|
||||||
self.menu_button.show()
|
self.menu_button.show()
|
||||||
self._update_style_sheet()
|
self._update_style_sheet()
|
||||||
|
|
||||||
|
@ -335,7 +335,7 @@ class ServiceItem(RegistryProperties):
|
|||||||
if image and not self.has_original_files and self.name == 'presentations':
|
if image and not self.has_original_files and self.name == 'presentations':
|
||||||
file_location = os.path.join(path, file_name)
|
file_location = os.path.join(path, file_name)
|
||||||
file_location_hash = md5_hash(file_location.encode('utf-8'))
|
file_location_hash = md5_hash(file_location.encode('utf-8'))
|
||||||
image = os.path.join(AppLocation.get_section_data_path(self.name), 'thumbnails',
|
image = os.path.join(str(AppLocation.get_section_data_path(self.name)), 'thumbnails',
|
||||||
file_location_hash, ntpath.basename(image))
|
file_location_hash, ntpath.basename(image))
|
||||||
self._raw_frames.append({'title': file_name, 'image': image, 'path': path,
|
self._raw_frames.append({'title': file_name, 'image': image, 'path': path,
|
||||||
'display_title': display_title, 'notes': notes})
|
'display_title': display_title, 'notes': notes})
|
||||||
|
@ -158,9 +158,8 @@ class Theme(object):
|
|||||||
Initialise the theme object.
|
Initialise the theme object.
|
||||||
"""
|
"""
|
||||||
# basic theme object with defaults
|
# basic theme object with defaults
|
||||||
json_dir = os.path.join(AppLocation.get_directory(AppLocation.AppDir), 'core', 'lib', 'json')
|
json_path = AppLocation.get_directory(AppLocation.AppDir) / 'core' / 'lib' / 'json' / 'theme.json'
|
||||||
json_file = os.path.join(json_dir, 'theme.json')
|
jsn = get_text_file_string(json_path)
|
||||||
jsn = get_text_file_string(json_file)
|
|
||||||
jsn = json.loads(jsn)
|
jsn = json.loads(jsn)
|
||||||
self.expand_json(jsn)
|
self.expand_json(jsn)
|
||||||
self.background_filename = ''
|
self.background_filename = ''
|
||||||
|
@ -49,6 +49,7 @@ def add_welcome_page(parent, image):
|
|||||||
parent.title_label = QtWidgets.QLabel(parent.welcome_page)
|
parent.title_label = QtWidgets.QLabel(parent.welcome_page)
|
||||||
parent.title_label.setObjectName('title_label')
|
parent.title_label.setObjectName('title_label')
|
||||||
parent.welcome_layout.addWidget(parent.title_label)
|
parent.welcome_layout.addWidget(parent.title_label)
|
||||||
|
parent.title_label.setWordWrap(True)
|
||||||
parent.welcome_layout.addSpacing(40)
|
parent.welcome_layout.addSpacing(40)
|
||||||
parent.information_label = QtWidgets.QLabel(parent.welcome_page)
|
parent.information_label = QtWidgets.QLabel(parent.welcome_page)
|
||||||
parent.information_label.setWordWrap(True)
|
parent.information_label.setWordWrap(True)
|
||||||
|
@ -99,7 +99,7 @@ from .themelayoutform import ThemeLayoutForm
|
|||||||
from .themeform import ThemeForm
|
from .themeform import ThemeForm
|
||||||
from .filerenameform import FileRenameForm
|
from .filerenameform import FileRenameForm
|
||||||
from .starttimeform import StartTimeForm
|
from .starttimeform import StartTimeForm
|
||||||
from .maindisplay import MainDisplay, Display
|
from .maindisplay import MainDisplay, Display, AudioPlayer
|
||||||
from .servicenoteform import ServiceNoteForm
|
from .servicenoteform import ServiceNoteForm
|
||||||
from .serviceitemeditform import ServiceItemEditForm
|
from .serviceitemeditform import ServiceItemEditForm
|
||||||
from .slidecontroller import SlideController, DisplayController, PreviewController, LiveController
|
from .slidecontroller import SlideController, DisplayController, PreviewController, LiveController
|
||||||
@ -120,8 +120,8 @@ from .projector.tab import ProjectorTab
|
|||||||
from .projector.editform import ProjectorEditForm
|
from .projector.editform import ProjectorEditForm
|
||||||
|
|
||||||
__all__ = ['SplashScreen', 'AboutForm', 'SettingsForm', 'MainDisplay', 'SlideController', 'ServiceManager', 'ThemeForm',
|
__all__ = ['SplashScreen', 'AboutForm', 'SettingsForm', 'MainDisplay', 'SlideController', 'ServiceManager', 'ThemeForm',
|
||||||
'ThemeManager', 'ServiceItemEditForm', 'FirstTimeForm', 'FirstTimeLanguageForm',
|
'ThemeManager', 'ServiceItemEditForm', 'FirstTimeForm', 'FirstTimeLanguageForm', 'Display', 'AudioPlayer',
|
||||||
'Display', 'ServiceNoteForm', 'ThemeLayoutForm', 'FileRenameForm', 'StartTimeForm', 'MainDisplay',
|
'ServiceNoteForm', 'ThemeLayoutForm', 'FileRenameForm', 'StartTimeForm', 'MainDisplay',
|
||||||
'SlideController', 'DisplayController', 'GeneralTab', 'ThemesTab', 'AdvancedTab', 'PluginForm',
|
'SlideController', 'DisplayController', 'GeneralTab', 'ThemesTab', 'AdvancedTab', 'PluginForm',
|
||||||
'FormattingTagForm', 'ShortcutListForm', 'FormattingTagController', 'SingleColumnTableWidget',
|
'FormattingTagForm', 'ShortcutListForm', 'FormattingTagController', 'SingleColumnTableWidget',
|
||||||
'ProjectorManager', 'ProjectorTab', 'ProjectorEditForm']
|
'ProjectorManager', 'ProjectorTab', 'ProjectorEditForm']
|
||||||
|
@ -40,7 +40,8 @@ class AboutForm(QtWidgets.QDialog, UiAboutDialog):
|
|||||||
"""
|
"""
|
||||||
Do some initialisation stuff
|
Do some initialisation stuff
|
||||||
"""
|
"""
|
||||||
super(AboutForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint)
|
super(AboutForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint |
|
||||||
|
QtCore.Qt.WindowCloseButtonHint)
|
||||||
self._setup()
|
self._setup()
|
||||||
|
|
||||||
def _setup(self):
|
def _setup(self):
|
||||||
|
@ -30,6 +30,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets
|
|||||||
|
|
||||||
from openlp.core.common import AppLocation, Settings, SlideLimits, UiStrings, translate
|
from openlp.core.common import AppLocation, Settings, SlideLimits, UiStrings, translate
|
||||||
from openlp.core.common.languagemanager import format_time
|
from openlp.core.common.languagemanager import format_time
|
||||||
|
from openlp.core.common.path import path_to_str
|
||||||
from openlp.core.lib import SettingsTab, build_icon
|
from openlp.core.lib import SettingsTab, build_icon
|
||||||
from openlp.core.ui.lib import PathEdit, PathType
|
from openlp.core.ui.lib import PathEdit, PathType
|
||||||
|
|
||||||
@ -207,6 +208,9 @@ class AdvancedTab(SettingsTab):
|
|||||||
self.display_workaround_group_box.setObjectName('display_workaround_group_box')
|
self.display_workaround_group_box.setObjectName('display_workaround_group_box')
|
||||||
self.display_workaround_layout = QtWidgets.QVBoxLayout(self.display_workaround_group_box)
|
self.display_workaround_layout = QtWidgets.QVBoxLayout(self.display_workaround_group_box)
|
||||||
self.display_workaround_layout.setObjectName('display_workaround_layout')
|
self.display_workaround_layout.setObjectName('display_workaround_layout')
|
||||||
|
self.ignore_aspect_ratio_check_box = QtWidgets.QCheckBox(self.display_workaround_group_box)
|
||||||
|
self.ignore_aspect_ratio_check_box.setObjectName('ignore_aspect_ratio_check_box')
|
||||||
|
self.display_workaround_layout.addWidget(self.ignore_aspect_ratio_check_box)
|
||||||
self.x11_bypass_check_box = QtWidgets.QCheckBox(self.display_workaround_group_box)
|
self.x11_bypass_check_box = QtWidgets.QCheckBox(self.display_workaround_group_box)
|
||||||
self.x11_bypass_check_box.setObjectName('x11_bypass_check_box')
|
self.x11_bypass_check_box.setObjectName('x11_bypass_check_box')
|
||||||
self.display_workaround_layout.addWidget(self.x11_bypass_check_box)
|
self.display_workaround_layout.addWidget(self.x11_bypass_check_box)
|
||||||
@ -310,6 +314,7 @@ class AdvancedTab(SettingsTab):
|
|||||||
translate('OpenLP.AdvancedTab', '<strong>WARNING:</strong> New data directory location contains '
|
translate('OpenLP.AdvancedTab', '<strong>WARNING:</strong> New data directory location contains '
|
||||||
'OpenLP data files. These files WILL be replaced during a copy.'))
|
'OpenLP data files. These files WILL be replaced during a copy.'))
|
||||||
self.display_workaround_group_box.setTitle(translate('OpenLP.AdvancedTab', 'Display Workarounds'))
|
self.display_workaround_group_box.setTitle(translate('OpenLP.AdvancedTab', 'Display Workarounds'))
|
||||||
|
self.ignore_aspect_ratio_check_box.setText(translate('OpenLP.AdvancedTab', 'Ignore Aspect Ratio'))
|
||||||
self.x11_bypass_check_box.setText(translate('OpenLP.AdvancedTab', 'Bypass X11 Window Manager'))
|
self.x11_bypass_check_box.setText(translate('OpenLP.AdvancedTab', 'Bypass X11 Window Manager'))
|
||||||
self.alternate_rows_check_box.setText(translate('OpenLP.AdvancedTab', 'Use alternating row colours in lists'))
|
self.alternate_rows_check_box.setText(translate('OpenLP.AdvancedTab', 'Use alternating row colours in lists'))
|
||||||
# Slide Limits
|
# Slide Limits
|
||||||
@ -354,6 +359,7 @@ class AdvancedTab(SettingsTab):
|
|||||||
default_service_enabled = settings.value('default service enabled')
|
default_service_enabled = settings.value('default service enabled')
|
||||||
self.service_name_check_box.setChecked(default_service_enabled)
|
self.service_name_check_box.setChecked(default_service_enabled)
|
||||||
self.service_name_check_box_toggled(default_service_enabled)
|
self.service_name_check_box_toggled(default_service_enabled)
|
||||||
|
self.ignore_aspect_ratio_check_box.setChecked(settings.value('ignore aspect ratio'))
|
||||||
self.x11_bypass_check_box.setChecked(settings.value('x11 bypass wm'))
|
self.x11_bypass_check_box.setChecked(settings.value('x11 bypass wm'))
|
||||||
self.slide_limits = settings.value('slide limits')
|
self.slide_limits = settings.value('slide limits')
|
||||||
self.is_search_as_you_type_enabled = settings.value('search as type')
|
self.is_search_as_you_type_enabled = settings.value('search as type')
|
||||||
@ -409,6 +415,7 @@ class AdvancedTab(SettingsTab):
|
|||||||
settings.setValue('hide mouse', self.hide_mouse_check_box.isChecked())
|
settings.setValue('hide mouse', self.hide_mouse_check_box.isChecked())
|
||||||
settings.setValue('alternate rows', self.alternate_rows_check_box.isChecked())
|
settings.setValue('alternate rows', self.alternate_rows_check_box.isChecked())
|
||||||
settings.setValue('slide limits', self.slide_limits)
|
settings.setValue('slide limits', self.slide_limits)
|
||||||
|
settings.setValue('ignore aspect ratio', self.ignore_aspect_ratio_check_box.isChecked())
|
||||||
if self.x11_bypass_check_box.isChecked() != settings.value('x11 bypass wm'):
|
if self.x11_bypass_check_box.isChecked() != settings.value('x11 bypass wm'):
|
||||||
settings.setValue('x11 bypass wm', self.x11_bypass_check_box.isChecked())
|
settings.setValue('x11 bypass wm', self.x11_bypass_check_box.isChecked())
|
||||||
self.settings_form.register_post_process('config_screen_changed')
|
self.settings_form.register_post_process('config_screen_changed')
|
||||||
@ -495,16 +502,14 @@ class AdvancedTab(SettingsTab):
|
|||||||
'location of the OpenLP data directory to:\n\n{path}'
|
'location of the OpenLP data directory to:\n\n{path}'
|
||||||
'\n\nThe data directory will be changed when OpenLP is '
|
'\n\nThe data directory will be changed when OpenLP is '
|
||||||
'closed.').format(path=new_data_path),
|
'closed.').format(path=new_data_path),
|
||||||
QtWidgets.QMessageBox.StandardButtons(QtWidgets.QMessageBox.Yes |
|
defaultButton=QtWidgets.QMessageBox.No)
|
||||||
QtWidgets.QMessageBox.No),
|
|
||||||
QtWidgets.QMessageBox.No)
|
|
||||||
if answer != QtWidgets.QMessageBox.Yes:
|
if answer != QtWidgets.QMessageBox.Yes:
|
||||||
self.data_directory_path_edit.path = AppLocation.get_data_path()
|
self.data_directory_path_edit.path = AppLocation.get_data_path()
|
||||||
return
|
return
|
||||||
# Check if data already exists here.
|
# Check if data already exists here.
|
||||||
self.check_data_overwrite(new_data_path)
|
self.check_data_overwrite(path_to_str(new_data_path))
|
||||||
# Save the new location.
|
# Save the new location.
|
||||||
self.main_window.set_new_data_path(new_data_path)
|
self.main_window.set_new_data_path(path_to_str(new_data_path))
|
||||||
self.data_directory_cancel_button.show()
|
self.data_directory_cancel_button.show()
|
||||||
|
|
||||||
def on_data_directory_copy_check_box_toggled(self):
|
def on_data_directory_copy_check_box_toggled(self):
|
||||||
|
@ -70,9 +70,9 @@ try:
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
VLC_VERSION = '-'
|
VLC_VERSION = '-'
|
||||||
|
|
||||||
from openlp.core.common import Settings, UiStrings, translate
|
from openlp.core.common import RegistryProperties, Settings, UiStrings, is_linux, translate
|
||||||
from openlp.core.common.versionchecker import get_application_version
|
from openlp.core.common.versionchecker import get_application_version
|
||||||
from openlp.core.common import RegistryProperties, is_linux
|
from openlp.core.ui.lib.filedialog import FileDialog
|
||||||
|
|
||||||
from .exceptiondialog import Ui_ExceptionDialog
|
from .exceptiondialog import Ui_ExceptionDialog
|
||||||
|
|
||||||
@ -139,17 +139,17 @@ class ExceptionForm(QtWidgets.QDialog, Ui_ExceptionDialog, RegistryProperties):
|
|||||||
"""
|
"""
|
||||||
Saving exception log and system information to a file.
|
Saving exception log and system information to a file.
|
||||||
"""
|
"""
|
||||||
filename = QtWidgets.QFileDialog.getSaveFileName(
|
file_path, filter_used = FileDialog.getSaveFileName(
|
||||||
self,
|
self,
|
||||||
translate('OpenLP.ExceptionForm', 'Save Crash Report'),
|
translate('OpenLP.ExceptionForm', 'Save Crash Report'),
|
||||||
Settings().value(self.settings_section + '/last directory'),
|
Settings().value(self.settings_section + '/last directory'),
|
||||||
translate('OpenLP.ExceptionForm', 'Text files (*.txt *.log *.text)'))[0]
|
translate('OpenLP.ExceptionForm', 'Text files (*.txt *.log *.text)'))
|
||||||
if filename:
|
if file_path:
|
||||||
filename = str(filename).replace('/', os.path.sep)
|
Settings().setValue(self.settings_section + '/last directory', file_path.parent)
|
||||||
Settings().setValue(self.settings_section + '/last directory', os.path.dirname(filename))
|
|
||||||
opts = self._create_report()
|
opts = self._create_report()
|
||||||
report_text = self.report_text.format(version=opts['version'], description=opts['description'],
|
report_text = self.report_text.format(version=opts['version'], description=opts['description'],
|
||||||
traceback=opts['traceback'], libs=opts['libs'], system=opts['system'])
|
traceback=opts['traceback'], libs=opts['libs'], system=opts['system'])
|
||||||
|
filename = str(file_path)
|
||||||
try:
|
try:
|
||||||
report_file = open(filename, 'w')
|
report_file = open(filename, 'w')
|
||||||
try:
|
try:
|
||||||
@ -212,17 +212,16 @@ class ExceptionForm(QtWidgets.QDialog, Ui_ExceptionDialog, RegistryProperties):
|
|||||||
|
|
||||||
def on_attach_file_button_clicked(self):
|
def on_attach_file_button_clicked(self):
|
||||||
"""
|
"""
|
||||||
Attache files to the bug report e-mail.
|
Attach files to the bug report e-mail.
|
||||||
"""
|
"""
|
||||||
files, filter_used = QtWidgets.QFileDialog.getOpenFileName(self,
|
file_path, filter_used = \
|
||||||
translate('ImagePlugin.ExceptionDialog',
|
FileDialog.getOpenFileName(self,
|
||||||
'Select Attachment'),
|
translate('ImagePlugin.ExceptionDialog', 'Select Attachment'),
|
||||||
Settings().value(self.settings_section +
|
Settings().value(self.settings_section + '/last directory'),
|
||||||
'/last directory'),
|
'{text} (*)'.format(text=UiStrings().AllFiles))
|
||||||
'{text} (*)'.format(text=UiStrings().AllFiles))
|
log.info('New file {file}'.format(file=file_path))
|
||||||
log.info('New files(s) {files}'.format(files=str(files)))
|
if file_path:
|
||||||
if files:
|
self.file_attachment = str(file_path)
|
||||||
self.file_attachment = str(files)
|
|
||||||
|
|
||||||
def __button_state(self, state):
|
def __button_state(self, state):
|
||||||
"""
|
"""
|
||||||
|
@ -38,8 +38,8 @@ class FileRenameForm(QtWidgets.QDialog, Ui_FileRenameDialog, RegistryProperties)
|
|||||||
"""
|
"""
|
||||||
Constructor
|
Constructor
|
||||||
"""
|
"""
|
||||||
super(FileRenameForm, self).__init__(Registry().get('main_window'),
|
super(FileRenameForm, self).__init__(Registry().get('main_window'), QtCore.Qt.WindowSystemMenuHint |
|
||||||
QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint)
|
QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowCloseButtonHint)
|
||||||
self._setup()
|
self._setup()
|
||||||
|
|
||||||
def _setup(self):
|
def _setup(self):
|
||||||
|
@ -29,13 +29,14 @@ import time
|
|||||||
import urllib.request
|
import urllib.request
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
import urllib.error
|
import urllib.error
|
||||||
|
from configparser import ConfigParser, MissingSectionHeaderError, NoOptionError, NoSectionError
|
||||||
from tempfile import gettempdir
|
from tempfile import gettempdir
|
||||||
from configparser import ConfigParser, MissingSectionHeaderError, NoSectionError, NoOptionError
|
|
||||||
|
|
||||||
from PyQt5 import QtCore, QtWidgets
|
from PyQt5 import QtCore, QtWidgets
|
||||||
|
|
||||||
from openlp.core.common import Registry, RegistryProperties, AppLocation, Settings, check_directory_exists, \
|
from openlp.core.common import Registry, RegistryProperties, AppLocation, Settings, check_directory_exists, \
|
||||||
translate, clean_button_text, trace_error_handler
|
translate, clean_button_text, trace_error_handler
|
||||||
|
from openlp.core.common.path import Path
|
||||||
from openlp.core.lib import PluginStatus, build_icon
|
from openlp.core.lib import PluginStatus, build_icon
|
||||||
from openlp.core.lib.ui import critical_error_message_box
|
from openlp.core.lib.ui import critical_error_message_box
|
||||||
from openlp.core.common.httputils import get_web_page, get_url_file_size, url_get_file, CONNECTION_TIMEOUT
|
from openlp.core.common.httputils import get_web_page, get_url_file_size, url_get_file, CONNECTION_TIMEOUT
|
||||||
@ -202,11 +203,10 @@ class FirstTimeForm(QtWidgets.QWizard, UiFirstTimeWizard, RegistryProperties):
|
|||||||
self.themes_url = self.web + self.config.get('themes', 'directory') + '/'
|
self.themes_url = self.web + self.config.get('themes', 'directory') + '/'
|
||||||
self.web_access = True
|
self.web_access = True
|
||||||
except (NoSectionError, NoOptionError, MissingSectionHeaderError):
|
except (NoSectionError, NoOptionError, MissingSectionHeaderError):
|
||||||
log.debug('A problem occured while parsing the downloaded config file')
|
log.debug('A problem occurred while parsing the downloaded config file')
|
||||||
trace_error_handler(log)
|
trace_error_handler(log)
|
||||||
self.update_screen_list_combo()
|
self.update_screen_list_combo()
|
||||||
self.application.process_events()
|
self.application.process_events()
|
||||||
# TODO: Tested at home
|
|
||||||
self.downloading = translate('OpenLP.FirstTimeWizard', 'Downloading {name}...')
|
self.downloading = translate('OpenLP.FirstTimeWizard', 'Downloading {name}...')
|
||||||
if self.has_run_wizard:
|
if self.has_run_wizard:
|
||||||
self.songs_check_box.setChecked(self.plugin_manager.get_plugin_by_name('songs').is_active())
|
self.songs_check_box.setChecked(self.plugin_manager.get_plugin_by_name('songs').is_active())
|
||||||
@ -214,7 +214,6 @@ class FirstTimeForm(QtWidgets.QWizard, UiFirstTimeWizard, RegistryProperties):
|
|||||||
self.presentation_check_box.setChecked(self.plugin_manager.get_plugin_by_name('presentations').is_active())
|
self.presentation_check_box.setChecked(self.plugin_manager.get_plugin_by_name('presentations').is_active())
|
||||||
self.image_check_box.setChecked(self.plugin_manager.get_plugin_by_name('images').is_active())
|
self.image_check_box.setChecked(self.plugin_manager.get_plugin_by_name('images').is_active())
|
||||||
self.media_check_box.setChecked(self.plugin_manager.get_plugin_by_name('media').is_active())
|
self.media_check_box.setChecked(self.plugin_manager.get_plugin_by_name('media').is_active())
|
||||||
self.remote_check_box.setChecked(self.plugin_manager.get_plugin_by_name('remotes').is_active())
|
|
||||||
self.custom_check_box.setChecked(self.plugin_manager.get_plugin_by_name('custom').is_active())
|
self.custom_check_box.setChecked(self.plugin_manager.get_plugin_by_name('custom').is_active())
|
||||||
self.song_usage_check_box.setChecked(self.plugin_manager.get_plugin_by_name('songusage').is_active())
|
self.song_usage_check_box.setChecked(self.plugin_manager.get_plugin_by_name('songusage').is_active())
|
||||||
self.alert_check_box.setChecked(self.plugin_manager.get_plugin_by_name('alerts').is_active())
|
self.alert_check_box.setChecked(self.plugin_manager.get_plugin_by_name('alerts').is_active())
|
||||||
@ -284,7 +283,7 @@ class FirstTimeForm(QtWidgets.QWizard, UiFirstTimeWizard, RegistryProperties):
|
|||||||
self.no_internet_cancel_button.setVisible(False)
|
self.no_internet_cancel_button.setVisible(False)
|
||||||
# Check if this is a re-run of the wizard.
|
# Check if this is a re-run of the wizard.
|
||||||
self.has_run_wizard = Settings().value('core/has run wizard')
|
self.has_run_wizard = Settings().value('core/has run wizard')
|
||||||
check_directory_exists(os.path.join(gettempdir(), 'openlp'))
|
check_directory_exists(Path(gettempdir(), 'openlp'))
|
||||||
|
|
||||||
def update_screen_list_combo(self):
|
def update_screen_list_combo(self):
|
||||||
"""
|
"""
|
||||||
@ -531,7 +530,6 @@ class FirstTimeForm(QtWidgets.QWizard, UiFirstTimeWizard, RegistryProperties):
|
|||||||
self._set_plugin_status(self.presentation_check_box, 'presentations/status')
|
self._set_plugin_status(self.presentation_check_box, 'presentations/status')
|
||||||
self._set_plugin_status(self.image_check_box, 'images/status')
|
self._set_plugin_status(self.image_check_box, 'images/status')
|
||||||
self._set_plugin_status(self.media_check_box, 'media/status')
|
self._set_plugin_status(self.media_check_box, 'media/status')
|
||||||
self._set_plugin_status(self.remote_check_box, 'remotes/status')
|
|
||||||
self._set_plugin_status(self.custom_check_box, 'custom/status')
|
self._set_plugin_status(self.custom_check_box, 'custom/status')
|
||||||
self._set_plugin_status(self.song_usage_check_box, 'songusage/status')
|
self._set_plugin_status(self.song_usage_check_box, 'songusage/status')
|
||||||
self._set_plugin_status(self.alert_check_box, 'alerts/status')
|
self._set_plugin_status(self.alert_check_box, 'alerts/status')
|
||||||
@ -555,15 +553,14 @@ class FirstTimeForm(QtWidgets.QWizard, UiFirstTimeWizard, RegistryProperties):
|
|||||||
"""
|
"""
|
||||||
# Build directories for downloads
|
# Build directories for downloads
|
||||||
songs_destination = os.path.join(gettempdir(), 'openlp')
|
songs_destination = os.path.join(gettempdir(), 'openlp')
|
||||||
bibles_destination = AppLocation.get_section_data_path('bibles')
|
bibles_destination = str(AppLocation.get_section_data_path('bibles'))
|
||||||
themes_destination = AppLocation.get_section_data_path('themes')
|
themes_destination = str(AppLocation.get_section_data_path('themes'))
|
||||||
missed_files = []
|
missed_files = []
|
||||||
# Download songs
|
# Download songs
|
||||||
for i in range(self.songs_list_widget.count()):
|
for i in range(self.songs_list_widget.count()):
|
||||||
item = self.songs_list_widget.item(i)
|
item = self.songs_list_widget.item(i)
|
||||||
if item.checkState() == QtCore.Qt.Checked:
|
if item.checkState() == QtCore.Qt.Checked:
|
||||||
filename, sha256 = item.data(QtCore.Qt.UserRole)
|
filename, sha256 = item.data(QtCore.Qt.UserRole)
|
||||||
# TODO: Tested at home
|
|
||||||
self._increment_progress_bar(self.downloading.format(name=filename), 0)
|
self._increment_progress_bar(self.downloading.format(name=filename), 0)
|
||||||
self.previous_size = 0
|
self.previous_size = 0
|
||||||
destination = os.path.join(songs_destination, str(filename))
|
destination = os.path.join(songs_destination, str(filename))
|
||||||
@ -576,7 +573,6 @@ class FirstTimeForm(QtWidgets.QWizard, UiFirstTimeWizard, RegistryProperties):
|
|||||||
item = bibles_iterator.value()
|
item = bibles_iterator.value()
|
||||||
if item.parent() and item.checkState(0) == QtCore.Qt.Checked:
|
if item.parent() and item.checkState(0) == QtCore.Qt.Checked:
|
||||||
bible, sha256 = item.data(0, QtCore.Qt.UserRole)
|
bible, sha256 = item.data(0, QtCore.Qt.UserRole)
|
||||||
# TODO: Tested at home
|
|
||||||
self._increment_progress_bar(self.downloading.format(name=bible), 0)
|
self._increment_progress_bar(self.downloading.format(name=bible), 0)
|
||||||
self.previous_size = 0
|
self.previous_size = 0
|
||||||
if not url_get_file(self, '{path}{name}'.format(path=self.bibles_url, name=bible),
|
if not url_get_file(self, '{path}{name}'.format(path=self.bibles_url, name=bible),
|
||||||
@ -589,7 +585,6 @@ class FirstTimeForm(QtWidgets.QWizard, UiFirstTimeWizard, RegistryProperties):
|
|||||||
item = self.themes_list_widget.item(i)
|
item = self.themes_list_widget.item(i)
|
||||||
if item.checkState() == QtCore.Qt.Checked:
|
if item.checkState() == QtCore.Qt.Checked:
|
||||||
theme, sha256 = item.data(QtCore.Qt.UserRole)
|
theme, sha256 = item.data(QtCore.Qt.UserRole)
|
||||||
# TODO: Tested at home
|
|
||||||
self._increment_progress_bar(self.downloading.format(name=theme), 0)
|
self._increment_progress_bar(self.downloading.format(name=theme), 0)
|
||||||
self.previous_size = 0
|
self.previous_size = 0
|
||||||
if not url_get_file(self, '{path}{name}'.format(path=self.themes_url, name=theme),
|
if not url_get_file(self, '{path}{name}'.format(path=self.themes_url, name=theme),
|
||||||
|
@ -37,7 +37,8 @@ class FirstTimeLanguageForm(QtWidgets.QDialog, Ui_FirstTimeLanguageDialog):
|
|||||||
"""
|
"""
|
||||||
Constructor
|
Constructor
|
||||||
"""
|
"""
|
||||||
super(FirstTimeLanguageForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint)
|
super(FirstTimeLanguageForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint |
|
||||||
|
QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowCloseButtonHint)
|
||||||
self.setupUi(self)
|
self.setupUi(self)
|
||||||
self.qm_list = LanguageManager.get_qm_list()
|
self.qm_list = LanguageManager.get_qm_list()
|
||||||
self.language_combo_box.addItem('Autodetect')
|
self.language_combo_box.addItem('Autodetect')
|
||||||
|
@ -24,6 +24,7 @@ The UI widgets for the first time wizard.
|
|||||||
"""
|
"""
|
||||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||||
|
|
||||||
|
from openlp.core.common.uistrings import UiStrings
|
||||||
from openlp.core.common import translate, is_macosx, clean_button_text, Settings
|
from openlp.core.common import translate, is_macosx, clean_button_text, Settings
|
||||||
from openlp.core.lib import build_icon
|
from openlp.core.lib import build_icon
|
||||||
from openlp.core.lib.ui import add_welcome_page
|
from openlp.core.lib.ui import add_welcome_page
|
||||||
@ -254,8 +255,7 @@ class UiFirstTimeWizard(object):
|
|||||||
self.presentation_check_box.setText(translate('OpenLP.FirstTimeWizard',
|
self.presentation_check_box.setText(translate('OpenLP.FirstTimeWizard',
|
||||||
'Presentations – Show .ppt, .odp and .pdf files'))
|
'Presentations – Show .ppt, .odp and .pdf files'))
|
||||||
self.media_check_box.setText(translate('OpenLP.FirstTimeWizard', 'Media – Playback of Audio and Video files'))
|
self.media_check_box.setText(translate('OpenLP.FirstTimeWizard', 'Media – Playback of Audio and Video files'))
|
||||||
self.remote_check_box.setText(translate('OpenLP.FirstTimeWizard', 'Remote – Control OpenLP via browser or smart'
|
self.remote_check_box.setText(str(UiStrings().WebDownloadText))
|
||||||
'phone app'))
|
|
||||||
self.song_usage_check_box.setText(translate('OpenLP.FirstTimeWizard', 'Song Usage Monitor'))
|
self.song_usage_check_box.setText(translate('OpenLP.FirstTimeWizard', 'Song Usage Monitor'))
|
||||||
self.alert_check_box.setText(translate('OpenLP.FirstTimeWizard',
|
self.alert_check_box.setText(translate('OpenLP.FirstTimeWizard',
|
||||||
'Alerts – Display informative messages while showing other slides'))
|
'Alerts – Display informative messages while showing other slides'))
|
||||||
|
@ -130,8 +130,7 @@ class FormattingTagController(object):
|
|||||||
elif not match.group('empty'):
|
elif not match.group('empty'):
|
||||||
end_tags.append(tag)
|
end_tags.append(tag)
|
||||||
match = self.html_tag_regex.search(start_html, match.end())
|
match = self.html_tag_regex.search(start_html, match.end())
|
||||||
# TODO: Verify format() works with lambda
|
return ''.join(map(lambda tag: '</{tag}>'.format(tag=tag), reversed(end_tags)))
|
||||||
return ''.join(map(lambda tag: '</%s>' % tag, reversed(end_tags)))
|
|
||||||
|
|
||||||
def start_tag_changed(self, start_html, end_html):
|
def start_tag_changed(self, start_html, end_html):
|
||||||
"""
|
"""
|
||||||
|
@ -51,7 +51,8 @@ class FormattingTagForm(QtWidgets.QDialog, Ui_FormattingTagDialog, FormattingTag
|
|||||||
"""
|
"""
|
||||||
Constructor
|
Constructor
|
||||||
"""
|
"""
|
||||||
super(FormattingTagForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint)
|
super(FormattingTagForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint |
|
||||||
|
QtCore.Qt.WindowCloseButtonHint)
|
||||||
self.setupUi(self)
|
self.setupUi(self)
|
||||||
self._setup()
|
self._setup()
|
||||||
|
|
||||||
@ -122,8 +123,7 @@ class FormattingTagForm(QtWidgets.QDialog, Ui_FormattingTagDialog, FormattingTag
|
|||||||
self.tag_table_widget.item(count, 2).text(),
|
self.tag_table_widget.item(count, 2).text(),
|
||||||
self.tag_table_widget.item(count, 3).text())
|
self.tag_table_widget.item(count, 3).text())
|
||||||
if error:
|
if error:
|
||||||
QtWidgets.QMessageBox.warning(self, translate('OpenLP.FormattingTagForm', 'Validation Error'), error,
|
QtWidgets.QMessageBox.warning(self, translate('OpenLP.FormattingTagForm', 'Validation Error'), error)
|
||||||
QtWidgets.QMessageBox.Ok)
|
|
||||||
self.tag_table_widget.selectRow(count)
|
self.tag_table_widget.selectRow(count)
|
||||||
return
|
return
|
||||||
count += 1
|
count += 1
|
||||||
@ -198,6 +198,5 @@ class FormattingTagForm(QtWidgets.QDialog, Ui_FormattingTagDialog, FormattingTag
|
|||||||
if tag:
|
if tag:
|
||||||
self.tag_table_widget.setItem(pre_row, 3, QtWidgets.QTableWidgetItem(tag))
|
self.tag_table_widget.setItem(pre_row, 3, QtWidgets.QTableWidgetItem(tag))
|
||||||
if errors:
|
if errors:
|
||||||
QtWidgets.QMessageBox.warning(self, translate('OpenLP.FormattingTagForm', 'Validation Error'), errors,
|
QtWidgets.QMessageBox.warning(self, translate('OpenLP.FormattingTagForm', 'Validation Error'), errors)
|
||||||
QtWidgets.QMessageBox.Ok)
|
|
||||||
self.tag_table_widget.resizeRowsToContents()
|
self.tag_table_widget.resizeRowsToContents()
|
||||||
|
@ -27,6 +27,7 @@ import logging
|
|||||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||||
|
|
||||||
from openlp.core.common import Registry, Settings, UiStrings, translate, get_images_filter
|
from openlp.core.common import Registry, Settings, UiStrings, translate, get_images_filter
|
||||||
|
from openlp.core.common.path import Path, path_to_str, str_to_path
|
||||||
from openlp.core.lib import SettingsTab, ScreenList
|
from openlp.core.lib import SettingsTab, ScreenList
|
||||||
from openlp.core.ui.lib import ColorButton, PathEdit
|
from openlp.core.ui.lib import ColorButton, PathEdit
|
||||||
|
|
||||||
@ -172,7 +173,8 @@ class GeneralTab(SettingsTab):
|
|||||||
self.logo_layout.setObjectName('logo_layout')
|
self.logo_layout.setObjectName('logo_layout')
|
||||||
self.logo_file_label = QtWidgets.QLabel(self.logo_group_box)
|
self.logo_file_label = QtWidgets.QLabel(self.logo_group_box)
|
||||||
self.logo_file_label.setObjectName('logo_file_label')
|
self.logo_file_label.setObjectName('logo_file_label')
|
||||||
self.logo_file_path_edit = PathEdit(self.logo_group_box, default_path=':/graphics/openlp-splash-screen.png')
|
self.logo_file_path_edit = PathEdit(self.logo_group_box,
|
||||||
|
default_path=Path(':/graphics/openlp-splash-screen.png'))
|
||||||
self.logo_layout.addRow(self.logo_file_label, self.logo_file_path_edit)
|
self.logo_layout.addRow(self.logo_file_label, self.logo_file_path_edit)
|
||||||
self.logo_color_label = QtWidgets.QLabel(self.logo_group_box)
|
self.logo_color_label = QtWidgets.QLabel(self.logo_group_box)
|
||||||
self.logo_color_label.setObjectName('logo_color_label')
|
self.logo_color_label.setObjectName('logo_color_label')
|
||||||
@ -266,7 +268,7 @@ class GeneralTab(SettingsTab):
|
|||||||
self.audio_group_box.setTitle(translate('OpenLP.GeneralTab', 'Background Audio'))
|
self.audio_group_box.setTitle(translate('OpenLP.GeneralTab', 'Background Audio'))
|
||||||
self.start_paused_check_box.setText(translate('OpenLP.GeneralTab', 'Start background audio paused'))
|
self.start_paused_check_box.setText(translate('OpenLP.GeneralTab', 'Start background audio paused'))
|
||||||
self.repeat_list_check_box.setText(translate('OpenLP.GeneralTab', 'Repeat track list'))
|
self.repeat_list_check_box.setText(translate('OpenLP.GeneralTab', 'Repeat track list'))
|
||||||
self.logo_file_path_edit.dialog_caption = dialog_caption = translate('OpenLP.AdvancedTab', 'Select Logo File')
|
self.logo_file_path_edit.dialog_caption = translate('OpenLP.AdvancedTab', 'Select Logo File')
|
||||||
self.logo_file_path_edit.filters = '{text};;{names} (*)'.format(
|
self.logo_file_path_edit.filters = '{text};;{names} (*)'.format(
|
||||||
text=get_images_filter(), names=UiStrings().AllFiles)
|
text=get_images_filter(), names=UiStrings().AllFiles)
|
||||||
|
|
||||||
|
111
openlp/core/ui/lib/filedialog.py
Executable file
111
openlp/core/ui/lib/filedialog.py
Executable file
@ -0,0 +1,111 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# OpenLP - Open Source Lyrics Projection #
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# Copyright (c) 2008-2017 OpenLP Developers #
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# This program is free software; you can redistribute it and/or modify it #
|
||||||
|
# under the terms of the GNU General Public License as published by the Free #
|
||||||
|
# Software Foundation; version 2 of the License. #
|
||||||
|
# #
|
||||||
|
# This program is distributed in the hope that it will be useful, but WITHOUT #
|
||||||
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
|
||||||
|
# more details. #
|
||||||
|
# #
|
||||||
|
# You should have received a copy of the GNU General Public License along #
|
||||||
|
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||||
|
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||||
|
###############################################################################
|
||||||
|
""" Patch the QFileDialog so it accepts and returns Path objects"""
|
||||||
|
from PyQt5 import QtWidgets
|
||||||
|
|
||||||
|
from openlp.core.common.path import Path, path_to_str, str_to_path
|
||||||
|
from openlp.core.lib import replace_params
|
||||||
|
|
||||||
|
|
||||||
|
class FileDialog(QtWidgets.QFileDialog):
|
||||||
|
@classmethod
|
||||||
|
def getExistingDirectory(cls, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Wraps `getExistingDirectory` so that it can be called with, and return Path objects
|
||||||
|
|
||||||
|
:type parent: QtWidgets.QWidget or None
|
||||||
|
:type caption: str
|
||||||
|
:type directory: openlp.core.common.path.Path
|
||||||
|
:type options: QtWidgets.QFileDialog.Options
|
||||||
|
:rtype: tuple[Path, str]
|
||||||
|
"""
|
||||||
|
args, kwargs = replace_params(args, kwargs, ((2, 'directory', path_to_str),))
|
||||||
|
|
||||||
|
return_value = super().getExistingDirectory(*args, **kwargs)
|
||||||
|
|
||||||
|
# getExistingDirectory returns a str that represents the path. The string is empty if the user cancels the
|
||||||
|
# dialog.
|
||||||
|
return str_to_path(return_value)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def getOpenFileName(cls, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Wraps `getOpenFileName` so that it can be called with, and return Path objects
|
||||||
|
|
||||||
|
:type parent: QtWidgets.QWidget or None
|
||||||
|
:type caption: str
|
||||||
|
:type directory: openlp.core.common.path.Path
|
||||||
|
:type filter: str
|
||||||
|
:type initialFilter: str
|
||||||
|
:type options: QtWidgets.QFileDialog.Options
|
||||||
|
:rtype: tuple[Path, str]
|
||||||
|
"""
|
||||||
|
args, kwargs = replace_params(args, kwargs, ((2, 'directory', path_to_str),))
|
||||||
|
|
||||||
|
file_name, selected_filter = super().getOpenFileName(*args, **kwargs)
|
||||||
|
|
||||||
|
# getOpenFileName returns a tuple. The first item is a str that represents the path. The string is empty if
|
||||||
|
# the user cancels the dialog.
|
||||||
|
return str_to_path(file_name), selected_filter
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def getOpenFileNames(cls, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Wraps `getOpenFileNames` so that it can be called with, and return Path objects
|
||||||
|
|
||||||
|
:type parent: QtWidgets.QWidget or None
|
||||||
|
:type caption: str
|
||||||
|
:type directory: openlp.core.common.path.Path
|
||||||
|
:type filter: str
|
||||||
|
:type initialFilter: str
|
||||||
|
:type options: QtWidgets.QFileDialog.Options
|
||||||
|
:rtype: tuple[list[Path], str]
|
||||||
|
"""
|
||||||
|
args, kwargs = replace_params(args, kwargs, ((2, 'directory', path_to_str),))
|
||||||
|
|
||||||
|
file_names, selected_filter = super().getOpenFileNames(*args, **kwargs)
|
||||||
|
|
||||||
|
# getSaveFileName returns a tuple. The first item is a list of str's that represents the path. The list is
|
||||||
|
# empty if the user cancels the dialog.
|
||||||
|
paths = [str_to_path(path) for path in file_names]
|
||||||
|
return paths, selected_filter
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def getSaveFileName(cls, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Wraps `getSaveFileName` so that it can be called with, and return Path objects
|
||||||
|
|
||||||
|
:type parent: QtWidgets.QWidget or None
|
||||||
|
:type caption: str
|
||||||
|
:type directory: openlp.core.common.path.Path
|
||||||
|
:type filter: str
|
||||||
|
:type initialFilter: str
|
||||||
|
:type options: QtWidgets.QFileDialog.Options
|
||||||
|
:rtype: tuple[Path or None, str]
|
||||||
|
"""
|
||||||
|
args, kwargs = replace_params(args, kwargs, ((2, 'directory', path_to_str),))
|
||||||
|
|
||||||
|
file_name, selected_filter = super().getSaveFileName(*args, **kwargs)
|
||||||
|
|
||||||
|
# getSaveFileName returns a tuple. The first item represents the path as a str. The string is empty if the user
|
||||||
|
# cancels the dialog.
|
||||||
|
return str_to_path(file_name), selected_filter
|
401
openlp/core/ui/lib/pathedit.py
Executable file → Normal file
401
openlp/core/ui/lib/pathedit.py
Executable file → Normal file
@ -1,205 +1,196 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
# OpenLP - Open Source Lyrics Projection #
|
# OpenLP - Open Source Lyrics Projection #
|
||||||
# --------------------------------------------------------------------------- #
|
# --------------------------------------------------------------------------- #
|
||||||
# Copyright (c) 2008-2017 OpenLP Developers #
|
# Copyright (c) 2008-2017 OpenLP Developers #
|
||||||
# --------------------------------------------------------------------------- #
|
# --------------------------------------------------------------------------- #
|
||||||
# This program is free software; you can redistribute it and/or modify it #
|
# 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 #
|
# under the terms of the GNU General Public License as published by the Free #
|
||||||
# Software Foundation; version 2 of the License. #
|
# Software Foundation; version 2 of the License. #
|
||||||
# #
|
# #
|
||||||
# This program is distributed in the hope that it will be useful, but WITHOUT #
|
# This program is distributed in the hope that it will be useful, but WITHOUT #
|
||||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
|
||||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
|
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
|
||||||
# more details. #
|
# more details. #
|
||||||
# #
|
# #
|
||||||
# You should have received a copy of the GNU General Public License along #
|
# You should have received a copy of the GNU General Public License along #
|
||||||
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||||
###############################################################################
|
###############################################################################
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
import os.path
|
|
||||||
|
from PyQt5 import QtCore, QtWidgets
|
||||||
from PyQt5 import QtCore, QtWidgets
|
|
||||||
|
from openlp.core.common import UiStrings, translate
|
||||||
from openlp.core.common import UiStrings, translate
|
from openlp.core.common.path import Path, path_to_str, str_to_path
|
||||||
from openlp.core.lib import build_icon
|
from openlp.core.lib import build_icon
|
||||||
|
from openlp.core.ui.lib.filedialog import FileDialog
|
||||||
|
|
||||||
class PathType(Enum):
|
|
||||||
Files = 1
|
class PathType(Enum):
|
||||||
Directories = 2
|
Files = 1
|
||||||
|
Directories = 2
|
||||||
|
|
||||||
class PathEdit(QtWidgets.QWidget):
|
|
||||||
"""
|
class PathEdit(QtWidgets.QWidget):
|
||||||
The :class:`~openlp.core.ui.lib.pathedit.PathEdit` class subclasses QWidget to create a custom widget for use when
|
"""
|
||||||
a file or directory needs to be selected.
|
The :class:`~openlp.core.ui.lib.pathedit.PathEdit` class subclasses QWidget to create a custom widget for use when
|
||||||
"""
|
a file or directory needs to be selected.
|
||||||
pathChanged = QtCore.pyqtSignal(str)
|
"""
|
||||||
|
pathChanged = QtCore.pyqtSignal(Path)
|
||||||
def __init__(self, parent=None, path_type=PathType.Files, default_path=None, dialog_caption=None, show_revert=True):
|
|
||||||
"""
|
def __init__(self, parent=None, path_type=PathType.Files, default_path=None, dialog_caption=None, show_revert=True):
|
||||||
Initalise the PathEdit widget
|
"""
|
||||||
|
Initialise the PathEdit widget
|
||||||
:param parent: The parent of the widget. This is just passed to the super method.
|
|
||||||
:type parent: QWidget or None
|
:param QtWidget.QWidget | None: The parent of the widget. This is just passed to the super method.
|
||||||
|
:param str dialog_caption: Used to customise the caption in the QFileDialog.
|
||||||
:param dialog_caption: Used to customise the caption in the QFileDialog.
|
:param openlp.core.common.path.Path default_path: The default path. This is set as the path when the revert
|
||||||
:param dialog_caption: str
|
button is clicked
|
||||||
|
:param bool show_revert: Used to determine if the 'revert button' should be visible.
|
||||||
:param default_path: The default path. This is set as the path when the revert button is clicked
|
:rtype: None
|
||||||
:type default_path: str
|
"""
|
||||||
|
super().__init__(parent)
|
||||||
:param show_revert: Used to determin if the 'revert button' should be visible.
|
self.default_path = default_path
|
||||||
:type show_revert: bool
|
self.dialog_caption = dialog_caption
|
||||||
|
self._path_type = path_type
|
||||||
:return: None
|
self._path = None
|
||||||
:rtype: None
|
self.filters = '{all_files} (*)'.format(all_files=UiStrings().AllFiles)
|
||||||
"""
|
self._setup(show_revert)
|
||||||
super().__init__(parent)
|
|
||||||
self.default_path = default_path
|
def _setup(self, show_revert):
|
||||||
self.dialog_caption = dialog_caption
|
"""
|
||||||
self._path_type = path_type
|
Set up the widget
|
||||||
self._path = None
|
:param bool show_revert: Show or hide the revert button
|
||||||
self.filters = '{all_files} (*)'.format(all_files=UiStrings().AllFiles)
|
:rtype: None
|
||||||
self._setup(show_revert)
|
"""
|
||||||
|
widget_layout = QtWidgets.QHBoxLayout()
|
||||||
def _setup(self, show_revert):
|
widget_layout.setContentsMargins(0, 0, 0, 0)
|
||||||
"""
|
self.line_edit = QtWidgets.QLineEdit(self)
|
||||||
Set up the widget
|
widget_layout.addWidget(self.line_edit)
|
||||||
:param show_revert: Show or hide the revert button
|
self.browse_button = QtWidgets.QToolButton(self)
|
||||||
:type show_revert: bool
|
self.browse_button.setIcon(build_icon(':/general/general_open.png'))
|
||||||
|
widget_layout.addWidget(self.browse_button)
|
||||||
:return: None
|
self.revert_button = QtWidgets.QToolButton(self)
|
||||||
:rtype: None
|
self.revert_button.setIcon(build_icon(':/general/general_revert.png'))
|
||||||
"""
|
self.revert_button.setVisible(show_revert)
|
||||||
widget_layout = QtWidgets.QHBoxLayout()
|
widget_layout.addWidget(self.revert_button)
|
||||||
widget_layout.setContentsMargins(0, 0, 0, 0)
|
self.setLayout(widget_layout)
|
||||||
self.line_edit = QtWidgets.QLineEdit(self)
|
# Signals and Slots
|
||||||
self.line_edit.setText(self._path)
|
self.browse_button.clicked.connect(self.on_browse_button_clicked)
|
||||||
widget_layout.addWidget(self.line_edit)
|
self.revert_button.clicked.connect(self.on_revert_button_clicked)
|
||||||
self.browse_button = QtWidgets.QToolButton(self)
|
self.line_edit.editingFinished.connect(self.on_line_edit_editing_finished)
|
||||||
self.browse_button.setIcon(build_icon(':/general/general_open.png'))
|
self.update_button_tool_tips()
|
||||||
widget_layout.addWidget(self.browse_button)
|
|
||||||
self.revert_button = QtWidgets.QToolButton(self)
|
@property
|
||||||
self.revert_button.setIcon(build_icon(':/general/general_revert.png'))
|
def path(self):
|
||||||
self.revert_button.setVisible(show_revert)
|
"""
|
||||||
widget_layout.addWidget(self.revert_button)
|
A property getter method to return the selected path.
|
||||||
self.setLayout(widget_layout)
|
|
||||||
# Signals and Slots
|
:return: The selected path
|
||||||
self.browse_button.clicked.connect(self.on_browse_button_clicked)
|
:rtype: openlp.core.common.path.Path
|
||||||
self.revert_button.clicked.connect(self.on_revert_button_clicked)
|
"""
|
||||||
self.line_edit.editingFinished.connect(self.on_line_edit_editing_finished)
|
return self._path
|
||||||
self.update_button_tool_tips()
|
|
||||||
|
@path.setter
|
||||||
@property
|
def path(self, path):
|
||||||
def path(self):
|
"""
|
||||||
"""
|
A Property setter method to set the selected path
|
||||||
A property getter method to return the selected path.
|
|
||||||
|
:param openlp.core.common.path.Path path: The path to set the widget to
|
||||||
:return: The selected path
|
:rtype: None
|
||||||
:rtype: str
|
"""
|
||||||
"""
|
self._path = path
|
||||||
return self._path
|
text = path_to_str(path)
|
||||||
|
self.line_edit.setText(text)
|
||||||
@path.setter
|
self.line_edit.setToolTip(text)
|
||||||
def path(self, path):
|
|
||||||
"""
|
@property
|
||||||
A Property setter method to set the selected path
|
def path_type(self):
|
||||||
|
"""
|
||||||
:param path: The path to set the widget to
|
A property getter method to return the path_type. Path type allows you to sepecify if the user is restricted to
|
||||||
:type path: str
|
selecting a file or directory.
|
||||||
"""
|
|
||||||
self._path = path
|
:return: The type selected
|
||||||
self.line_edit.setText(path)
|
:rtype: PathType
|
||||||
self.line_edit.setToolTip(path)
|
"""
|
||||||
|
return self._path_type
|
||||||
@property
|
|
||||||
def path_type(self):
|
@path_type.setter
|
||||||
"""
|
def path_type(self, path_type):
|
||||||
A property getter method to return the path_type. Path type allows you to sepecify if the user is restricted to
|
"""
|
||||||
selecting a file or directory.
|
A Property setter method to set the path type
|
||||||
|
|
||||||
:return: The type selected
|
:param PathType path_type: The type of path to select
|
||||||
:rtype: Enum of PathEdit
|
:rtype: None
|
||||||
"""
|
"""
|
||||||
return self._path_type
|
self._path_type = path_type
|
||||||
|
self.update_button_tool_tips()
|
||||||
@path_type.setter
|
|
||||||
def path_type(self, path_type):
|
def update_button_tool_tips(self):
|
||||||
"""
|
"""
|
||||||
A Property setter method to set the path type
|
Called to update the tooltips on the buttons. This is changing path types, and when the widget is initalised
|
||||||
|
|
||||||
:param path: The type of path to select
|
:rtype: None
|
||||||
:type path: Enum of PathEdit
|
"""
|
||||||
"""
|
if self._path_type == PathType.Directories:
|
||||||
self._path_type = path_type
|
self.browse_button.setToolTip(translate('OpenLP.PathEdit', 'Browse for directory.'))
|
||||||
self.update_button_tool_tips()
|
self.revert_button.setToolTip(translate('OpenLP.PathEdit', 'Revert to default directory.'))
|
||||||
|
else:
|
||||||
def update_button_tool_tips(self):
|
self.browse_button.setToolTip(translate('OpenLP.PathEdit', 'Browse for file.'))
|
||||||
"""
|
self.revert_button.setToolTip(translate('OpenLP.PathEdit', 'Revert to default file.'))
|
||||||
Called to update the tooltips on the buttons. This is changing path types, and when the widget is initalised
|
|
||||||
:return: None
|
def on_browse_button_clicked(self):
|
||||||
"""
|
"""
|
||||||
if self._path_type == PathType.Directories:
|
A handler to handle a click on the browse button.
|
||||||
self.browse_button.setToolTip(translate('OpenLP.PathEdit', 'Browse for directory.'))
|
|
||||||
self.revert_button.setToolTip(translate('OpenLP.PathEdit', 'Revert to default directory.'))
|
Show the QFileDialog and process the input from the user
|
||||||
else:
|
|
||||||
self.browse_button.setToolTip(translate('OpenLP.PathEdit', 'Browse for file.'))
|
:rtype: None
|
||||||
self.revert_button.setToolTip(translate('OpenLP.PathEdit', 'Revert to default file.'))
|
"""
|
||||||
|
caption = self.dialog_caption
|
||||||
def on_browse_button_clicked(self):
|
path = None
|
||||||
"""
|
if self._path_type == PathType.Directories:
|
||||||
A handler to handle a click on the browse button.
|
if not caption:
|
||||||
|
caption = translate('OpenLP.PathEdit', 'Select Directory')
|
||||||
Show the QFileDialog and process the input from the user
|
path = FileDialog.getExistingDirectory(self, caption, self._path, FileDialog.ShowDirsOnly)
|
||||||
:return: None
|
elif self._path_type == PathType.Files:
|
||||||
"""
|
if not caption:
|
||||||
caption = self.dialog_caption
|
caption = self.dialog_caption = translate('OpenLP.PathEdit', 'Select File')
|
||||||
path = ''
|
path, filter_used = FileDialog.getOpenFileName(self, caption, self._path, self.filters)
|
||||||
if self._path_type == PathType.Directories:
|
if path:
|
||||||
if not caption:
|
self.on_new_path(path)
|
||||||
caption = translate('OpenLP.PathEdit', 'Select Directory')
|
|
||||||
path = QtWidgets.QFileDialog.getExistingDirectory(self, caption,
|
def on_revert_button_clicked(self):
|
||||||
self._path, QtWidgets.QFileDialog.ShowDirsOnly)
|
"""
|
||||||
elif self._path_type == PathType.Files:
|
A handler to handle a click on the revert button.
|
||||||
if not caption:
|
|
||||||
caption = self.dialog_caption = translate('OpenLP.PathEdit', 'Select File')
|
Set the new path to the value of the default_path instance variable.
|
||||||
path, filter_used = QtWidgets.QFileDialog.getOpenFileName(self, caption, self._path, self.filters)
|
|
||||||
if path:
|
:rtype: None
|
||||||
path = os.path.normpath(path)
|
"""
|
||||||
self.on_new_path(path)
|
self.on_new_path(self.default_path)
|
||||||
|
|
||||||
def on_revert_button_clicked(self):
|
def on_line_edit_editing_finished(self):
|
||||||
"""
|
"""
|
||||||
A handler to handle a click on the revert button.
|
A handler to handle when the line edit has finished being edited.
|
||||||
|
|
||||||
Set the new path to the value of the default_path instance variable.
|
:rtype: None
|
||||||
:return: None
|
"""
|
||||||
"""
|
path = str_to_path(self.line_edit.text())
|
||||||
self.on_new_path(self.default_path)
|
self.on_new_path(path)
|
||||||
|
|
||||||
def on_line_edit_editing_finished(self):
|
def on_new_path(self, path):
|
||||||
"""
|
"""
|
||||||
A handler to handle when the line edit has finished being edited.
|
A method called to validate and set a new path.
|
||||||
:return: None
|
|
||||||
"""
|
Emits the pathChanged Signal
|
||||||
self.on_new_path(self.line_edit.text())
|
|
||||||
|
:param openlp.core.common.path.Path path: The new path
|
||||||
def on_new_path(self, path):
|
:rtype: None
|
||||||
"""
|
"""
|
||||||
A method called to validate and set a new path.
|
if self._path != path:
|
||||||
|
self.path = path
|
||||||
Emits the pathChanged Signal
|
self.pathChanged.emit(path)
|
||||||
|
|
||||||
:param path: The new path
|
|
||||||
:type path: str
|
|
||||||
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
if self._path != path:
|
|
||||||
self.path = path
|
|
||||||
self.pathChanged.emit(path)
|
|
||||||
|
@ -25,11 +25,12 @@ The :mod:``wizard`` module provides generic wizard tools for OpenLP.
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from PyQt5 import QtGui, QtWidgets
|
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||||
|
|
||||||
from openlp.core.common import Registry, RegistryProperties, Settings, UiStrings, translate, is_macosx
|
from openlp.core.common import Registry, RegistryProperties, Settings, UiStrings, translate, is_macosx
|
||||||
from openlp.core.lib import build_icon
|
from openlp.core.lib import build_icon
|
||||||
from openlp.core.lib.ui import add_welcome_page
|
from openlp.core.lib.ui import add_welcome_page
|
||||||
|
from openlp.core.ui.lib.filedialog import FileDialog
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -50,13 +51,13 @@ class WizardStrings(object):
|
|||||||
# These strings should need a good reason to be retranslated elsewhere.
|
# These strings should need a good reason to be retranslated elsewhere.
|
||||||
FinishedImport = translate('OpenLP.Ui', 'Finished import.')
|
FinishedImport = translate('OpenLP.Ui', 'Finished import.')
|
||||||
FormatLabel = translate('OpenLP.Ui', 'Format:')
|
FormatLabel = translate('OpenLP.Ui', 'Format:')
|
||||||
HeaderStyle = '<span style="font-size:14pt; font-weight:600;">%s</span>'
|
HeaderStyle = '<span style="font-size:14pt; font-weight:600;">{text}</span>'
|
||||||
Importing = translate('OpenLP.Ui', 'Importing')
|
Importing = translate('OpenLP.Ui', 'Importing')
|
||||||
ImportingType = translate('OpenLP.Ui', 'Importing "%s"...')
|
ImportingType = translate('OpenLP.Ui', 'Importing "{source}"...')
|
||||||
ImportSelect = translate('OpenLP.Ui', 'Select Import Source')
|
ImportSelect = translate('OpenLP.Ui', 'Select Import Source')
|
||||||
ImportSelectLong = translate('OpenLP.Ui', 'Select the import format and the location to import from.')
|
ImportSelectLong = translate('OpenLP.Ui', 'Select the import format and the location to import from.')
|
||||||
OpenTypeFile = translate('OpenLP.Ui', 'Open %s File')
|
OpenTypeFile = translate('OpenLP.Ui', 'Open {file_type} File')
|
||||||
OpenTypeFolder = translate('OpenLP.Ui', 'Open %s Folder')
|
OpenTypeFolder = translate('OpenLP.Ui', 'Open {folder_name} Folder')
|
||||||
PercentSymbolFormat = translate('OpenLP.Ui', '%p%')
|
PercentSymbolFormat = translate('OpenLP.Ui', '%p%')
|
||||||
Ready = translate('OpenLP.Ui', 'Ready.')
|
Ready = translate('OpenLP.Ui', 'Ready.')
|
||||||
StartingImport = translate('OpenLP.Ui', 'Starting import...')
|
StartingImport = translate('OpenLP.Ui', 'Starting import...')
|
||||||
@ -93,7 +94,10 @@ class OpenLPWizard(QtWidgets.QWizard, RegistryProperties):
|
|||||||
"""
|
"""
|
||||||
Constructor
|
Constructor
|
||||||
"""
|
"""
|
||||||
super(OpenLPWizard, self).__init__(parent)
|
# QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint remove the "?" buttons from windows,
|
||||||
|
# QtCore.Qt.WindowCloseButtonHint enables the "x" button to close these windows.
|
||||||
|
super(OpenLPWizard, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint |
|
||||||
|
QtCore.Qt.WindowCloseButtonHint)
|
||||||
self.plugin = plugin
|
self.plugin = plugin
|
||||||
self.with_progress_page = add_progress_page
|
self.with_progress_page = add_progress_page
|
||||||
self.setFixedWidth(640)
|
self.setFixedWidth(640)
|
||||||
@ -275,37 +279,38 @@ class OpenLPWizard(QtWidgets.QWizard, RegistryProperties):
|
|||||||
|
|
||||||
def get_file_name(self, title, editbox, setting_name, filters=''):
|
def get_file_name(self, title, editbox, setting_name, filters=''):
|
||||||
"""
|
"""
|
||||||
Opens a QFileDialog and saves the filename to the given editbox.
|
Opens a FileDialog and saves the filename to the given editbox.
|
||||||
|
|
||||||
:param title: The title of the dialog (unicode).
|
:param str title: The title of the dialog.
|
||||||
:param editbox: An editbox (QLineEdit).
|
:param QtWidgets.QLineEdit editbox: An QLineEdit.
|
||||||
:param setting_name: The place where to save the last opened directory.
|
:param str setting_name: The place where to save the last opened directory.
|
||||||
:param filters: The file extension filters. It should contain the file description
|
:param str filters: The file extension filters. It should contain the file description
|
||||||
as well as the file extension. For example::
|
as well as the file extension. For example::
|
||||||
|
|
||||||
'OpenLP 2 Databases (*.sqlite)'
|
'OpenLP 2 Databases (*.sqlite)'
|
||||||
|
:rtype: None
|
||||||
"""
|
"""
|
||||||
if filters:
|
if filters:
|
||||||
filters += ';;'
|
filters += ';;'
|
||||||
filters += '%s (*)' % UiStrings().AllFiles
|
filters += '%s (*)' % UiStrings().AllFiles
|
||||||
filename, filter_used = QtWidgets.QFileDialog.getOpenFileName(
|
file_path, filter_used = FileDialog.getOpenFileName(
|
||||||
self, title, os.path.dirname(Settings().value(self.plugin.settings_section + '/' + setting_name)),
|
self, title, Settings().value(self.plugin.settings_section + '/' + setting_name), filters)
|
||||||
filters)
|
if file_path:
|
||||||
if filename:
|
editbox.setText(str(file_path))
|
||||||
editbox.setText(filename)
|
Settings().setValue(self.plugin.settings_section + '/' + setting_name, file_path.parent)
|
||||||
Settings().setValue(self.plugin.settings_section + '/' + setting_name, filename)
|
|
||||||
|
|
||||||
def get_folder(self, title, editbox, setting_name):
|
def get_folder(self, title, editbox, setting_name):
|
||||||
"""
|
"""
|
||||||
Opens a QFileDialog and saves the selected folder to the given editbox.
|
Opens a FileDialog and saves the selected folder to the given editbox.
|
||||||
|
|
||||||
:param title: The title of the dialog (unicode).
|
:param str title: The title of the dialog.
|
||||||
:param editbox: An editbox (QLineEdit).
|
:param QtWidgets.QLineEdit editbox: An QLineEditbox.
|
||||||
:param setting_name: The place where to save the last opened directory.
|
:param str setting_name: The place where to save the last opened directory.
|
||||||
|
:rtype: None
|
||||||
"""
|
"""
|
||||||
folder = QtWidgets.QFileDialog.getExistingDirectory(
|
folder_path = FileDialog.getExistingDirectory(
|
||||||
self, title, Settings().value(self.plugin.settings_section + '/' + setting_name),
|
self, title, Settings().value(self.plugin.settings_section + '/' + setting_name),
|
||||||
QtWidgets.QFileDialog.ShowDirsOnly)
|
QtWidgets.QFileDialog.ShowDirsOnly)
|
||||||
if folder:
|
if folder_path:
|
||||||
editbox.setText(folder)
|
editbox.setText(str(folder_path))
|
||||||
Settings().setValue(self.plugin.settings_section + '/' + setting_name, folder)
|
Settings().setValue(self.plugin.settings_section + '/' + setting_name, folder_path)
|
||||||
|
@ -37,6 +37,7 @@ from PyQt5 import QtCore, QtWidgets, QtWebKit, QtWebKitWidgets, QtGui, QtMultime
|
|||||||
|
|
||||||
from openlp.core.common import AppLocation, Registry, RegistryProperties, OpenLPMixin, Settings, translate,\
|
from openlp.core.common import AppLocation, Registry, RegistryProperties, OpenLPMixin, Settings, translate,\
|
||||||
is_macosx, is_win
|
is_macosx, is_win
|
||||||
|
from openlp.core.common.path import path_to_str
|
||||||
from openlp.core.lib import ServiceItem, ImageSource, ScreenList, build_html, expand_tags, image_to_byte
|
from openlp.core.lib import ServiceItem, ImageSource, ScreenList, build_html, expand_tags, image_to_byte
|
||||||
from openlp.core.lib.theme import BackgroundType
|
from openlp.core.lib.theme import BackgroundType
|
||||||
from openlp.core.ui import HideMode, AlertLocation, DisplayControllerType
|
from openlp.core.ui import HideMode, AlertLocation, DisplayControllerType
|
||||||
@ -157,7 +158,7 @@ class MainDisplay(OpenLPMixin, Display, RegistryProperties):
|
|||||||
# platforms. For OpenLP 2.0 keep it only for OS X to not cause any
|
# platforms. For OpenLP 2.0 keep it only for OS X to not cause any
|
||||||
# regressions on other platforms.
|
# regressions on other platforms.
|
||||||
if is_macosx():
|
if is_macosx():
|
||||||
window_flags = QtCore.Qt.FramelessWindowHint | QtCore.Qt.Window
|
window_flags = QtCore.Qt.FramelessWindowHint | QtCore.Qt.Window | QtCore.Qt.NoDropShadowWindowHint
|
||||||
self.setWindowFlags(window_flags)
|
self.setWindowFlags(window_flags)
|
||||||
self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
|
self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
|
||||||
self.set_transparency(False)
|
self.set_transparency(False)
|
||||||
@ -259,7 +260,7 @@ class MainDisplay(OpenLPMixin, Display, RegistryProperties):
|
|||||||
background_color.setNamedColor(Settings().value('core/logo background color'))
|
background_color.setNamedColor(Settings().value('core/logo background color'))
|
||||||
if not background_color.isValid():
|
if not background_color.isValid():
|
||||||
background_color = QtCore.Qt.white
|
background_color = QtCore.Qt.white
|
||||||
image_file = Settings().value('core/logo file')
|
image_file = path_to_str(Settings().value('core/logo file'))
|
||||||
splash_image = QtGui.QImage(image_file)
|
splash_image = QtGui.QImage(image_file)
|
||||||
self.initial_fame = QtGui.QImage(
|
self.initial_fame = QtGui.QImage(
|
||||||
self.screen['size'].width(),
|
self.screen['size'].width(),
|
||||||
@ -484,7 +485,7 @@ class MainDisplay(OpenLPMixin, Display, RegistryProperties):
|
|||||||
service_item = ServiceItem()
|
service_item = ServiceItem()
|
||||||
service_item.title = 'webkit'
|
service_item.title = 'webkit'
|
||||||
service_item.processor = 'webkit'
|
service_item.processor = 'webkit'
|
||||||
path = os.path.join(AppLocation.get_section_data_path('themes'),
|
path = os.path.join(str(AppLocation.get_section_data_path('themes')),
|
||||||
self.service_item.theme_data.theme_name)
|
self.service_item.theme_data.theme_name)
|
||||||
service_item.add_from_command(path,
|
service_item.add_from_command(path,
|
||||||
self.service_item.theme_data.background_filename,
|
self.service_item.theme_data.background_filename,
|
||||||
@ -689,7 +690,7 @@ class AudioPlayer(OpenLPMixin, QtCore.QObject):
|
|||||||
"""
|
"""
|
||||||
Skip forward to the next track in the list
|
Skip forward to the next track in the list
|
||||||
"""
|
"""
|
||||||
self.playerlist.next()
|
self.playlist.next()
|
||||||
|
|
||||||
def go_to(self, index):
|
def go_to(self, index):
|
||||||
"""
|
"""
|
||||||
|
@ -34,12 +34,15 @@ from tempfile import gettempdir
|
|||||||
|
|
||||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||||
|
|
||||||
from openlp.core.common import Registry, RegistryProperties, AppLocation, LanguageManager, Settings, \
|
from openlp.core.api import websockets
|
||||||
|
from openlp.core.api.http import server
|
||||||
|
from openlp.core.common import Registry, RegistryProperties, AppLocation, LanguageManager, Settings, UiStrings, \
|
||||||
check_directory_exists, translate, is_win, is_macosx, add_actions
|
check_directory_exists, translate, is_win, is_macosx, add_actions
|
||||||
from openlp.core.common.actions import ActionList, CategoryOrder
|
from openlp.core.common.actions import ActionList, CategoryOrder
|
||||||
|
from openlp.core.common.path import Path, path_to_str, str_to_path
|
||||||
from openlp.core.common.versionchecker import get_application_version
|
from openlp.core.common.versionchecker import get_application_version
|
||||||
from openlp.core.lib import Renderer, PluginManager, ImageManager, PluginStatus, ScreenList, build_icon
|
from openlp.core.lib import Renderer, PluginManager, ImageManager, PluginStatus, ScreenList, build_icon
|
||||||
from openlp.core.lib.ui import UiStrings, create_action
|
from openlp.core.lib.ui import create_action
|
||||||
from openlp.core.ui import AboutForm, SettingsForm, ServiceManager, ThemeManager, LiveController, PluginForm, \
|
from openlp.core.ui import AboutForm, SettingsForm, ServiceManager, ThemeManager, LiveController, PluginForm, \
|
||||||
ShortcutListForm, FormattingTagForm, PreviewController
|
ShortcutListForm, FormattingTagForm, PreviewController
|
||||||
from openlp.core.ui.firsttimeform import FirstTimeForm
|
from openlp.core.ui.firsttimeform import FirstTimeForm
|
||||||
@ -47,8 +50,10 @@ from openlp.core.ui.media import MediaController
|
|||||||
from openlp.core.ui.printserviceform import PrintServiceForm
|
from openlp.core.ui.printserviceform import PrintServiceForm
|
||||||
from openlp.core.ui.projector.manager import ProjectorManager
|
from openlp.core.ui.projector.manager import ProjectorManager
|
||||||
from openlp.core.ui.lib.dockwidget import OpenLPDockWidget
|
from openlp.core.ui.lib.dockwidget import OpenLPDockWidget
|
||||||
|
from openlp.core.ui.lib.filedialog import FileDialog
|
||||||
from openlp.core.ui.lib.mediadockmanager import MediaDockManager
|
from openlp.core.ui.lib.mediadockmanager import MediaDockManager
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
MEDIA_MANAGER_STYLE = """
|
MEDIA_MANAGER_STYLE = """
|
||||||
@ -305,9 +310,9 @@ class Ui_MainWindow(object):
|
|||||||
# Give QT Extra Hint that this is an About Menu Item
|
# Give QT Extra Hint that this is an About Menu Item
|
||||||
self.about_item.setMenuRole(QtWidgets.QAction.AboutRole)
|
self.about_item.setMenuRole(QtWidgets.QAction.AboutRole)
|
||||||
if is_win():
|
if is_win():
|
||||||
self.local_help_file = os.path.join(AppLocation.get_directory(AppLocation.AppDir), 'OpenLP.chm')
|
self.local_help_file = os.path.join(str(AppLocation.get_directory(AppLocation.AppDir)), 'OpenLP.chm')
|
||||||
elif is_macosx():
|
elif is_macosx():
|
||||||
self.local_help_file = os.path.join(AppLocation.get_directory(AppLocation.AppDir),
|
self.local_help_file = os.path.join(str(AppLocation.get_directory(AppLocation.AppDir)),
|
||||||
'..', 'Resources', 'OpenLP.help')
|
'..', 'Resources', 'OpenLP.help')
|
||||||
self.user_manual_item = create_action(main_window, 'userManualItem', icon=':/system/system_help_contents.png',
|
self.user_manual_item = create_action(main_window, 'userManualItem', icon=':/system/system_help_contents.png',
|
||||||
can_shortcuts=True, category=UiStrings().Help,
|
can_shortcuts=True, category=UiStrings().Help,
|
||||||
@ -370,7 +375,7 @@ class Ui_MainWindow(object):
|
|||||||
"""
|
"""
|
||||||
Set up the translation system
|
Set up the translation system
|
||||||
"""
|
"""
|
||||||
main_window.setWindowTitle(UiStrings().OLP)
|
main_window.setWindowTitle(UiStrings().OpenLP)
|
||||||
self.file_menu.setTitle(translate('OpenLP.MainWindow', '&File'))
|
self.file_menu.setTitle(translate('OpenLP.MainWindow', '&File'))
|
||||||
self.file_import_menu.setTitle(translate('OpenLP.MainWindow', '&Import'))
|
self.file_import_menu.setTitle(translate('OpenLP.MainWindow', '&Import'))
|
||||||
self.file_export_menu.setTitle(translate('OpenLP.MainWindow', '&Export'))
|
self.file_export_menu.setTitle(translate('OpenLP.MainWindow', '&Export'))
|
||||||
@ -513,6 +518,9 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
|
|||||||
Settings().set_up_default_values()
|
Settings().set_up_default_values()
|
||||||
self.about_form = AboutForm(self)
|
self.about_form = AboutForm(self)
|
||||||
MediaController()
|
MediaController()
|
||||||
|
if Registry().get_flag('no_web_server'):
|
||||||
|
websockets.WebSocketServer()
|
||||||
|
server.HttpServer()
|
||||||
SettingsForm(self)
|
SettingsForm(self)
|
||||||
self.formatting_tag_form = FormattingTagForm(self)
|
self.formatting_tag_form = FormattingTagForm(self)
|
||||||
self.shortcut_form = ShortcutListForm(self)
|
self.shortcut_form = ShortcutListForm(self)
|
||||||
@ -540,7 +548,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
|
|||||||
self.tools_first_time_wizard.triggered.connect(self.on_first_time_wizard_clicked)
|
self.tools_first_time_wizard.triggered.connect(self.on_first_time_wizard_clicked)
|
||||||
self.update_theme_images.triggered.connect(self.on_update_theme_images)
|
self.update_theme_images.triggered.connect(self.on_update_theme_images)
|
||||||
self.formatting_tag_item.triggered.connect(self.on_formatting_tag_item_clicked)
|
self.formatting_tag_item.triggered.connect(self.on_formatting_tag_item_clicked)
|
||||||
self.settings_configure_item.triggered.connect(self.on_settings_configure_iem_clicked)
|
self.settings_configure_item.triggered.connect(self.on_settings_configure_item_clicked)
|
||||||
self.settings_shortcuts_item.triggered.connect(self.on_settings_shortcuts_item_clicked)
|
self.settings_shortcuts_item.triggered.connect(self.on_settings_shortcuts_item_clicked)
|
||||||
self.settings_import_item.triggered.connect(self.on_settings_import_item_clicked)
|
self.settings_import_item.triggered.connect(self.on_settings_import_item_clicked)
|
||||||
self.settings_export_item.triggered.connect(self.on_settings_export_item_clicked)
|
self.settings_export_item.triggered.connect(self.on_settings_export_item_clicked)
|
||||||
@ -788,7 +796,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
|
|||||||
"""
|
"""
|
||||||
Open data folder
|
Open data folder
|
||||||
"""
|
"""
|
||||||
path = AppLocation.get_data_path()
|
path = str(AppLocation.get_data_path())
|
||||||
QtGui.QDesktopServices.openUrl(QtCore.QUrl.fromLocalFile(path))
|
QtGui.QDesktopServices.openUrl(QtCore.QUrl.fromLocalFile(path))
|
||||||
|
|
||||||
def on_update_theme_images(self):
|
def on_update_theme_images(self):
|
||||||
@ -803,7 +811,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
|
|||||||
"""
|
"""
|
||||||
self.formatting_tag_form.exec()
|
self.formatting_tag_form.exec()
|
||||||
|
|
||||||
def on_settings_configure_iem_clicked(self):
|
def on_settings_configure_item_clicked(self):
|
||||||
"""
|
"""
|
||||||
Show the Settings dialog
|
Show the Settings dialog
|
||||||
"""
|
"""
|
||||||
@ -864,16 +872,17 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
|
|||||||
setting_sections.extend([plugin.name for plugin in self.plugin_manager.plugins])
|
setting_sections.extend([plugin.name for plugin in self.plugin_manager.plugins])
|
||||||
# Copy the settings file to the tmp dir, because we do not want to change the original one.
|
# Copy the settings file to the tmp dir, because we do not want to change the original one.
|
||||||
temp_directory = os.path.join(str(gettempdir()), 'openlp')
|
temp_directory = os.path.join(str(gettempdir()), 'openlp')
|
||||||
check_directory_exists(temp_directory)
|
check_directory_exists(Path(temp_directory))
|
||||||
temp_config = os.path.join(temp_directory, os.path.basename(import_file_name))
|
temp_config = os.path.join(temp_directory, os.path.basename(import_file_name))
|
||||||
shutil.copyfile(import_file_name, temp_config)
|
shutil.copyfile(import_file_name, temp_config)
|
||||||
settings = Settings()
|
settings = Settings()
|
||||||
import_settings = Settings(temp_config, Settings.IniFormat)
|
import_settings = Settings(temp_config, Settings.IniFormat)
|
||||||
# Convert image files
|
|
||||||
log.info('hook upgrade_plugin_settings')
|
log.info('hook upgrade_plugin_settings')
|
||||||
self.plugin_manager.hook_upgrade_plugin_settings(import_settings)
|
self.plugin_manager.hook_upgrade_plugin_settings(import_settings)
|
||||||
# Remove/rename old settings to prepare the import.
|
# Upgrade settings to prepare the import.
|
||||||
import_settings.remove_obsolete_settings()
|
if import_settings.can_upgrade():
|
||||||
|
import_settings.upgrade_settings()
|
||||||
# Lets do a basic sanity check. If it contains this string we can assume it was created by OpenLP and so we'll
|
# Lets do a basic sanity check. If it contains this string we can assume it was created by OpenLP and so we'll
|
||||||
# load what we can from it, and just silently ignore anything we don't recognise.
|
# load what we can from it, and just silently ignore anything we don't recognise.
|
||||||
if import_settings.value('SettingsImport/type') != 'OpenLP_settings_export':
|
if import_settings.value('SettingsImport/type') != 'OpenLP_settings_export':
|
||||||
@ -920,8 +929,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
|
|||||||
QtWidgets.QMessageBox.information(self, translate('OpenLP.MainWindow', 'Import settings'),
|
QtWidgets.QMessageBox.information(self, translate('OpenLP.MainWindow', 'Import settings'),
|
||||||
translate('OpenLP.MainWindow',
|
translate('OpenLP.MainWindow',
|
||||||
'OpenLP will now close. Imported settings will '
|
'OpenLP will now close. Imported settings will '
|
||||||
'be applied the next time you start OpenLP.'),
|
'be applied the next time you start OpenLP.'))
|
||||||
QtWidgets.QMessageBox.StandardButtons(QtWidgets.QMessageBox.Ok))
|
|
||||||
self.settings_imported = True
|
self.settings_imported = True
|
||||||
self.clean_up()
|
self.clean_up()
|
||||||
QtCore.QCoreApplication.exit()
|
QtCore.QCoreApplication.exit()
|
||||||
@ -930,89 +938,17 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
|
|||||||
"""
|
"""
|
||||||
Export settings to a .conf file in INI format
|
Export settings to a .conf file in INI format
|
||||||
"""
|
"""
|
||||||
export_file_name, filter_used = QtWidgets.QFileDialog.getSaveFileName(
|
export_file_path, filter_used = FileDialog.getSaveFileName(
|
||||||
self,
|
self,
|
||||||
translate('OpenLP.MainWindow', 'Export Settings File'),
|
translate('OpenLP.MainWindow', 'Export Settings File'),
|
||||||
'',
|
None,
|
||||||
translate('OpenLP.MainWindow', 'OpenLP Settings (*.conf)'))
|
translate('OpenLP.MainWindow', 'OpenLP Settings (*.conf)'))
|
||||||
if not export_file_name:
|
if not export_file_path:
|
||||||
return
|
return
|
||||||
# Make sure it's a .conf file.
|
# Make sure it's a .conf file.
|
||||||
if not export_file_name.endswith('conf'):
|
export_file_path = export_file_path.with_suffix('.conf')
|
||||||
export_file_name += '.conf'
|
|
||||||
temp_file = os.path.join(gettempdir(), 'openlp', 'exportConf.tmp')
|
|
||||||
self.save_settings()
|
self.save_settings()
|
||||||
setting_sections = []
|
Settings().export(export_file_path)
|
||||||
# Add main sections.
|
|
||||||
setting_sections.extend([self.general_settings_section])
|
|
||||||
setting_sections.extend([self.advanced_settings_section])
|
|
||||||
setting_sections.extend([self.ui_settings_section])
|
|
||||||
setting_sections.extend([self.shortcuts_settings_section])
|
|
||||||
setting_sections.extend([self.service_manager_settings_section])
|
|
||||||
setting_sections.extend([self.themes_settings_section])
|
|
||||||
setting_sections.extend([self.display_tags_section])
|
|
||||||
# Add plugin sections.
|
|
||||||
for plugin in self.plugin_manager.plugins:
|
|
||||||
setting_sections.extend([plugin.name])
|
|
||||||
# Delete old files if found.
|
|
||||||
if os.path.exists(temp_file):
|
|
||||||
os.remove(temp_file)
|
|
||||||
if os.path.exists(export_file_name):
|
|
||||||
os.remove(export_file_name)
|
|
||||||
settings = Settings()
|
|
||||||
settings.remove(self.header_section)
|
|
||||||
# Get the settings.
|
|
||||||
keys = settings.allKeys()
|
|
||||||
export_settings = Settings(temp_file, Settings.IniFormat)
|
|
||||||
# Add a header section.
|
|
||||||
# This is to insure it's our conf file for import.
|
|
||||||
now = datetime.now()
|
|
||||||
application_version = get_application_version()
|
|
||||||
# Write INI format using Qsettings.
|
|
||||||
# Write our header.
|
|
||||||
export_settings.beginGroup(self.header_section)
|
|
||||||
export_settings.setValue('Make_Changes', 'At_Own_RISK')
|
|
||||||
export_settings.setValue('type', 'OpenLP_settings_export')
|
|
||||||
export_settings.setValue('file_date_created', now.strftime("%Y-%m-%d %H:%M"))
|
|
||||||
export_settings.setValue('version', application_version['full'])
|
|
||||||
export_settings.endGroup()
|
|
||||||
# Write all the sections and keys.
|
|
||||||
for section_key in keys:
|
|
||||||
# FIXME: We are conflicting with the standard "General" section.
|
|
||||||
if 'eneral' in section_key:
|
|
||||||
section_key = section_key.lower()
|
|
||||||
try:
|
|
||||||
key_value = settings.value(section_key)
|
|
||||||
except KeyError:
|
|
||||||
QtWidgets.QMessageBox.critical(self, translate('OpenLP.MainWindow', 'Export setting error'),
|
|
||||||
translate('OpenLP.MainWindow', 'The key "{key}" does not have a default '
|
|
||||||
'value so it will be skipped in this '
|
|
||||||
'export.').format(key=section_key),
|
|
||||||
QtWidgets.QMessageBox.StandardButtons(QtWidgets.QMessageBox.Ok))
|
|
||||||
key_value = None
|
|
||||||
if key_value is not None:
|
|
||||||
export_settings.setValue(section_key, key_value)
|
|
||||||
export_settings.sync()
|
|
||||||
# Temp CONF file has been written. Blanks in keys are now '%20'.
|
|
||||||
# Read the temp file and output the user's CONF file with blanks to
|
|
||||||
# make it more readable.
|
|
||||||
temp_conf = open(temp_file, 'r')
|
|
||||||
try:
|
|
||||||
export_conf = open(export_file_name, 'w')
|
|
||||||
for file_record in temp_conf:
|
|
||||||
# Get rid of any invalid entries.
|
|
||||||
if file_record.find('@Invalid()') == -1:
|
|
||||||
file_record = file_record.replace('%20', ' ')
|
|
||||||
export_conf.write(file_record)
|
|
||||||
temp_conf.close()
|
|
||||||
export_conf.close()
|
|
||||||
os.remove(temp_file)
|
|
||||||
except OSError as ose:
|
|
||||||
QtWidgets.QMessageBox.critical(self, translate('OpenLP.MainWindow', 'Export setting error'),
|
|
||||||
translate('OpenLP.MainWindow',
|
|
||||||
'An error occurred while exporting the '
|
|
||||||
'settings: {err}').format(err=ose.strerror),
|
|
||||||
QtWidgets.QMessageBox.StandardButtons(QtWidgets.QMessageBox.Ok))
|
|
||||||
|
|
||||||
def on_mode_default_item_clicked(self):
|
def on_mode_default_item_clicked(self):
|
||||||
"""
|
"""
|
||||||
@ -1151,9 +1087,9 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
|
|||||||
:param file_name: The file name of the service file.
|
:param file_name: The file name of the service file.
|
||||||
"""
|
"""
|
||||||
if modified:
|
if modified:
|
||||||
title = '{title} - {name}*'.format(title=UiStrings().OLP, name=file_name)
|
title = '{title} - {name}*'.format(title=UiStrings().OpenLP, name=file_name)
|
||||||
else:
|
else:
|
||||||
title = '{title} - {name}'.format(title=UiStrings().OLP, name=file_name)
|
title = '{title} - {name}'.format(title=UiStrings().OpenLP, name=file_name)
|
||||||
self.setWindowTitle(title)
|
self.setWindowTitle(title)
|
||||||
|
|
||||||
def show_status_message(self, message):
|
def show_status_message(self, message):
|
||||||
@ -1271,7 +1207,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
|
|||||||
settings.remove('custom slide')
|
settings.remove('custom slide')
|
||||||
settings.remove('service')
|
settings.remove('service')
|
||||||
settings.beginGroup(self.general_settings_section)
|
settings.beginGroup(self.general_settings_section)
|
||||||
self.recent_files = settings.value('recent files')
|
self.recent_files = [path_to_str(file_path) for file_path in settings.value('recent files')]
|
||||||
settings.endGroup()
|
settings.endGroup()
|
||||||
settings.beginGroup(self.ui_settings_section)
|
settings.beginGroup(self.ui_settings_section)
|
||||||
self.move(settings.value('main window position'))
|
self.move(settings.value('main window position'))
|
||||||
@ -1295,7 +1231,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
|
|||||||
log.debug('Saving QSettings')
|
log.debug('Saving QSettings')
|
||||||
settings = Settings()
|
settings = Settings()
|
||||||
settings.beginGroup(self.general_settings_section)
|
settings.beginGroup(self.general_settings_section)
|
||||||
settings.setValue('recent files', self.recent_files)
|
settings.setValue('recent files', [str_to_path(file) for file in self.recent_files])
|
||||||
settings.endGroup()
|
settings.endGroup()
|
||||||
settings.beginGroup(self.ui_settings_section)
|
settings.beginGroup(self.ui_settings_section)
|
||||||
settings.setValue('main window position', self.pos())
|
settings.setValue('main window position', self.pos())
|
||||||
@ -1316,7 +1252,6 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
|
|||||||
self.recent_files_menu.clear()
|
self.recent_files_menu.clear()
|
||||||
for file_id, filename in enumerate(recent_files_to_display):
|
for file_id, filename in enumerate(recent_files_to_display):
|
||||||
log.debug('Recent file name: {name}'.format(name=filename))
|
log.debug('Recent file name: {name}'.format(name=filename))
|
||||||
# TODO: Should be good
|
|
||||||
action = create_action(self, '',
|
action = create_action(self, '',
|
||||||
text='&{n} {name}'.format(n=file_id + 1,
|
text='&{n} {name}'.format(n=file_id + 1,
|
||||||
name=os.path.splitext(os.path.basename(str(filename)))[0]),
|
name=os.path.splitext(os.path.basename(str(filename)))[0]),
|
||||||
@ -1438,9 +1373,9 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
|
|||||||
log.info('No data copy requested')
|
log.info('No data copy requested')
|
||||||
# Change the location of data directory in config file.
|
# Change the location of data directory in config file.
|
||||||
settings = QtCore.QSettings()
|
settings = QtCore.QSettings()
|
||||||
settings.setValue('advanced/data path', self.new_data_path)
|
settings.setValue('advanced/data path', Path(self.new_data_path))
|
||||||
# Check if the new data path is our default.
|
# Check if the new data path is our default.
|
||||||
if self.new_data_path == AppLocation.get_directory(AppLocation.DataDir):
|
if self.new_data_path == str(AppLocation.get_directory(AppLocation.DataDir)):
|
||||||
settings.remove('advanced/data path')
|
settings.remove('advanced/data path')
|
||||||
self.application.set_normal_cursor()
|
self.application.set_normal_cursor()
|
||||||
|
|
||||||
|
@ -146,5 +146,6 @@ def format_milliseconds(milliseconds):
|
|||||||
|
|
||||||
from .mediacontroller import MediaController
|
from .mediacontroller import MediaController
|
||||||
from .playertab import PlayerTab
|
from .playertab import PlayerTab
|
||||||
|
from .endpoint import media_endpoint
|
||||||
|
|
||||||
__all__ = ['MediaController', 'PlayerTab']
|
__all__ = ['MediaController', 'PlayerTab']
|
||||||
|
@ -19,40 +19,54 @@
|
|||||||
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||||
###############################################################################
|
###############################################################################
|
||||||
"""
|
|
||||||
Provide a work around for a bug in QFileDialog <https://bugs.launchpad.net/openlp/+bug/1209515>
|
|
||||||
"""
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
|
||||||
from urllib import parse
|
|
||||||
|
|
||||||
from PyQt5 import QtWidgets
|
from openlp.core.api.http.endpoint import Endpoint
|
||||||
|
from openlp.core.api.http import requires_auth
|
||||||
|
from openlp.core.common import Registry
|
||||||
|
|
||||||
from openlp.core.common import UiStrings
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
media_endpoint = Endpoint('media')
|
||||||
|
|
||||||
class FileDialog(QtWidgets.QFileDialog):
|
|
||||||
|
@media_endpoint.route('play')
|
||||||
|
@requires_auth
|
||||||
|
def media_play(request):
|
||||||
"""
|
"""
|
||||||
Subclass QFileDialog to work round a bug
|
Handles requests for playing media
|
||||||
|
|
||||||
|
:param request: The http request object.
|
||||||
"""
|
"""
|
||||||
@staticmethod
|
media = Registry().get('media_controller')
|
||||||
def getOpenFileNames(parent, *args, **kwargs):
|
live = Registry().get('live_controller')
|
||||||
"""
|
status = media.media_play(live, False)
|
||||||
Reimplement getOpenFileNames to fix the way it returns some file names that url encoded when selecting multiple
|
return {'results': {'success': status}}
|
||||||
files
|
|
||||||
"""
|
|
||||||
files, filter_used = QtWidgets.QFileDialog.getOpenFileNames(parent, *args, **kwargs)
|
@media_endpoint.route('pause')
|
||||||
file_list = []
|
@requires_auth
|
||||||
for file in files:
|
def media_pause(request):
|
||||||
if not os.path.exists(file):
|
"""
|
||||||
log.info('File not found. Attempting to unquote.')
|
Handles requests for pausing media
|
||||||
file = parse.unquote(file)
|
|
||||||
if not os.path.exists(file):
|
:param request: The http request object.
|
||||||
log.error('File {text} not found.'.format(text=file))
|
"""
|
||||||
QtWidgets.QMessageBox.information(parent, UiStrings().FileNotFound,
|
media = Registry().get('media_controller')
|
||||||
UiStrings().FileNotFoundMessage.format(name=file))
|
live = Registry().get('live_controller')
|
||||||
continue
|
status = media.media_pause(live)
|
||||||
file_list.append(file)
|
return {'results': {'success': status}}
|
||||||
return file_list
|
|
||||||
|
|
||||||
|
@media_endpoint.route('stop')
|
||||||
|
@requires_auth
|
||||||
|
def media_stop(request):
|
||||||
|
"""
|
||||||
|
Handles requests for stopping
|
||||||
|
|
||||||
|
:param request: The http request object.
|
||||||
|
"""
|
||||||
|
event = getattr(Registry().get('live_controller'), 'mediacontroller_live_stop')
|
||||||
|
event.emit()
|
||||||
|
return {'results': {'success': True}}
|
@ -28,12 +28,13 @@ import os
|
|||||||
import datetime
|
import datetime
|
||||||
from PyQt5 import QtCore, QtWidgets
|
from PyQt5 import QtCore, QtWidgets
|
||||||
|
|
||||||
|
from openlp.core.api.http import register_endpoint
|
||||||
from openlp.core.common import OpenLPMixin, Registry, RegistryMixin, RegistryProperties, Settings, UiStrings, \
|
from openlp.core.common import OpenLPMixin, Registry, RegistryMixin, RegistryProperties, Settings, UiStrings, \
|
||||||
extension_loader, translate
|
extension_loader, translate
|
||||||
from openlp.core.lib import ItemCapabilities
|
from openlp.core.lib import ItemCapabilities
|
||||||
from openlp.core.lib.ui import critical_error_message_box
|
from openlp.core.lib.ui import critical_error_message_box
|
||||||
from openlp.core.common import AppLocation
|
|
||||||
from openlp.core.ui import DisplayControllerType
|
from openlp.core.ui import DisplayControllerType
|
||||||
|
from openlp.core.ui.media.endpoint import media_endpoint
|
||||||
from openlp.core.ui.media.vendor.mediainfoWrapper import MediaInfoWrapper
|
from openlp.core.ui.media.vendor.mediainfoWrapper import MediaInfoWrapper
|
||||||
from openlp.core.ui.media.mediaplayer import MediaPlayer
|
from openlp.core.ui.media.mediaplayer import MediaPlayer
|
||||||
from openlp.core.ui.media import MediaState, MediaInfo, MediaType, get_media_players, set_media_players,\
|
from openlp.core.ui.media import MediaState, MediaInfo, MediaType, get_media_players, set_media_players,\
|
||||||
@ -127,9 +128,11 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties):
|
|||||||
Registry().register_function('media_unblank', self.media_unblank)
|
Registry().register_function('media_unblank', self.media_unblank)
|
||||||
# Signals for background video
|
# Signals for background video
|
||||||
Registry().register_function('songs_hide', self.media_hide)
|
Registry().register_function('songs_hide', self.media_hide)
|
||||||
|
Registry().register_function('songs_blank', self.media_blank)
|
||||||
Registry().register_function('songs_unblank', self.media_unblank)
|
Registry().register_function('songs_unblank', self.media_unblank)
|
||||||
Registry().register_function('mediaitem_media_rebuild', self._set_active_players)
|
Registry().register_function('mediaitem_media_rebuild', self._set_active_players)
|
||||||
Registry().register_function('mediaitem_suffixes', self._generate_extensions_lists)
|
Registry().register_function('mediaitem_suffixes', self._generate_extensions_lists)
|
||||||
|
register_endpoint(media_endpoint)
|
||||||
|
|
||||||
def _set_active_players(self):
|
def _set_active_players(self):
|
||||||
"""
|
"""
|
||||||
@ -174,7 +177,7 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties):
|
|||||||
Check to see if we have any media Player's available.
|
Check to see if we have any media Player's available.
|
||||||
"""
|
"""
|
||||||
log.debug('_check_available_media_players')
|
log.debug('_check_available_media_players')
|
||||||
controller_dir = os.path.join('openlp', 'core', 'ui', 'media')
|
controller_dir = os.path.join('core', 'ui', 'media')
|
||||||
glob_pattern = os.path.join(controller_dir, '*player.py')
|
glob_pattern = os.path.join(controller_dir, '*player.py')
|
||||||
extension_loader(glob_pattern, ['mediaplayer.py'])
|
extension_loader(glob_pattern, ['mediaplayer.py'])
|
||||||
player_classes = MediaPlayer.__subclasses__()
|
player_classes = MediaPlayer.__subclasses__()
|
||||||
@ -294,7 +297,9 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties):
|
|||||||
triggers=controller.send_to_plugins)
|
triggers=controller.send_to_plugins)
|
||||||
controller.position_label = QtWidgets.QLabel()
|
controller.position_label = QtWidgets.QLabel()
|
||||||
controller.position_label.setText(' 00:00 / 00:00')
|
controller.position_label.setText(' 00:00 / 00:00')
|
||||||
|
controller.position_label.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
|
||||||
controller.position_label.setToolTip(translate('OpenLP.SlideController', 'Video timer.'))
|
controller.position_label.setToolTip(translate('OpenLP.SlideController', 'Video timer.'))
|
||||||
|
controller.position_label.setMinimumSize(90, 0)
|
||||||
controller.position_label.setObjectName('position_label')
|
controller.position_label.setObjectName('position_label')
|
||||||
controller.mediabar.add_toolbar_widget(controller.position_label)
|
controller.mediabar.add_toolbar_widget(controller.position_label)
|
||||||
# Build the seek_slider.
|
# Build the seek_slider.
|
||||||
@ -430,7 +435,7 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties):
|
|||||||
log.debug('video mediatype: ' + str(controller.media_info.media_type))
|
log.debug('video mediatype: ' + str(controller.media_info.media_type))
|
||||||
# dont care about actual theme, set a black background
|
# dont care about actual theme, set a black background
|
||||||
if controller.is_live and not controller.media_info.is_background:
|
if controller.is_live and not controller.media_info.is_background:
|
||||||
display.frame.evaluateJavaScript('show_video( "setBackBoard", null, null, null,"visible");')
|
display.frame.evaluateJavaScript('show_video("setBackBoard", null, null,"visible");')
|
||||||
# now start playing - Preview is autoplay!
|
# now start playing - Preview is autoplay!
|
||||||
autoplay = False
|
autoplay = False
|
||||||
# Preview requested
|
# Preview requested
|
||||||
@ -466,9 +471,10 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties):
|
|||||||
player = self.media_players[used_players[0]]
|
player = self.media_players[used_players[0]]
|
||||||
if suffix not in player.video_extensions_list and suffix not in player.audio_extensions_list:
|
if suffix not in player.video_extensions_list and suffix not in player.audio_extensions_list:
|
||||||
# Media could not be loaded correctly
|
# Media could not be loaded correctly
|
||||||
critical_error_message_box(translate('MediaPlugin.MediaItem', 'Unsupported Media File'),
|
critical_error_message_box(
|
||||||
translate('MediaPlugin.MediaItem', 'File %s not supported using player %s') %
|
translate('MediaPlugin.MediaItem', 'Unsupported Media File'),
|
||||||
(service_item.get_frame_path(), used_players[0]))
|
translate('MediaPlugin.MediaItem', 'File {file_path} not supported using player {player_name}'
|
||||||
|
).format(file_path=service_item.get_frame_path(), player_name=used_players[0]))
|
||||||
return False
|
return False
|
||||||
media_data = MediaInfoWrapper.parse(service_item.get_frame_path())
|
media_data = MediaInfoWrapper.parse(service_item.get_frame_path())
|
||||||
# duration returns in milli seconds
|
# duration returns in milli seconds
|
||||||
@ -610,6 +616,14 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties):
|
|||||||
"""
|
"""
|
||||||
self.media_play(msg[0], status)
|
self.media_play(msg[0], status)
|
||||||
|
|
||||||
|
def on_media_play(self):
|
||||||
|
"""
|
||||||
|
Responds to the request to play a loaded video from the web.
|
||||||
|
|
||||||
|
:param msg: First element is the controller which should be used
|
||||||
|
"""
|
||||||
|
self.media_play(Registry().get('live_controller'), False)
|
||||||
|
|
||||||
def media_play(self, controller, first_time=True):
|
def media_play(self, controller, first_time=True):
|
||||||
"""
|
"""
|
||||||
Responds to the request to play a loaded video
|
Responds to the request to play a loaded video
|
||||||
@ -684,6 +698,14 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties):
|
|||||||
"""
|
"""
|
||||||
self.media_pause(msg[0])
|
self.media_pause(msg[0])
|
||||||
|
|
||||||
|
def on_media_pause(self):
|
||||||
|
"""
|
||||||
|
Responds to the request to pause a loaded video from the web.
|
||||||
|
|
||||||
|
:param msg: First element is the controller which should be used
|
||||||
|
"""
|
||||||
|
self.media_pause(Registry().get('live_controller'))
|
||||||
|
|
||||||
def media_pause(self, controller):
|
def media_pause(self, controller):
|
||||||
"""
|
"""
|
||||||
Responds to the request to pause a loaded video
|
Responds to the request to pause a loaded video
|
||||||
@ -724,6 +746,14 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties):
|
|||||||
"""
|
"""
|
||||||
self.media_stop(msg[0])
|
self.media_stop(msg[0])
|
||||||
|
|
||||||
|
def on_media_stop(self):
|
||||||
|
"""
|
||||||
|
Responds to the request to stop a loaded video from the web.
|
||||||
|
|
||||||
|
:param msg: First element is the controller which should be used
|
||||||
|
"""
|
||||||
|
self.media_stop(Registry().get('live_controller'))
|
||||||
|
|
||||||
def media_stop(self, controller, looping_background=False):
|
def media_stop(self, controller, looping_background=False):
|
||||||
"""
|
"""
|
||||||
Responds to the request to stop a loaded video
|
Responds to the request to stop a loaded video
|
||||||
@ -738,6 +768,11 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties):
|
|||||||
self.current_media_players[controller.controller_type].stop(display)
|
self.current_media_players[controller.controller_type].stop(display)
|
||||||
self.current_media_players[controller.controller_type].set_visible(display, False)
|
self.current_media_players[controller.controller_type].set_visible(display, False)
|
||||||
controller.seek_slider.setSliderPosition(0)
|
controller.seek_slider.setSliderPosition(0)
|
||||||
|
total_seconds = controller.media_info.length // 1000
|
||||||
|
total_minutes = total_seconds // 60
|
||||||
|
total_seconds %= 60
|
||||||
|
controller.position_label.setText(' %02d:%02d / %02d:%02d' %
|
||||||
|
(0, 0, total_minutes, total_seconds))
|
||||||
controller.mediabar.actions['playbackPlay'].setVisible(True)
|
controller.mediabar.actions['playbackPlay'].setVisible(True)
|
||||||
controller.mediabar.actions['playbackStop'].setDisabled(True)
|
controller.mediabar.actions['playbackStop'].setDisabled(True)
|
||||||
controller.mediabar.actions['playbackPause'].setVisible(False)
|
controller.mediabar.actions['playbackPause'].setVisible(False)
|
||||||
@ -800,7 +835,7 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties):
|
|||||||
display.override = {}
|
display.override = {}
|
||||||
self.current_media_players[controller.controller_type].reset(display)
|
self.current_media_players[controller.controller_type].reset(display)
|
||||||
self.current_media_players[controller.controller_type].set_visible(display, False)
|
self.current_media_players[controller.controller_type].set_visible(display, False)
|
||||||
display.frame.evaluateJavaScript('show_video( "setBackBoard", null, null, null,"hidden");')
|
display.frame.evaluateJavaScript('show_video("setBackBoard", null, null, "hidden");')
|
||||||
del self.current_media_players[controller.controller_type]
|
del self.current_media_players[controller.controller_type]
|
||||||
|
|
||||||
def media_hide(self, msg):
|
def media_hide(self, msg):
|
||||||
@ -815,7 +850,7 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties):
|
|||||||
display = self._define_display(self.live_controller)
|
display = self._define_display(self.live_controller)
|
||||||
if self.live_controller.controller_type in self.current_media_players and \
|
if self.live_controller.controller_type in self.current_media_players and \
|
||||||
self.current_media_players[self.live_controller.controller_type].get_live_state() == MediaState.Playing:
|
self.current_media_players[self.live_controller.controller_type].get_live_state() == MediaState.Playing:
|
||||||
self.current_media_players[self.live_controller.controller_type].pause(display)
|
self.media_pause(display.controller)
|
||||||
self.current_media_players[self.live_controller.controller_type].set_visible(display, False)
|
self.current_media_players[self.live_controller.controller_type].set_visible(display, False)
|
||||||
|
|
||||||
def media_blank(self, msg):
|
def media_blank(self, msg):
|
||||||
@ -833,7 +868,7 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties):
|
|||||||
display = self._define_display(self.live_controller)
|
display = self._define_display(self.live_controller)
|
||||||
if self.live_controller.controller_type in self.current_media_players and \
|
if self.live_controller.controller_type in self.current_media_players and \
|
||||||
self.current_media_players[self.live_controller.controller_type].get_live_state() == MediaState.Playing:
|
self.current_media_players[self.live_controller.controller_type].get_live_state() == MediaState.Playing:
|
||||||
self.current_media_players[self.live_controller.controller_type].pause(display)
|
self.media_pause(display.controller)
|
||||||
self.current_media_players[self.live_controller.controller_type].set_visible(display, False)
|
self.current_media_players[self.live_controller.controller_type].set_visible(display, False)
|
||||||
|
|
||||||
def media_unblank(self, msg):
|
def media_unblank(self, msg):
|
||||||
@ -851,7 +886,7 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties):
|
|||||||
if self.live_controller.controller_type in self.current_media_players and \
|
if self.live_controller.controller_type in self.current_media_players and \
|
||||||
self.current_media_players[self.live_controller.controller_type].get_live_state() != \
|
self.current_media_players[self.live_controller.controller_type].get_live_state() != \
|
||||||
MediaState.Playing:
|
MediaState.Playing:
|
||||||
if self.current_media_players[self.live_controller.controller_type].play(display):
|
if self.media_play(display.controller):
|
||||||
self.current_media_players[self.live_controller.controller_type].set_visible(display, True)
|
self.current_media_players[self.live_controller.controller_type].set_visible(display, True)
|
||||||
# Start Timer for ui updates
|
# Start Timer for ui updates
|
||||||
if not self.live_timer.isActive():
|
if not self.live_timer.isActive():
|
||||||
|
@ -258,7 +258,7 @@ class SystemPlayer(MediaPlayer):
|
|||||||
:param display: The display where the media is
|
:param display: The display where the media is
|
||||||
"""
|
"""
|
||||||
if display.media_player.state() == QtMultimedia.QMediaPlayer.PausedState and self.state != MediaState.Paused:
|
if display.media_player.state() == QtMultimedia.QMediaPlayer.PausedState and self.state != MediaState.Paused:
|
||||||
self.stop(display)
|
self.pause(display)
|
||||||
controller = display.controller
|
controller = display.controller
|
||||||
if controller.media_info.end_time > 0:
|
if controller.media_info.end_time > 0:
|
||||||
if display.media_player.position() > controller.media_info.end_time:
|
if display.media_player.position() > controller.media_info.end_time:
|
||||||
|
@ -41,7 +41,8 @@ class PluginForm(QtWidgets.QDialog, Ui_PluginViewDialog, RegistryProperties):
|
|||||||
"""
|
"""
|
||||||
Constructor
|
Constructor
|
||||||
"""
|
"""
|
||||||
super(PluginForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint)
|
super(PluginForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint |
|
||||||
|
QtCore.Qt.WindowCloseButtonHint)
|
||||||
self.active_plugin = None
|
self.active_plugin = None
|
||||||
self.programatic_change = False
|
self.programatic_change = False
|
||||||
self.setupUi(self)
|
self.setupUi(self)
|
||||||
@ -60,7 +61,6 @@ class PluginForm(QtWidgets.QDialog, Ui_PluginViewDialog, RegistryProperties):
|
|||||||
self._clear_details()
|
self._clear_details()
|
||||||
self.programatic_change = True
|
self.programatic_change = True
|
||||||
plugin_list_width = 0
|
plugin_list_width = 0
|
||||||
# TODO: Tested at home
|
|
||||||
for plugin in self.plugin_manager.plugins:
|
for plugin in self.plugin_manager.plugins:
|
||||||
item = QtWidgets.QListWidgetItem(self.plugin_list_widget)
|
item = QtWidgets.QListWidgetItem(self.plugin_list_widget)
|
||||||
# We do this just to make 100% sure the status is an integer as
|
# We do this just to make 100% sure the status is an integer as
|
||||||
@ -137,7 +137,6 @@ class PluginForm(QtWidgets.QDialog, Ui_PluginViewDialog, RegistryProperties):
|
|||||||
self.active_plugin.app_startup()
|
self.active_plugin.app_startup()
|
||||||
else:
|
else:
|
||||||
self.active_plugin.toggle_status(PluginStatus.Inactive)
|
self.active_plugin.toggle_status(PluginStatus.Inactive)
|
||||||
# TODO: Tested at home
|
|
||||||
status_text = translate('OpenLP.PluginForm', '{name} (Inactive)')
|
status_text = translate('OpenLP.PluginForm', '{name} (Inactive)')
|
||||||
if self.active_plugin.status == PluginStatus.Active:
|
if self.active_plugin.status == PluginStatus.Active:
|
||||||
status_text = translate('OpenLP.PluginForm', '{name} (Active)')
|
status_text = translate('OpenLP.PluginForm', '{name} (Active)')
|
||||||
|
@ -125,8 +125,8 @@ class PrintServiceForm(QtWidgets.QDialog, Ui_PrintServiceDialog, RegistryPropert
|
|||||||
"""
|
"""
|
||||||
Constructor
|
Constructor
|
||||||
"""
|
"""
|
||||||
super(PrintServiceForm, self).__init__(Registry().get('main_window'),
|
super(PrintServiceForm, self).__init__(Registry().get('main_window'), QtCore.Qt.WindowSystemMenuHint |
|
||||||
QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint)
|
QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowCloseButtonHint)
|
||||||
self.printer = QtPrintSupport.QPrinter()
|
self.printer = QtPrintSupport.QPrinter()
|
||||||
self.print_dialog = QtPrintSupport.QPrintDialog(self.printer, self)
|
self.print_dialog = QtPrintSupport.QPrintDialog(self.printer, self)
|
||||||
self.document = QtGui.QTextDocument()
|
self.document = QtGui.QTextDocument()
|
||||||
@ -176,7 +176,7 @@ class PrintServiceForm(QtWidgets.QDialog, Ui_PrintServiceDialog, RegistryPropert
|
|||||||
html_data = self._add_element('html')
|
html_data = self._add_element('html')
|
||||||
self._add_element('head', parent=html_data)
|
self._add_element('head', parent=html_data)
|
||||||
self._add_element('title', self.title_line_edit.text(), html_data.head)
|
self._add_element('title', self.title_line_edit.text(), html_data.head)
|
||||||
css_path = os.path.join(AppLocation.get_data_path(), 'serviceprint', 'service_print.css')
|
css_path = AppLocation.get_data_path() / 'serviceprint' / 'service_print.css'
|
||||||
custom_css = get_text_file_string(css_path)
|
custom_css = get_text_file_string(css_path)
|
||||||
if not custom_css:
|
if not custom_css:
|
||||||
custom_css = DEFAULT_CSS
|
custom_css = DEFAULT_CSS
|
||||||
|
@ -142,7 +142,8 @@ class ProjectorEditForm(QtWidgets.QDialog, Ui_ProjectorEditForm):
|
|||||||
editProjector = QtCore.pyqtSignal(object)
|
editProjector = QtCore.pyqtSignal(object)
|
||||||
|
|
||||||
def __init__(self, parent=None, projectordb=None):
|
def __init__(self, parent=None, projectordb=None):
|
||||||
super(ProjectorEditForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint)
|
super(ProjectorEditForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint |
|
||||||
|
QtCore.Qt.WindowCloseButtonHint)
|
||||||
self.projectordb = projectordb
|
self.projectordb = projectordb
|
||||||
self.setupUi(self)
|
self.setupUi(self)
|
||||||
self.button_box.accepted.connect(self.accept_me)
|
self.button_box.accepted.connect(self.accept_me)
|
||||||
|
@ -38,7 +38,8 @@ from openlp.core.lib.projector.constants import ERROR_MSG, ERROR_STRING, E_AUTHE
|
|||||||
E_NETWORK, E_NOT_CONNECTED, E_UNKNOWN_SOCKET_ERROR, STATUS_STRING, S_CONNECTED, S_CONNECTING, S_COOLDOWN, \
|
E_NETWORK, E_NOT_CONNECTED, E_UNKNOWN_SOCKET_ERROR, STATUS_STRING, S_CONNECTED, S_CONNECTING, S_COOLDOWN, \
|
||||||
S_INITIALIZE, S_NOT_CONNECTED, S_OFF, S_ON, S_STANDBY, S_WARMUP
|
S_INITIALIZE, S_NOT_CONNECTED, S_OFF, S_ON, S_STANDBY, S_WARMUP
|
||||||
from openlp.core.lib.projector.db import ProjectorDB
|
from openlp.core.lib.projector.db import ProjectorDB
|
||||||
from openlp.core.lib.projector.pjlink1 import PJLink
|
from openlp.core.lib.projector.pjlink import PJLink
|
||||||
|
from openlp.core.lib.projector.pjlink2 import PJLinkUDP
|
||||||
from openlp.core.ui.projector.editform import ProjectorEditForm
|
from openlp.core.ui.projector.editform import ProjectorEditForm
|
||||||
from openlp.core.ui.projector.sourceselectform import SourceSelectTabs, SourceSelectSingle
|
from openlp.core.ui.projector.sourceselectform import SourceSelectTabs, SourceSelectSingle
|
||||||
|
|
||||||
@ -290,6 +291,8 @@ class ProjectorManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, UiProjecto
|
|||||||
self.settings_section = 'projector'
|
self.settings_section = 'projector'
|
||||||
self.projectordb = projectordb
|
self.projectordb = projectordb
|
||||||
self.projector_list = []
|
self.projector_list = []
|
||||||
|
self.pjlink_udp = PJLinkUDP()
|
||||||
|
self.pjlink_udp.projector_list = self.projector_list
|
||||||
self.source_select_form = None
|
self.source_select_form = None
|
||||||
|
|
||||||
def bootstrap_initialise(self):
|
def bootstrap_initialise(self):
|
||||||
@ -417,9 +420,7 @@ class ProjectorManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, UiProjecto
|
|||||||
:param opt: Needed by PyQt5
|
:param opt: Needed by PyQt5
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
ip = opt.link.ip
|
opt.link.set_shutter_closed()
|
||||||
projector = opt
|
|
||||||
projector.link.set_shutter_closed()
|
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
for list_item in self.projector_list_widget.selectedItems():
|
for list_item in self.projector_list_widget.selectedItems():
|
||||||
if list_item is None:
|
if list_item is None:
|
||||||
@ -452,9 +453,7 @@ class ProjectorManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, UiProjecto
|
|||||||
:param opt: Needed by PyQt5
|
:param opt: Needed by PyQt5
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
ip = opt.link.ip
|
opt.link.connect_to_host()
|
||||||
projector = opt
|
|
||||||
projector.link.connect_to_host()
|
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
for list_item in self.projector_list_widget.selectedItems():
|
for list_item in self.projector_list_widget.selectedItems():
|
||||||
if list_item is None:
|
if list_item is None:
|
||||||
@ -524,7 +523,8 @@ class ProjectorManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, UiProjecto
|
|||||||
self.projector_list = new_list
|
self.projector_list = new_list
|
||||||
list_item = self.projector_list_widget.takeItem(self.projector_list_widget.currentRow())
|
list_item = self.projector_list_widget.takeItem(self.projector_list_widget.currentRow())
|
||||||
list_item = None
|
list_item = None
|
||||||
deleted = self.projectordb.delete_projector(projector.db_item)
|
if not self.projectordb.delete_projector(projector.db_item):
|
||||||
|
log.warning('Delete projector {item} failed'.format(item=projector.db_item))
|
||||||
for item in self.projector_list:
|
for item in self.projector_list:
|
||||||
log.debug('New projector list - item: {ip} {name}'.format(ip=item.link.ip, name=item.link.name))
|
log.debug('New projector list - item: {ip} {name}'.format(ip=item.link.ip, name=item.link.name))
|
||||||
|
|
||||||
@ -535,9 +535,7 @@ class ProjectorManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, UiProjecto
|
|||||||
:param opt: Needed by PyQt5
|
:param opt: Needed by PyQt5
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
ip = opt.link.ip
|
opt.link.disconnect_from_host()
|
||||||
projector = opt
|
|
||||||
projector.link.disconnect_from_host()
|
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
for list_item in self.projector_list_widget.selectedItems():
|
for list_item in self.projector_list_widget.selectedItems():
|
||||||
if list_item is None:
|
if list_item is None:
|
||||||
@ -570,9 +568,7 @@ class ProjectorManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, UiProjecto
|
|||||||
:param opt: Needed by PyQt5
|
:param opt: Needed by PyQt5
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
ip = opt.link.ip
|
opt.link.set_power_off()
|
||||||
projector = opt
|
|
||||||
projector.link.set_power_off()
|
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
for list_item in self.projector_list_widget.selectedItems():
|
for list_item in self.projector_list_widget.selectedItems():
|
||||||
if list_item is None:
|
if list_item is None:
|
||||||
@ -590,9 +586,7 @@ class ProjectorManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, UiProjecto
|
|||||||
:param opt: Needed by PyQt5
|
:param opt: Needed by PyQt5
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
ip = opt.link.ip
|
opt.link.set_power_on()
|
||||||
projector = opt
|
|
||||||
projector.link.set_power_on()
|
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
for list_item in self.projector_list_widget.selectedItems():
|
for list_item in self.projector_list_widget.selectedItems():
|
||||||
if list_item is None:
|
if list_item is None:
|
||||||
@ -610,9 +604,7 @@ class ProjectorManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, UiProjecto
|
|||||||
:param opt: Needed by PyQt5
|
:param opt: Needed by PyQt5
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
ip = opt.link.ip
|
opt.link.set_shutter_open()
|
||||||
projector = opt
|
|
||||||
projector.link.set_shutter_open()
|
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
for list_item in self.projector_list_widget.selectedItems():
|
for list_item in self.projector_list_widget.selectedItems():
|
||||||
if list_item is None:
|
if list_item is None:
|
||||||
@ -659,19 +651,34 @@ class ProjectorManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, UiProjecto
|
|||||||
data=translate('OpenLP.ProjectorManager', 'Closed')
|
data=translate('OpenLP.ProjectorManager', 'Closed')
|
||||||
if projector.link.shutter
|
if projector.link.shutter
|
||||||
else translate('OpenLP', 'Open'))
|
else translate('OpenLP', 'Open'))
|
||||||
message = '%s<b>%s</b>: %s<br />' % (message,
|
message = '{msg}<b>{source}</b>: {selected}<br />'.format(msg=message,
|
||||||
translate('OpenLP.ProjectorManager', 'Current source input is'),
|
source=translate('OpenLP.ProjectorManager',
|
||||||
projector.link.source)
|
'Current source input is'),
|
||||||
|
selected=projector.link.source)
|
||||||
|
if projector.link.pjlink_class == '2':
|
||||||
|
# Information only available for PJLink Class 2 projectors
|
||||||
|
message += '<b>{title}</b>: {data}<br /><br />'.format(title=translate('OpenLP.ProjectorManager',
|
||||||
|
'Serial Number'),
|
||||||
|
data=projector.serial_no)
|
||||||
|
message += '<b>{title}</b>: {data}<br /><br />'.format(title=translate('OpenLP.ProjectorManager',
|
||||||
|
'Software Version'),
|
||||||
|
data=projector.sw_version)
|
||||||
|
message += '<b>{title}</b>: {data}<br /><br />'.format(title=translate('OpenLP.ProjectorManager',
|
||||||
|
'Lamp type'),
|
||||||
|
data=projector.model_lamp)
|
||||||
|
message += '<b>{title}</b>: {data}<br /><br />'.format(title=translate('OpenLP.ProjectorManager',
|
||||||
|
'Filter type'),
|
||||||
|
data=projector.model_filter)
|
||||||
count = 1
|
count = 1
|
||||||
for item in projector.link.lamp:
|
for item in projector.link.lamp:
|
||||||
message += '<b>{title} {count}</b> {status} '.format(title=translate('OpenLP.ProjectorManager',
|
message += '<b>{title} {count}</b> {status} '.format(title=translate('OpenLP.ProjectorManager',
|
||||||
'Lamp'),
|
'Lamp'),
|
||||||
count=count,
|
count=count,
|
||||||
status=translate('OpenLP.ProjectorManager',
|
status=translate('OpenLP.ProjectorManager',
|
||||||
' is on')
|
'ON')
|
||||||
if item['On']
|
if item['On']
|
||||||
else translate('OpenLP.ProjectorManager',
|
else translate('OpenLP.ProjectorManager',
|
||||||
'is off'))
|
'OFF'))
|
||||||
|
|
||||||
message += '<b>{title}</b>: {hours}<br />'.format(title=translate('OpenLP.ProjectorManager', 'Hours'),
|
message += '<b>{title}</b>: {hours}<br />'.format(title=translate('OpenLP.ProjectorManager', 'Hours'),
|
||||||
hours=item['Hours'])
|
hours=item['Hours'])
|
||||||
@ -973,7 +980,7 @@ class ProjectorItem(QtCore.QObject):
|
|||||||
self.poll_time = None
|
self.poll_time = None
|
||||||
self.socket_timeout = None
|
self.socket_timeout = None
|
||||||
self.status = S_NOT_CONNECTED
|
self.status = S_NOT_CONNECTED
|
||||||
super(ProjectorItem, self).__init__()
|
super().__init__()
|
||||||
|
|
||||||
|
|
||||||
def not_implemented(function):
|
def not_implemented(function):
|
||||||
|
@ -233,7 +233,8 @@ class SourceSelectTabs(QtWidgets.QDialog):
|
|||||||
:param projectordb: ProjectorDB session to use
|
:param projectordb: ProjectorDB session to use
|
||||||
"""
|
"""
|
||||||
log.debug('Initializing SourceSelectTabs()')
|
log.debug('Initializing SourceSelectTabs()')
|
||||||
super(SourceSelectTabs, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint)
|
super(SourceSelectTabs, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint |
|
||||||
|
QtCore.Qt.WindowCloseButtonHint)
|
||||||
self.setMinimumWidth(350)
|
self.setMinimumWidth(350)
|
||||||
self.projectordb = projectordb
|
self.projectordb = projectordb
|
||||||
self.edit = edit
|
self.edit = edit
|
||||||
@ -388,12 +389,13 @@ class SourceSelectSingle(QtWidgets.QDialog):
|
|||||||
"""
|
"""
|
||||||
log.debug('Initializing SourceSelectSingle()')
|
log.debug('Initializing SourceSelectSingle()')
|
||||||
self.projectordb = projectordb
|
self.projectordb = projectordb
|
||||||
super(SourceSelectSingle, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint)
|
super(SourceSelectSingle, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint |
|
||||||
|
QtCore.Qt.WindowCloseButtonHint)
|
||||||
self.edit = edit
|
self.edit = edit
|
||||||
if self.edit:
|
if self.edit:
|
||||||
title = translate('OpenLP.SourceSelectForm', 'Edit Projector Source Text')
|
self.title = translate('OpenLP.SourceSelectForm', 'Edit Projector Source Text')
|
||||||
else:
|
else:
|
||||||
title = translate('OpenLP.SourceSelectForm', 'Select Projector Source')
|
self.title = translate('OpenLP.SourceSelectForm', 'Select Projector Source')
|
||||||
self.setObjectName('source_select_single')
|
self.setObjectName('source_select_single')
|
||||||
self.setWindowIcon(build_icon(':/icon/openlp-log.svg'))
|
self.setWindowIcon(build_icon(':/icon/openlp-log.svg'))
|
||||||
self.setModal(True)
|
self.setModal(True)
|
||||||
|
@ -37,8 +37,8 @@ class ServiceItemEditForm(QtWidgets.QDialog, Ui_ServiceItemEditDialog, RegistryP
|
|||||||
"""
|
"""
|
||||||
Constructor
|
Constructor
|
||||||
"""
|
"""
|
||||||
super(ServiceItemEditForm, self).__init__(Registry().get('main_window'),
|
super(ServiceItemEditForm, self).__init__(Registry().get('main_window'), QtCore.Qt.WindowSystemMenuHint |
|
||||||
QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint)
|
QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowCloseButtonHint)
|
||||||
self.setupUi(self)
|
self.setupUi(self)
|
||||||
self.item_list = []
|
self.item_list = []
|
||||||
self.list_widget.currentRowChanged.connect(self.on_current_row_changed)
|
self.list_widget.currentRowChanged.connect(self.on_current_row_changed)
|
||||||
|
@ -35,11 +35,13 @@ from PyQt5 import QtCore, QtGui, QtWidgets
|
|||||||
from openlp.core.common import Registry, RegistryProperties, AppLocation, Settings, ThemeLevel, OpenLPMixin, \
|
from openlp.core.common import Registry, RegistryProperties, AppLocation, Settings, ThemeLevel, OpenLPMixin, \
|
||||||
RegistryMixin, check_directory_exists, UiStrings, translate, split_filename, delete_file
|
RegistryMixin, check_directory_exists, UiStrings, translate, split_filename, delete_file
|
||||||
from openlp.core.common.actions import ActionList, CategoryOrder
|
from openlp.core.common.actions import ActionList, CategoryOrder
|
||||||
|
from openlp.core.common.languagemanager import format_time
|
||||||
|
from openlp.core.common.path import Path, path_to_str, str_to_path
|
||||||
from openlp.core.lib import ServiceItem, ItemCapabilities, PluginStatus, build_icon
|
from openlp.core.lib import ServiceItem, ItemCapabilities, PluginStatus, build_icon
|
||||||
from openlp.core.lib.ui import critical_error_message_box, create_widget_action, find_and_set_in_combo_box
|
from openlp.core.lib.ui import critical_error_message_box, create_widget_action, find_and_set_in_combo_box
|
||||||
from openlp.core.ui import ServiceNoteForm, ServiceItemEditForm, StartTimeForm
|
from openlp.core.ui import ServiceNoteForm, ServiceItemEditForm, StartTimeForm
|
||||||
from openlp.core.ui.lib import OpenLPToolbar
|
from openlp.core.ui.lib import OpenLPToolbar
|
||||||
from openlp.core.common.languagemanager import format_time
|
from openlp.core.ui.lib.filedialog import FileDialog
|
||||||
|
|
||||||
|
|
||||||
class ServiceManagerList(QtWidgets.QTreeWidget):
|
class ServiceManagerList(QtWidgets.QTreeWidget):
|
||||||
@ -66,6 +68,12 @@ class ServiceManagerList(QtWidgets.QTreeWidget):
|
|||||||
elif event.key() == QtCore.Qt.Key_Down:
|
elif event.key() == QtCore.Qt.Key_Down:
|
||||||
self.service_manager.on_move_selection_down()
|
self.service_manager.on_move_selection_down()
|
||||||
event.accept()
|
event.accept()
|
||||||
|
elif event.key() == QtCore.Qt.Key_Right:
|
||||||
|
self.service_manager.on_expand_selection()
|
||||||
|
event.accept()
|
||||||
|
elif event.key() == QtCore.Qt.Key_Left:
|
||||||
|
self.service_manager.on_collapse_selection()
|
||||||
|
event.accept()
|
||||||
elif event.key() == QtCore.Qt.Key_Delete:
|
elif event.key() == QtCore.Qt.Key_Delete:
|
||||||
self.service_manager.on_delete_from_service()
|
self.service_manager.on_delete_from_service()
|
||||||
event.accept()
|
event.accept()
|
||||||
@ -217,7 +225,7 @@ class Ui_ServiceManager(object):
|
|||||||
self.service_manager_list.itemExpanded.connect(self.expanded)
|
self.service_manager_list.itemExpanded.connect(self.expanded)
|
||||||
# Last little bits of setting up
|
# Last little bits of setting up
|
||||||
self.service_theme = Settings().value(self.main_window.service_manager_settings_section + '/service theme')
|
self.service_theme = Settings().value(self.main_window.service_manager_settings_section + '/service theme')
|
||||||
self.service_path = AppLocation.get_section_data_path('servicemanager')
|
self.service_path = str(AppLocation.get_section_data_path('servicemanager'))
|
||||||
# build the drag and drop context menu
|
# build the drag and drop context menu
|
||||||
self.dnd_menu = QtWidgets.QMenu()
|
self.dnd_menu = QtWidgets.QMenu()
|
||||||
self.new_action = self.dnd_menu.addAction(translate('OpenLP.ServiceManager', '&Add New Item'))
|
self.new_action = self.dnd_menu.addAction(translate('OpenLP.ServiceManager', '&Add New Item'))
|
||||||
@ -366,7 +374,7 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ServiceMa
|
|||||||
"""
|
"""
|
||||||
self._file_name = str(file_name)
|
self._file_name = str(file_name)
|
||||||
self.main_window.set_service_modified(self.is_modified(), self.short_file_name())
|
self.main_window.set_service_modified(self.is_modified(), self.short_file_name())
|
||||||
Settings().setValue('servicemanager/last file', file_name)
|
Settings().setValue('servicemanager/last file', Path(file_name))
|
||||||
self._save_lite = self._file_name.endswith('.oszl')
|
self._save_lite = self._file_name.endswith('.oszl')
|
||||||
|
|
||||||
def file_name(self):
|
def file_name(self):
|
||||||
@ -428,18 +436,17 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ServiceMa
|
|||||||
elif result == QtWidgets.QMessageBox.Save:
|
elif result == QtWidgets.QMessageBox.Save:
|
||||||
self.decide_save_method()
|
self.decide_save_method()
|
||||||
if not load_file:
|
if not load_file:
|
||||||
file_name, filter_used = QtWidgets.QFileDialog.getOpenFileName(
|
file_path, filter_used = FileDialog.getOpenFileName(
|
||||||
self.main_window,
|
self.main_window,
|
||||||
translate('OpenLP.ServiceManager', 'Open File'),
|
translate('OpenLP.ServiceManager', 'Open File'),
|
||||||
Settings().value(self.main_window.service_manager_settings_section + '/last directory'),
|
Settings().value(self.main_window.service_manager_settings_section + '/last directory'),
|
||||||
translate('OpenLP.ServiceManager', 'OpenLP Service Files (*.osz *.oszl)'))
|
translate('OpenLP.ServiceManager', 'OpenLP Service Files (*.osz *.oszl)'))
|
||||||
if not file_name:
|
if not file_path:
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
file_name = load_file
|
file_path = str_to_path(load_file)
|
||||||
Settings().setValue(self.main_window.service_manager_settings_section + '/last directory',
|
Settings().setValue(self.main_window.service_manager_settings_section + '/last directory', file_path.parent)
|
||||||
split_filename(file_name)[0])
|
self.load_file(str(file_path))
|
||||||
self.load_file(file_name)
|
|
||||||
|
|
||||||
def save_modified_service(self):
|
def save_modified_service(self):
|
||||||
"""
|
"""
|
||||||
@ -470,7 +477,7 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ServiceMa
|
|||||||
self.set_file_name('')
|
self.set_file_name('')
|
||||||
self.service_id += 1
|
self.service_id += 1
|
||||||
self.set_modified(False)
|
self.set_modified(False)
|
||||||
Settings().setValue('servicemanager/last file', '')
|
Settings().setValue('servicemanager/last file', None)
|
||||||
self.plugin_manager.new_service_created()
|
self.plugin_manager.new_service_created()
|
||||||
|
|
||||||
def create_basic_service(self):
|
def create_basic_service(self):
|
||||||
@ -506,7 +513,7 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ServiceMa
|
|||||||
base_name = os.path.splitext(file_name)[0]
|
base_name = os.path.splitext(file_name)[0]
|
||||||
service_file_name = '{name}.osj'.format(name=base_name)
|
service_file_name = '{name}.osj'.format(name=base_name)
|
||||||
self.log_debug('ServiceManager.save_file - {name}'.format(name=path_file_name))
|
self.log_debug('ServiceManager.save_file - {name}'.format(name=path_file_name))
|
||||||
Settings().setValue(self.main_window.service_manager_settings_section + '/last directory', path)
|
Settings().setValue(self.main_window.service_manager_settings_section + '/last directory', Path(path))
|
||||||
service = self.create_basic_service()
|
service = self.create_basic_service()
|
||||||
write_list = []
|
write_list = []
|
||||||
missing_list = []
|
missing_list = []
|
||||||
@ -581,7 +588,7 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ServiceMa
|
|||||||
audio_from = os.path.join(self.service_path, audio_from)
|
audio_from = os.path.join(self.service_path, audio_from)
|
||||||
save_file = os.path.join(self.service_path, audio_to)
|
save_file = os.path.join(self.service_path, audio_to)
|
||||||
save_path = os.path.split(save_file)[0]
|
save_path = os.path.split(save_file)[0]
|
||||||
check_directory_exists(save_path)
|
check_directory_exists(Path(save_path))
|
||||||
if not os.path.exists(save_file):
|
if not os.path.exists(save_file):
|
||||||
shutil.copy(audio_from, save_file)
|
shutil.copy(audio_from, save_file)
|
||||||
zip_file.write(audio_from, audio_to)
|
zip_file.write(audio_from, audio_to)
|
||||||
@ -608,7 +615,7 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ServiceMa
|
|||||||
success = False
|
success = False
|
||||||
self.main_window.add_recent_file(path_file_name)
|
self.main_window.add_recent_file(path_file_name)
|
||||||
self.set_modified(False)
|
self.set_modified(False)
|
||||||
delete_file(temp_file_name)
|
delete_file(Path(temp_file_name))
|
||||||
return success
|
return success
|
||||||
|
|
||||||
def save_local_file(self):
|
def save_local_file(self):
|
||||||
@ -627,7 +634,7 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ServiceMa
|
|||||||
base_name = os.path.splitext(file_name)[0]
|
base_name = os.path.splitext(file_name)[0]
|
||||||
service_file_name = '{name}.osj'.format(name=base_name)
|
service_file_name = '{name}.osj'.format(name=base_name)
|
||||||
self.log_debug('ServiceManager.save_file - {name}'.format(name=path_file_name))
|
self.log_debug('ServiceManager.save_file - {name}'.format(name=path_file_name))
|
||||||
Settings().setValue(self.main_window.service_manager_settings_section + '/last directory', path)
|
Settings().setValue(self.main_window.service_manager_settings_section + '/last directory', Path(path))
|
||||||
service = self.create_basic_service()
|
service = self.create_basic_service()
|
||||||
self.application.set_busy_cursor()
|
self.application.set_busy_cursor()
|
||||||
# Number of items + 1 to zip it
|
# Number of items + 1 to zip it
|
||||||
@ -663,7 +670,7 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ServiceMa
|
|||||||
return self.save_file_as()
|
return self.save_file_as()
|
||||||
self.main_window.add_recent_file(path_file_name)
|
self.main_window.add_recent_file(path_file_name)
|
||||||
self.set_modified(False)
|
self.set_modified(False)
|
||||||
delete_file(temp_file_name)
|
delete_file(Path(temp_file_name))
|
||||||
return success
|
return success
|
||||||
|
|
||||||
def save_file_as(self, field=None):
|
def save_file_as(self, field=None):
|
||||||
@ -688,7 +695,7 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ServiceMa
|
|||||||
default_file_name = format_time(default_pattern, local_time)
|
default_file_name = format_time(default_pattern, local_time)
|
||||||
else:
|
else:
|
||||||
default_file_name = ''
|
default_file_name = ''
|
||||||
directory = Settings().value(self.main_window.service_manager_settings_section + '/last directory')
|
directory = path_to_str(Settings().value(self.main_window.service_manager_settings_section + '/last directory'))
|
||||||
path = os.path.join(directory, default_file_name)
|
path = os.path.join(directory, default_file_name)
|
||||||
# SaveAs from osz to oszl is not valid as the files will be deleted on exit which is not sensible or usable in
|
# SaveAs from osz to oszl is not valid as the files will be deleted on exit which is not sensible or usable in
|
||||||
# the long term.
|
# the long term.
|
||||||
@ -768,10 +775,10 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ServiceMa
|
|||||||
self.set_file_name(file_name)
|
self.set_file_name(file_name)
|
||||||
self.main_window.display_progress_bar(len(items))
|
self.main_window.display_progress_bar(len(items))
|
||||||
self.process_service_items(items)
|
self.process_service_items(items)
|
||||||
delete_file(p_file)
|
delete_file(Path(p_file))
|
||||||
self.main_window.add_recent_file(file_name)
|
self.main_window.add_recent_file(file_name)
|
||||||
self.set_modified(False)
|
self.set_modified(False)
|
||||||
Settings().setValue('servicemanager/last file', file_name)
|
Settings().setValue('servicemanager/last file', Path(file_name))
|
||||||
else:
|
else:
|
||||||
critical_error_message_box(message=translate('OpenLP.ServiceManager', 'File is not a valid service.'))
|
critical_error_message_box(message=translate('OpenLP.ServiceManager', 'File is not a valid service.'))
|
||||||
self.log_error('File contains no service data')
|
self.log_error('File contains no service data')
|
||||||
@ -836,7 +843,7 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ServiceMa
|
|||||||
Load the last service item from the service manager when the service was last closed. Can be blank if there was
|
Load the last service item from the service manager when the service was last closed. Can be blank if there was
|
||||||
no service present.
|
no service present.
|
||||||
"""
|
"""
|
||||||
file_name = Settings().value('servicemanager/last file')
|
file_name = str_to_path(Settings().value('servicemanager/last file'))
|
||||||
if file_name:
|
if file_name:
|
||||||
self.load_file(file_name)
|
self.load_file(file_name)
|
||||||
|
|
||||||
@ -1119,6 +1126,35 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ServiceMa
|
|||||||
return
|
return
|
||||||
self.service_manager_list.setCurrentItem(item_after)
|
self.service_manager_list.setCurrentItem(item_after)
|
||||||
|
|
||||||
|
def on_expand_selection(self):
|
||||||
|
"""
|
||||||
|
Expands cursor selection on the window. Called by the right arrow
|
||||||
|
"""
|
||||||
|
item = self.service_manager_list.currentItem()
|
||||||
|
# Since we only have 2 levels we find them by checking for children
|
||||||
|
if item.childCount():
|
||||||
|
if not self.service_manager_list.isExpanded(self.service_manager_list.currentIndex()):
|
||||||
|
self.service_manager_list.expandItem(item)
|
||||||
|
self.service_manager.expanded(item)
|
||||||
|
# If not expanded, Expand it
|
||||||
|
self.service_manager_list.setCurrentItem(self.service_manager_list.itemBelow(item))
|
||||||
|
# Then move selection down to child whether it needed to be expanded or not
|
||||||
|
|
||||||
|
def on_collapse_selection(self):
|
||||||
|
"""
|
||||||
|
Collapses cursor selection on the window Called by the left arrow
|
||||||
|
"""
|
||||||
|
item = self.service_manager_list.currentItem()
|
||||||
|
# Since we only have 2 levels we find them by checking for children
|
||||||
|
if item.childCount():
|
||||||
|
if self.service_manager_list.isExpanded(self.service_manager_list.currentIndex()):
|
||||||
|
self.service_manager_list.collapseItem(item)
|
||||||
|
self.service_manager.collapsed(item)
|
||||||
|
else: # If selection is lower level
|
||||||
|
self.service_manager_list.collapseItem(item.parent())
|
||||||
|
self.service_manager.collapsed(item.parent())
|
||||||
|
self.service_manager_list.setCurrentItem(item.parent())
|
||||||
|
|
||||||
def on_collapse_all(self, field=None):
|
def on_collapse_all(self, field=None):
|
||||||
"""
|
"""
|
||||||
Collapse all the service items.
|
Collapse all the service items.
|
||||||
@ -1308,7 +1344,7 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ServiceMa
|
|||||||
Empties the service_path of temporary files on system exit.
|
Empties the service_path of temporary files on system exit.
|
||||||
"""
|
"""
|
||||||
for file_name in os.listdir(self.service_path):
|
for file_name in os.listdir(self.service_path):
|
||||||
file_path = os.path.join(self.service_path, file_name)
|
file_path = Path(self.service_path, file_name)
|
||||||
delete_file(file_path)
|
delete_file(file_path)
|
||||||
if os.path.exists(os.path.join(self.service_path, 'audio')):
|
if os.path.exists(os.path.join(self.service_path, 'audio')):
|
||||||
shutil.rmtree(os.path.join(self.service_path, 'audio'), True)
|
shutil.rmtree(os.path.join(self.service_path, 'audio'), True)
|
||||||
|
@ -37,8 +37,8 @@ class ServiceNoteForm(QtWidgets.QDialog, RegistryProperties):
|
|||||||
"""
|
"""
|
||||||
Constructor
|
Constructor
|
||||||
"""
|
"""
|
||||||
super(ServiceNoteForm, self).__init__(Registry().get('main_window'),
|
super(ServiceNoteForm, self).__init__(Registry().get('main_window'), QtCore.Qt.WindowSystemMenuHint |
|
||||||
QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint)
|
QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowCloseButtonHint)
|
||||||
self.setupUi()
|
self.setupUi()
|
||||||
self.retranslateUi()
|
self.retranslateUi()
|
||||||
|
|
||||||
|
@ -26,6 +26,7 @@ import logging
|
|||||||
|
|
||||||
from PyQt5 import QtCore, QtWidgets
|
from PyQt5 import QtCore, QtWidgets
|
||||||
|
|
||||||
|
from openlp.core.api import ApiTab
|
||||||
from openlp.core.common import Registry, RegistryProperties
|
from openlp.core.common import Registry, RegistryProperties
|
||||||
from openlp.core.lib import build_icon
|
from openlp.core.lib import build_icon
|
||||||
from openlp.core.ui import AdvancedTab, GeneralTab, ThemesTab
|
from openlp.core.ui import AdvancedTab, GeneralTab, ThemesTab
|
||||||
@ -46,7 +47,8 @@ class SettingsForm(QtWidgets.QDialog, Ui_SettingsDialog, RegistryProperties):
|
|||||||
"""
|
"""
|
||||||
Registry().register('settings_form', self)
|
Registry().register('settings_form', self)
|
||||||
Registry().register_function('bootstrap_post_set_up', self.bootstrap_post_set_up)
|
Registry().register_function('bootstrap_post_set_up', self.bootstrap_post_set_up)
|
||||||
super(SettingsForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint)
|
super(SettingsForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint |
|
||||||
|
QtCore.Qt.WindowCloseButtonHint)
|
||||||
self.processes = []
|
self.processes = []
|
||||||
self.setupUi(self)
|
self.setupUi(self)
|
||||||
self.setting_list_widget.currentRowChanged.connect(self.list_item_changed)
|
self.setting_list_widget.currentRowChanged.connect(self.list_item_changed)
|
||||||
@ -55,12 +57,13 @@ class SettingsForm(QtWidgets.QDialog, Ui_SettingsDialog, RegistryProperties):
|
|||||||
self.projector_tab = None
|
self.projector_tab = None
|
||||||
self.advanced_tab = None
|
self.advanced_tab = None
|
||||||
self.player_tab = None
|
self.player_tab = None
|
||||||
|
self.api_tab = None
|
||||||
|
|
||||||
def exec(self):
|
def exec(self):
|
||||||
"""
|
"""
|
||||||
Execute the form
|
Execute the form
|
||||||
"""
|
"""
|
||||||
# load all the
|
# load all the widgets
|
||||||
self.setting_list_widget.blockSignals(True)
|
self.setting_list_widget.blockSignals(True)
|
||||||
self.setting_list_widget.clear()
|
self.setting_list_widget.clear()
|
||||||
while self.stacked_layout.count():
|
while self.stacked_layout.count():
|
||||||
@ -71,6 +74,7 @@ class SettingsForm(QtWidgets.QDialog, Ui_SettingsDialog, RegistryProperties):
|
|||||||
self.insert_tab(self.advanced_tab)
|
self.insert_tab(self.advanced_tab)
|
||||||
self.insert_tab(self.player_tab)
|
self.insert_tab(self.player_tab)
|
||||||
self.insert_tab(self.projector_tab)
|
self.insert_tab(self.projector_tab)
|
||||||
|
self.insert_tab(self.api_tab)
|
||||||
for plugin in self.plugin_manager.plugins:
|
for plugin in self.plugin_manager.plugins:
|
||||||
if plugin.settings_tab:
|
if plugin.settings_tab:
|
||||||
self.insert_tab(plugin.settings_tab, plugin.is_active())
|
self.insert_tab(plugin.settings_tab, plugin.is_active())
|
||||||
@ -92,6 +96,7 @@ class SettingsForm(QtWidgets.QDialog, Ui_SettingsDialog, RegistryProperties):
|
|||||||
list_item = QtWidgets.QListWidgetItem(build_icon(tab_widget.icon_path), tab_widget.tab_title_visible)
|
list_item = QtWidgets.QListWidgetItem(build_icon(tab_widget.icon_path), tab_widget.tab_title_visible)
|
||||||
list_item.setData(QtCore.Qt.UserRole, tab_widget.tab_title)
|
list_item.setData(QtCore.Qt.UserRole, tab_widget.tab_title)
|
||||||
self.setting_list_widget.addItem(list_item)
|
self.setting_list_widget.addItem(list_item)
|
||||||
|
tab_widget.load()
|
||||||
|
|
||||||
def accept(self):
|
def accept(self):
|
||||||
"""
|
"""
|
||||||
@ -153,10 +158,13 @@ class SettingsForm(QtWidgets.QDialog, Ui_SettingsDialog, RegistryProperties):
|
|||||||
self.advanced_tab = AdvancedTab(self)
|
self.advanced_tab = AdvancedTab(self)
|
||||||
# Advanced tab
|
# Advanced tab
|
||||||
self.player_tab = PlayerTab(self)
|
self.player_tab = PlayerTab(self)
|
||||||
|
# Api tab
|
||||||
|
self.api_tab = ApiTab(self)
|
||||||
self.general_tab.post_set_up()
|
self.general_tab.post_set_up()
|
||||||
self.themes_tab.post_set_up()
|
self.themes_tab.post_set_up()
|
||||||
self.advanced_tab.post_set_up()
|
self.advanced_tab.post_set_up()
|
||||||
self.player_tab.post_set_up()
|
self.player_tab.post_set_up()
|
||||||
|
self.api_tab.post_set_up()
|
||||||
for plugin in self.plugin_manager.plugins:
|
for plugin in self.plugin_manager.plugins:
|
||||||
if plugin.settings_tab:
|
if plugin.settings_tab:
|
||||||
plugin.settings_tab.post_set_up()
|
plugin.settings_tab.post_set_up()
|
||||||
|
@ -44,7 +44,8 @@ class ShortcutListForm(QtWidgets.QDialog, Ui_ShortcutListDialog, RegistryPropert
|
|||||||
"""
|
"""
|
||||||
Constructor
|
Constructor
|
||||||
"""
|
"""
|
||||||
super(ShortcutListForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint)
|
super(ShortcutListForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint |
|
||||||
|
QtCore.Qt.WindowCloseButtonHint)
|
||||||
self.setupUi(self)
|
self.setupUi(self)
|
||||||
self.changed_actions = {}
|
self.changed_actions = {}
|
||||||
self.action_list = ActionList.get_instance()
|
self.action_list = ActionList.get_instance()
|
||||||
@ -279,9 +280,7 @@ class ShortcutListForm(QtWidgets.QDialog, Ui_ShortcutListDialog, RegistryPropert
|
|||||||
return
|
return
|
||||||
if QtWidgets.QMessageBox.question(self, translate('OpenLP.ShortcutListDialog', 'Restore Default Shortcuts'),
|
if QtWidgets.QMessageBox.question(self, translate('OpenLP.ShortcutListDialog', 'Restore Default Shortcuts'),
|
||||||
translate('OpenLP.ShortcutListDialog', 'Do you want to restore all '
|
translate('OpenLP.ShortcutListDialog', 'Do you want to restore all '
|
||||||
'shortcuts to their defaults?'),
|
'shortcuts to their defaults?')
|
||||||
QtWidgets.QMessageBox.StandardButtons(QtWidgets.QMessageBox.Yes |
|
|
||||||
QtWidgets.QMessageBox.No)
|
|
||||||
) == QtWidgets.QMessageBox.No:
|
) == QtWidgets.QMessageBox.No:
|
||||||
return
|
return
|
||||||
self._adjust_button(self.primary_push_button, False, text='')
|
self._adjust_button(self.primary_push_button, False, text='')
|
||||||
|
@ -439,6 +439,10 @@ class SlideController(DisplayController, RegistryProperties):
|
|||||||
# NOTE: {t} used to keep line length < maxline
|
# NOTE: {t} used to keep line length < maxline
|
||||||
getattr(self,
|
getattr(self,
|
||||||
'slidecontroller_{t}_previous'.format(t=self.type_prefix)).connect(self.on_slide_selected_previous)
|
'slidecontroller_{t}_previous'.format(t=self.type_prefix)).connect(self.on_slide_selected_previous)
|
||||||
|
if self.is_live:
|
||||||
|
getattr(self, 'mediacontroller_live_play').connect(self.media_controller.on_media_play)
|
||||||
|
getattr(self, 'mediacontroller_live_pause').connect(self.media_controller.on_media_pause)
|
||||||
|
getattr(self, 'mediacontroller_live_stop').connect(self.media_controller.on_media_stop)
|
||||||
|
|
||||||
def _slide_shortcut_activated(self):
|
def _slide_shortcut_activated(self):
|
||||||
"""
|
"""
|
||||||
@ -1530,6 +1534,9 @@ class LiveController(RegistryMixin, OpenLPMixin, SlideController):
|
|||||||
slidecontroller_live_next = QtCore.pyqtSignal()
|
slidecontroller_live_next = QtCore.pyqtSignal()
|
||||||
slidecontroller_live_previous = QtCore.pyqtSignal()
|
slidecontroller_live_previous = QtCore.pyqtSignal()
|
||||||
slidecontroller_toggle_display = QtCore.pyqtSignal(str)
|
slidecontroller_toggle_display = QtCore.pyqtSignal(str)
|
||||||
|
mediacontroller_live_play = QtCore.pyqtSignal()
|
||||||
|
mediacontroller_live_pause = QtCore.pyqtSignal()
|
||||||
|
mediacontroller_live_stop = QtCore.pyqtSignal()
|
||||||
|
|
||||||
def __init__(self, parent):
|
def __init__(self, parent):
|
||||||
"""
|
"""
|
||||||
|
@ -38,8 +38,8 @@ class StartTimeForm(QtWidgets.QDialog, Ui_StartTimeDialog, RegistryProperties):
|
|||||||
"""
|
"""
|
||||||
Constructor
|
Constructor
|
||||||
"""
|
"""
|
||||||
super(StartTimeForm, self).__init__(Registry().get('main_window'),
|
super(StartTimeForm, self).__init__(Registry().get('main_window'), QtCore.Qt.WindowSystemMenuHint |
|
||||||
QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint)
|
QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowCloseButtonHint)
|
||||||
self.setupUi(self)
|
self.setupUi(self)
|
||||||
|
|
||||||
def exec(self):
|
def exec(self):
|
||||||
|
@ -28,6 +28,7 @@ import os
|
|||||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||||
|
|
||||||
from openlp.core.common import Registry, RegistryProperties, UiStrings, translate, get_images_filter, is_not_image_file
|
from openlp.core.common import Registry, RegistryProperties, UiStrings, translate, get_images_filter, is_not_image_file
|
||||||
|
from openlp.core.common.path import Path, path_to_str, str_to_path
|
||||||
from openlp.core.lib.theme import BackgroundType, BackgroundGradientType
|
from openlp.core.lib.theme import BackgroundType, BackgroundGradientType
|
||||||
from openlp.core.lib.ui import critical_error_message_box
|
from openlp.core.lib.ui import critical_error_message_box
|
||||||
from openlp.core.ui import ThemeLayoutForm
|
from openlp.core.ui import ThemeLayoutForm
|
||||||
@ -187,7 +188,8 @@ class ThemeForm(QtWidgets.QWizard, Ui_ThemeWizard, RegistryProperties):
|
|||||||
"""
|
"""
|
||||||
background_image = BackgroundType.to_string(BackgroundType.Image)
|
background_image = BackgroundType.to_string(BackgroundType.Image)
|
||||||
if self.page(self.currentId()) == self.background_page and \
|
if self.page(self.currentId()) == self.background_page and \
|
||||||
self.theme.background_type == background_image and is_not_image_file(self.theme.background_filename):
|
self.theme.background_type == background_image and \
|
||||||
|
is_not_image_file(Path(self.theme.background_filename)):
|
||||||
QtWidgets.QMessageBox.critical(self, translate('OpenLP.ThemeWizard', 'Background Image Empty'),
|
QtWidgets.QMessageBox.critical(self, translate('OpenLP.ThemeWizard', 'Background Image Empty'),
|
||||||
translate('OpenLP.ThemeWizard', 'You have not selected a '
|
translate('OpenLP.ThemeWizard', 'You have not selected a '
|
||||||
'background image. Please select one before continuing.'))
|
'background image. Please select one before continuing.'))
|
||||||
@ -316,11 +318,11 @@ class ThemeForm(QtWidgets.QWizard, Ui_ThemeWizard, RegistryProperties):
|
|||||||
self.setField('background_type', 1)
|
self.setField('background_type', 1)
|
||||||
elif self.theme.background_type == BackgroundType.to_string(BackgroundType.Image):
|
elif self.theme.background_type == BackgroundType.to_string(BackgroundType.Image):
|
||||||
self.image_color_button.color = self.theme.background_border_color
|
self.image_color_button.color = self.theme.background_border_color
|
||||||
self.image_path_edit.path = self.theme.background_filename
|
self.image_path_edit.path = str_to_path(self.theme.background_filename)
|
||||||
self.setField('background_type', 2)
|
self.setField('background_type', 2)
|
||||||
elif self.theme.background_type == BackgroundType.to_string(BackgroundType.Video):
|
elif self.theme.background_type == BackgroundType.to_string(BackgroundType.Video):
|
||||||
self.video_color_button.color = self.theme.background_border_color
|
self.video_color_button.color = self.theme.background_border_color
|
||||||
self.video_path_edit.path = self.theme.background_filename
|
self.video_path_edit.path = str_to_path(self.theme.background_filename)
|
||||||
self.setField('background_type', 4)
|
self.setField('background_type', 4)
|
||||||
elif self.theme.background_type == BackgroundType.to_string(BackgroundType.Transparent):
|
elif self.theme.background_type == BackgroundType.to_string(BackgroundType.Transparent):
|
||||||
self.setField('background_type', 3)
|
self.setField('background_type', 3)
|
||||||
@ -448,18 +450,18 @@ class ThemeForm(QtWidgets.QWizard, Ui_ThemeWizard, RegistryProperties):
|
|||||||
"""
|
"""
|
||||||
self.theme.background_end_color = color
|
self.theme.background_end_color = color
|
||||||
|
|
||||||
def on_image_path_edit_path_changed(self, filename):
|
def on_image_path_edit_path_changed(self, file_path):
|
||||||
"""
|
"""
|
||||||
Background Image button pushed.
|
Background Image button pushed.
|
||||||
"""
|
"""
|
||||||
self.theme.background_filename = filename
|
self.theme.background_filename = path_to_str(file_path)
|
||||||
self.set_background_page_values()
|
self.set_background_page_values()
|
||||||
|
|
||||||
def on_video_path_edit_path_changed(self, filename):
|
def on_video_path_edit_path_changed(self, file_path):
|
||||||
"""
|
"""
|
||||||
Background video button pushed.
|
Background video button pushed.
|
||||||
"""
|
"""
|
||||||
self.theme.background_filename = filename
|
self.theme.background_filename = path_to_str(file_path)
|
||||||
self.set_background_page_values()
|
self.set_background_page_values()
|
||||||
|
|
||||||
def on_main_color_changed(self, color):
|
def on_main_color_changed(self, color):
|
||||||
|
@ -22,7 +22,6 @@
|
|||||||
"""
|
"""
|
||||||
The Theme Manager manages adding, deleteing and modifying of themes.
|
The Theme Manager manages adding, deleteing and modifying of themes.
|
||||||
"""
|
"""
|
||||||
import json
|
|
||||||
import os
|
import os
|
||||||
import zipfile
|
import zipfile
|
||||||
import shutil
|
import shutil
|
||||||
@ -31,13 +30,15 @@ from xml.etree.ElementTree import ElementTree, XML
|
|||||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||||
|
|
||||||
from openlp.core.common import Registry, RegistryProperties, AppLocation, Settings, OpenLPMixin, RegistryMixin, \
|
from openlp.core.common import Registry, RegistryProperties, AppLocation, Settings, OpenLPMixin, RegistryMixin, \
|
||||||
check_directory_exists, UiStrings, translate, is_win, get_filesystem_encoding, delete_file
|
UiStrings, check_directory_exists, translate, is_win, get_filesystem_encoding, delete_file
|
||||||
from openlp.core.lib import FileDialog, ImageSource, ValidationError, get_text_file_string, build_icon, \
|
from openlp.core.common.path import Path, path_to_str, str_to_path
|
||||||
|
from openlp.core.lib import ImageSource, ValidationError, get_text_file_string, build_icon, \
|
||||||
check_item_selected, create_thumb, validate_thumb
|
check_item_selected, create_thumb, validate_thumb
|
||||||
from openlp.core.lib.theme import Theme, BackgroundType
|
from openlp.core.lib.theme import Theme, BackgroundType
|
||||||
from openlp.core.lib.ui import critical_error_message_box, create_widget_action
|
from openlp.core.lib.ui import critical_error_message_box, create_widget_action
|
||||||
from openlp.core.ui import FileRenameForm, ThemeForm
|
from openlp.core.ui import FileRenameForm, ThemeForm
|
||||||
from openlp.core.ui.lib import OpenLPToolbar
|
from openlp.core.ui.lib import OpenLPToolbar
|
||||||
|
from openlp.core.ui.lib.filedialog import FileDialog
|
||||||
from openlp.core.common.languagemanager import get_locale_key
|
from openlp.core.common.languagemanager import get_locale_key
|
||||||
|
|
||||||
|
|
||||||
@ -159,10 +160,10 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ThemeManage
|
|||||||
"""
|
"""
|
||||||
Set up the theme path variables
|
Set up the theme path variables
|
||||||
"""
|
"""
|
||||||
self.path = AppLocation.get_section_data_path(self.settings_section)
|
self.path = str(AppLocation.get_section_data_path(self.settings_section))
|
||||||
check_directory_exists(self.path)
|
check_directory_exists(Path(self.path))
|
||||||
self.thumb_path = os.path.join(self.path, 'thumbnails')
|
self.thumb_path = os.path.join(self.path, 'thumbnails')
|
||||||
check_directory_exists(self.thumb_path)
|
check_directory_exists(Path(self.thumb_path))
|
||||||
|
|
||||||
def check_list_state(self, item, field=None):
|
def check_list_state(self, item, field=None):
|
||||||
"""
|
"""
|
||||||
@ -257,10 +258,9 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ThemeManage
|
|||||||
Renames an existing theme to a new name
|
Renames an existing theme to a new name
|
||||||
:param field:
|
:param field:
|
||||||
"""
|
"""
|
||||||
# TODO: Check for delayed format() conversions
|
|
||||||
if self._validate_theme_action(translate('OpenLP.ThemeManager', 'You must select a theme to rename.'),
|
if self._validate_theme_action(translate('OpenLP.ThemeManager', 'You must select a theme to rename.'),
|
||||||
translate('OpenLP.ThemeManager', 'Rename Confirmation'),
|
translate('OpenLP.ThemeManager', 'Rename Confirmation'),
|
||||||
translate('OpenLP.ThemeManager', 'Rename %s theme?'), False, False):
|
translate('OpenLP.ThemeManager', 'Rename {theme_name} theme?'), False, False):
|
||||||
item = self.theme_list_widget.currentItem()
|
item = self.theme_list_widget.currentItem()
|
||||||
old_theme_name = item.data(QtCore.Qt.UserRole)
|
old_theme_name = item.data(QtCore.Qt.UserRole)
|
||||||
self.file_rename_form.file_name_edit.setText(old_theme_name)
|
self.file_rename_form.file_name_edit.setText(old_theme_name)
|
||||||
@ -334,10 +334,9 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ThemeManage
|
|||||||
Delete a theme triggered by the UI.
|
Delete a theme triggered by the UI.
|
||||||
:param field:
|
:param field:
|
||||||
"""
|
"""
|
||||||
# TODO: Verify delayed format() conversions
|
|
||||||
if self._validate_theme_action(translate('OpenLP.ThemeManager', 'You must select a theme to delete.'),
|
if self._validate_theme_action(translate('OpenLP.ThemeManager', 'You must select a theme to delete.'),
|
||||||
translate('OpenLP.ThemeManager', 'Delete Confirmation'),
|
translate('OpenLP.ThemeManager', 'Delete Confirmation'),
|
||||||
translate('OpenLP.ThemeManager', 'Delete %s theme?')):
|
translate('OpenLP.ThemeManager', 'Delete {theme_name} theme?')):
|
||||||
item = self.theme_list_widget.currentItem()
|
item = self.theme_list_widget.currentItem()
|
||||||
theme = item.text()
|
theme = item.text()
|
||||||
row = self.theme_list_widget.row(item)
|
row = self.theme_list_widget.row(item)
|
||||||
@ -356,8 +355,8 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ThemeManage
|
|||||||
"""
|
"""
|
||||||
self.theme_list.remove(theme)
|
self.theme_list.remove(theme)
|
||||||
thumb = '{name}.png'.format(name=theme)
|
thumb = '{name}.png'.format(name=theme)
|
||||||
delete_file(os.path.join(self.path, thumb))
|
delete_file(Path(self.path, thumb))
|
||||||
delete_file(os.path.join(self.thumb_path, thumb))
|
delete_file(Path(self.thumb_path, thumb))
|
||||||
try:
|
try:
|
||||||
# Windows is always unicode, so no need to encode filenames
|
# Windows is always unicode, so no need to encode filenames
|
||||||
if is_win():
|
if is_win():
|
||||||
@ -379,16 +378,16 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ThemeManage
|
|||||||
critical_error_message_box(message=translate('OpenLP.ThemeManager', 'You have not selected a theme.'))
|
critical_error_message_box(message=translate('OpenLP.ThemeManager', 'You have not selected a theme.'))
|
||||||
return
|
return
|
||||||
theme = item.data(QtCore.Qt.UserRole)
|
theme = item.data(QtCore.Qt.UserRole)
|
||||||
path, filter_used = \
|
export_path, filter_used = \
|
||||||
QtWidgets.QFileDialog.getSaveFileName(self.main_window,
|
FileDialog.getSaveFileName(self.main_window,
|
||||||
translate('OpenLP.ThemeManager', 'Save Theme - ({name})').
|
translate('OpenLP.ThemeManager', 'Save Theme - ({name})').
|
||||||
format(name=theme),
|
format(name=theme),
|
||||||
Settings().value(self.settings_section + '/last directory export'),
|
Settings().value(self.settings_section + '/last directory export'),
|
||||||
translate('OpenLP.ThemeManager', 'OpenLP Themes (*.otz)'))
|
translate('OpenLP.ThemeManager', 'OpenLP Themes (*.otz)'))
|
||||||
self.application.set_busy_cursor()
|
self.application.set_busy_cursor()
|
||||||
if path:
|
if export_path:
|
||||||
Settings().setValue(self.settings_section + '/last directory export', path)
|
Settings().setValue(self.settings_section + '/last directory export', export_path.parent)
|
||||||
if self._export_theme(path, theme):
|
if self._export_theme(str(export_path), theme):
|
||||||
QtWidgets.QMessageBox.information(self,
|
QtWidgets.QMessageBox.information(self,
|
||||||
translate('OpenLP.ThemeManager', 'Theme Exported'),
|
translate('OpenLP.ThemeManager', 'Theme Exported'),
|
||||||
translate('OpenLP.ThemeManager',
|
translate('OpenLP.ThemeManager',
|
||||||
@ -426,17 +425,18 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ThemeManage
|
|||||||
those files. This process will only load version 2 themes.
|
those files. This process will only load version 2 themes.
|
||||||
:param field:
|
:param field:
|
||||||
"""
|
"""
|
||||||
files = FileDialog.getOpenFileNames(self,
|
file_paths, selected_filter = FileDialog.getOpenFileNames(
|
||||||
translate('OpenLP.ThemeManager', 'Select Theme Import File'),
|
self,
|
||||||
Settings().value(self.settings_section + '/last directory import'),
|
translate('OpenLP.ThemeManager', 'Select Theme Import File'),
|
||||||
translate('OpenLP.ThemeManager', 'OpenLP Themes (*.otz)'))
|
Settings().value(self.settings_section + '/last directory import'),
|
||||||
self.log_info('New Themes {name}'.format(name=str(files)))
|
translate('OpenLP.ThemeManager', 'OpenLP Themes (*.otz)'))
|
||||||
if not files:
|
self.log_info('New Themes {file_paths}'.format(file_paths=file_paths))
|
||||||
|
if not file_paths:
|
||||||
return
|
return
|
||||||
self.application.set_busy_cursor()
|
self.application.set_busy_cursor()
|
||||||
for file_name in files:
|
for file_path in file_paths:
|
||||||
Settings().setValue(self.settings_section + '/last directory import', str(file_name))
|
self.unzip_theme(path_to_str(file_path), self.path)
|
||||||
self.unzip_theme(file_name, self.path)
|
Settings().setValue(self.settings_section + '/last directory import', file_path)
|
||||||
self.load_themes()
|
self.load_themes()
|
||||||
self.application.set_normal_cursor()
|
self.application.set_normal_cursor()
|
||||||
|
|
||||||
@ -447,9 +447,9 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ThemeManage
|
|||||||
self.application.set_busy_cursor()
|
self.application.set_busy_cursor()
|
||||||
files = AppLocation.get_files(self.settings_section, '.otz')
|
files = AppLocation.get_files(self.settings_section, '.otz')
|
||||||
for theme_file in files:
|
for theme_file in files:
|
||||||
theme_file = os.path.join(self.path, theme_file)
|
theme_file = os.path.join(self.path, str(theme_file))
|
||||||
self.unzip_theme(theme_file, self.path)
|
self.unzip_theme(theme_file, self.path)
|
||||||
delete_file(theme_file)
|
delete_file(Path(theme_file))
|
||||||
files = AppLocation.get_files(self.settings_section, '.png')
|
files = AppLocation.get_files(self.settings_section, '.png')
|
||||||
# No themes have been found so create one
|
# No themes have been found so create one
|
||||||
if not files:
|
if not files:
|
||||||
@ -472,6 +472,7 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ThemeManage
|
|||||||
files.sort(key=lambda file_name: get_locale_key(str(file_name)))
|
files.sort(key=lambda file_name: get_locale_key(str(file_name)))
|
||||||
# now process the file list of png files
|
# now process the file list of png files
|
||||||
for name in files:
|
for name in files:
|
||||||
|
name = str(name)
|
||||||
# check to see file is in theme root directory
|
# check to see file is in theme root directory
|
||||||
theme = os.path.join(self.path, name)
|
theme = os.path.join(self.path, name)
|
||||||
if os.path.exists(theme):
|
if os.path.exists(theme):
|
||||||
@ -512,12 +513,12 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ThemeManage
|
|||||||
:return: The theme object.
|
:return: The theme object.
|
||||||
"""
|
"""
|
||||||
self.log_debug('get theme data for theme {name}'.format(name=theme_name))
|
self.log_debug('get theme data for theme {name}'.format(name=theme_name))
|
||||||
theme_file = os.path.join(self.path, str(theme_name), str(theme_name) + '.json')
|
theme_file_path = Path(self.path, str(theme_name), '{file_name}.json'.format(file_name=theme_name))
|
||||||
theme_data = get_text_file_string(theme_file)
|
theme_data = get_text_file_string(theme_file_path)
|
||||||
jsn = True
|
jsn = True
|
||||||
if not theme_data:
|
if not theme_data:
|
||||||
theme_file = os.path.join(self.path, str(theme_name), str(theme_name) + '.xml')
|
theme_file_path = theme_file_path.with_suffix('.xml')
|
||||||
theme_data = get_text_file_string(theme_file)
|
theme_data = get_text_file_string(theme_file_path)
|
||||||
jsn = False
|
jsn = False
|
||||||
if not theme_data:
|
if not theme_data:
|
||||||
self.log_debug('No theme data - using default theme')
|
self.log_debug('No theme data - using default theme')
|
||||||
@ -539,9 +540,7 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ThemeManage
|
|||||||
translate('OpenLP.ThemeManager',
|
translate('OpenLP.ThemeManager',
|
||||||
'Theme {name} already exists. '
|
'Theme {name} already exists. '
|
||||||
'Do you want to replace it?').format(name=theme_name),
|
'Do you want to replace it?').format(name=theme_name),
|
||||||
QtWidgets.QMessageBox.StandardButtons(QtWidgets.QMessageBox.Yes |
|
defaultButton=QtWidgets.QMessageBox.No)
|
||||||
QtWidgets.QMessageBox.No),
|
|
||||||
QtWidgets.QMessageBox.No)
|
|
||||||
return ret == QtWidgets.QMessageBox.Yes
|
return ret == QtWidgets.QMessageBox.Yes
|
||||||
|
|
||||||
def unzip_theme(self, file_name, directory):
|
def unzip_theme(self, file_name, directory):
|
||||||
@ -592,7 +591,7 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ThemeManage
|
|||||||
# is directory or preview file
|
# is directory or preview file
|
||||||
continue
|
continue
|
||||||
full_name = os.path.join(directory, out_name)
|
full_name = os.path.join(directory, out_name)
|
||||||
check_directory_exists(os.path.dirname(full_name))
|
check_directory_exists(Path(os.path.dirname(full_name)))
|
||||||
if os.path.splitext(name)[1].lower() == '.xml' or os.path.splitext(name)[1].lower() == '.json':
|
if os.path.splitext(name)[1].lower() == '.xml' or os.path.splitext(name)[1].lower() == '.json':
|
||||||
file_xml = str(theme_zip.read(name), 'utf-8')
|
file_xml = str(theme_zip.read(name), 'utf-8')
|
||||||
out_file = open(full_name, 'w', encoding='utf-8')
|
out_file = open(full_name, 'w', encoding='utf-8')
|
||||||
@ -670,10 +669,10 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ThemeManage
|
|||||||
name = theme.theme_name
|
name = theme.theme_name
|
||||||
theme_pretty = theme.export_theme()
|
theme_pretty = theme.export_theme()
|
||||||
theme_dir = os.path.join(self.path, name)
|
theme_dir = os.path.join(self.path, name)
|
||||||
check_directory_exists(theme_dir)
|
check_directory_exists(Path(theme_dir))
|
||||||
theme_file = os.path.join(theme_dir, name + '.json')
|
theme_file = os.path.join(theme_dir, name + '.json')
|
||||||
if self.old_background_image and image_to != self.old_background_image:
|
if self.old_background_image and image_to != self.old_background_image:
|
||||||
delete_file(self.old_background_image)
|
delete_file(Path(self.old_background_image))
|
||||||
out_file = None
|
out_file = None
|
||||||
try:
|
try:
|
||||||
out_file = open(theme_file, 'w', encoding='utf-8')
|
out_file = open(theme_file, 'w', encoding='utf-8')
|
||||||
@ -785,9 +784,8 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ThemeManage
|
|||||||
# confirm deletion
|
# confirm deletion
|
||||||
if confirm:
|
if confirm:
|
||||||
answer = QtWidgets.QMessageBox.question(
|
answer = QtWidgets.QMessageBox.question(
|
||||||
self, confirm_title, confirm_text % theme,
|
self, confirm_title, confirm_text.format(theme_name=theme),
|
||||||
QtWidgets.QMessageBox.StandardButtons(QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No),
|
defaultButton=QtWidgets.QMessageBox.No)
|
||||||
QtWidgets.QMessageBox.No)
|
|
||||||
if answer == QtWidgets.QMessageBox.No:
|
if answer == QtWidgets.QMessageBox.No:
|
||||||
return False
|
return False
|
||||||
# should be the same unless default
|
# should be the same unless default
|
||||||
|
@ -25,6 +25,7 @@ The Create/Edit theme wizard
|
|||||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||||
|
|
||||||
from openlp.core.common import UiStrings, translate, is_macosx
|
from openlp.core.common import UiStrings, translate, is_macosx
|
||||||
|
from openlp.core.common.path import Path
|
||||||
from openlp.core.lib import build_icon
|
from openlp.core.lib import build_icon
|
||||||
from openlp.core.lib.theme import HorizontalType, BackgroundType, BackgroundGradientType
|
from openlp.core.lib.theme import HorizontalType, BackgroundType, BackgroundGradientType
|
||||||
from openlp.core.lib.ui import add_welcome_page, create_valign_selection_widgets
|
from openlp.core.lib.ui import add_welcome_page, create_valign_selection_widgets
|
||||||
|
@ -24,13 +24,15 @@ import logging
|
|||||||
|
|
||||||
from PyQt5 import QtGui
|
from PyQt5 import QtGui
|
||||||
|
|
||||||
from openlp.core.common import Settings, translate
|
from openlp.core.api.http import register_endpoint
|
||||||
|
from openlp.core.common import Settings, UiStrings, translate
|
||||||
from openlp.core.common.actions import ActionList
|
from openlp.core.common.actions import ActionList
|
||||||
from openlp.core.lib import Plugin, StringContent, build_icon
|
from openlp.core.lib import Plugin, StringContent, build_icon
|
||||||
from openlp.core.lib.db import Manager
|
from openlp.core.lib.db import Manager
|
||||||
from openlp.core.lib.theme import VerticalType
|
from openlp.core.lib.theme import VerticalType
|
||||||
from openlp.core.lib.ui import create_action, UiStrings
|
from openlp.core.lib.ui import create_action
|
||||||
from openlp.core.ui import AlertLocation
|
from openlp.core.ui import AlertLocation
|
||||||
|
from openlp.plugins.alerts.endpoint import api_alerts_endpoint, alerts_endpoint
|
||||||
from openlp.plugins.alerts.forms import AlertForm
|
from openlp.plugins.alerts.forms import AlertForm
|
||||||
from openlp.plugins.alerts.lib import AlertsManager, AlertsTab
|
from openlp.plugins.alerts.lib import AlertsManager, AlertsTab
|
||||||
from openlp.plugins.alerts.lib.db import init_schema
|
from openlp.plugins.alerts.lib.db import init_schema
|
||||||
@ -88,21 +90,20 @@ JAVASCRIPT = """
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
# TODO: Verify format() with variable templates
|
|
||||||
CSS = """
|
CSS = """
|
||||||
#alert {
|
#alert {{
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0px;
|
left: 0px;
|
||||||
top: 0px;
|
top: 0px;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
width: 100%%;
|
width: 100%;
|
||||||
vertical-align: %s;
|
vertical-align: {vertical_align};
|
||||||
font-family: %s;
|
font-family: {font_family};
|
||||||
font-size: %spt;
|
font-size: {font_size:d}pt;
|
||||||
color: %s;
|
color: {color};
|
||||||
background-color: %s;
|
background-color: {background_color};
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
}
|
}}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
HTML = """
|
HTML = """
|
||||||
@ -141,6 +142,8 @@ class AlertsPlugin(Plugin):
|
|||||||
AlertsManager(self)
|
AlertsManager(self)
|
||||||
self.manager = Manager('alerts', init_schema)
|
self.manager = Manager('alerts', init_schema)
|
||||||
self.alert_form = AlertForm(self)
|
self.alert_form = AlertForm(self)
|
||||||
|
register_endpoint(alerts_endpoint)
|
||||||
|
register_endpoint(api_alerts_endpoint)
|
||||||
|
|
||||||
def add_tools_menu_item(self, tools_menu):
|
def add_tools_menu_item(self, tools_menu):
|
||||||
"""
|
"""
|
||||||
@ -228,8 +231,11 @@ class AlertsPlugin(Plugin):
|
|||||||
Add CSS to the main display.
|
Add CSS to the main display.
|
||||||
"""
|
"""
|
||||||
align = VerticalType.Names[self.settings_tab.location]
|
align = VerticalType.Names[self.settings_tab.location]
|
||||||
return CSS % (align, self.settings_tab.font_face, self.settings_tab.font_size, self.settings_tab.font_color,
|
return CSS.format(vertical_align=align,
|
||||||
self.settings_tab.background_color)
|
font_family=self.settings_tab.font_face,
|
||||||
|
font_size=self.settings_tab.font_size,
|
||||||
|
color=self.settings_tab.font_color,
|
||||||
|
background_color=self.settings_tab.background_color)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_display_html():
|
def get_display_html():
|
||||||
|
60
openlp/plugins/alerts/endpoint.py
Normal file
60
openlp/plugins/alerts/endpoint.py
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# OpenLP - Open Source Lyrics Projection #
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# Copyright (c) 2008-2017 OpenLP Developers #
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# This program is free software; you can redistribute it and/or modify it #
|
||||||
|
# under the terms of the GNU General Public License as published by the Free #
|
||||||
|
# Software Foundation; version 2 of the License. #
|
||||||
|
# #
|
||||||
|
# This program is distributed in the hope that it will be useful, but WITHOUT #
|
||||||
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
|
||||||
|
# more details. #
|
||||||
|
# #
|
||||||
|
# You should have received a copy of the GNU General Public License along #
|
||||||
|
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||||
|
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||||
|
###############################################################################
|
||||||
|
import logging
|
||||||
|
import json
|
||||||
|
import urllib
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
|
from openlp.core.api.http.endpoint import Endpoint
|
||||||
|
from openlp.core.api.http import requires_auth
|
||||||
|
from openlp.core.common import Registry
|
||||||
|
from openlp.core.lib import PluginStatus
|
||||||
|
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
alerts_endpoint = Endpoint('alert')
|
||||||
|
api_alerts_endpoint = Endpoint('api')
|
||||||
|
|
||||||
|
|
||||||
|
@alerts_endpoint.route('')
|
||||||
|
@api_alerts_endpoint.route('alert')
|
||||||
|
@requires_auth
|
||||||
|
def alert(request):
|
||||||
|
"""
|
||||||
|
Handles requests for setting service items in the service manager
|
||||||
|
|
||||||
|
:param request: The http request object.
|
||||||
|
"""
|
||||||
|
plugin = Registry().get('plugin_manager').get_plugin_by_name("alerts")
|
||||||
|
if plugin.status == PluginStatus.Active:
|
||||||
|
try:
|
||||||
|
json_data = request.GET.get('data')
|
||||||
|
text = json.loads(json_data)['request']['text']
|
||||||
|
except KeyError:
|
||||||
|
log.error("Endpoint alerts request text not found")
|
||||||
|
text = urllib.parse.unquote(text)
|
||||||
|
Registry().get('alerts_manager').alerts_text.emit([text])
|
||||||
|
success = True
|
||||||
|
else:
|
||||||
|
success = False
|
||||||
|
return {'results': {'success': success}}
|
@ -36,8 +36,8 @@ class AlertForm(QtWidgets.QDialog, Ui_AlertDialog):
|
|||||||
"""
|
"""
|
||||||
Initialise the alert form
|
Initialise the alert form
|
||||||
"""
|
"""
|
||||||
super(AlertForm, self).__init__(Registry().get('main_window'),
|
super(AlertForm, self).__init__(Registry().get('main_window'), QtCore.Qt.WindowSystemMenuHint |
|
||||||
QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint)
|
QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowCloseButtonHint)
|
||||||
self.manager = plugin.manager
|
self.manager = plugin.manager
|
||||||
self.plugin = plugin
|
self.plugin = plugin
|
||||||
self.item_id = None
|
self.item_id = None
|
||||||
@ -180,9 +180,7 @@ class AlertForm(QtWidgets.QDialog, Ui_AlertDialog):
|
|||||||
translate('AlertsPlugin.AlertForm', 'No Parameter Found'),
|
translate('AlertsPlugin.AlertForm', 'No Parameter Found'),
|
||||||
translate('AlertsPlugin.AlertForm',
|
translate('AlertsPlugin.AlertForm',
|
||||||
'You have not entered a parameter to be replaced.\n'
|
'You have not entered a parameter to be replaced.\n'
|
||||||
'Do you want to continue anyway?'),
|
'Do you want to continue anyway?')
|
||||||
QtWidgets.QMessageBox.StandardButtons(
|
|
||||||
QtWidgets.QMessageBox.No | QtWidgets.QMessageBox.Yes)
|
|
||||||
) == QtWidgets.QMessageBox.No:
|
) == QtWidgets.QMessageBox.No:
|
||||||
self.parameter_edit.setFocus()
|
self.parameter_edit.setFocus()
|
||||||
return False
|
return False
|
||||||
@ -193,9 +191,7 @@ class AlertForm(QtWidgets.QDialog, Ui_AlertDialog):
|
|||||||
translate('AlertsPlugin.AlertForm', 'No Placeholder Found'),
|
translate('AlertsPlugin.AlertForm', 'No Placeholder Found'),
|
||||||
translate('AlertsPlugin.AlertForm',
|
translate('AlertsPlugin.AlertForm',
|
||||||
'The alert text does not contain \'<>\'.\n'
|
'The alert text does not contain \'<>\'.\n'
|
||||||
'Do you want to continue anyway?'),
|
'Do you want to continue anyway?')
|
||||||
QtWidgets.QMessageBox.StandardButtons(
|
|
||||||
QtWidgets.QMessageBox.No | QtWidgets.QMessageBox.Yes)
|
|
||||||
) == QtWidgets.QMessageBox.No:
|
) == QtWidgets.QMessageBox.No:
|
||||||
self.parameter_edit.setFocus()
|
self.parameter_edit.setFocus()
|
||||||
return False
|
return False
|
||||||
|
@ -105,7 +105,7 @@ class AlertsTab(SettingsTab):
|
|||||||
self.timeout_label.setText(translate('AlertsPlugin.AlertsTab', 'Alert timeout:'))
|
self.timeout_label.setText(translate('AlertsPlugin.AlertsTab', 'Alert timeout:'))
|
||||||
self.timeout_spin_box.setSuffix(' {unit}'.format(unit=UiStrings().Seconds))
|
self.timeout_spin_box.setSuffix(' {unit}'.format(unit=UiStrings().Seconds))
|
||||||
self.preview_group_box.setTitle(UiStrings().Preview)
|
self.preview_group_box.setTitle(UiStrings().Preview)
|
||||||
self.font_preview.setText(UiStrings().OLPV2x)
|
self.font_preview.setText(UiStrings().OpenLP)
|
||||||
|
|
||||||
def on_background_color_changed(self, color):
|
def on_background_color_changed(self, color):
|
||||||
"""
|
"""
|
||||||
|
@ -22,9 +22,12 @@
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from openlp.core.api.http import register_endpoint
|
||||||
|
from openlp.core.common import UiStrings
|
||||||
from openlp.core.common.actions import ActionList
|
from openlp.core.common.actions import ActionList
|
||||||
from openlp.core.lib import Plugin, StringContent, build_icon, translate
|
from openlp.core.lib import Plugin, StringContent, build_icon, translate
|
||||||
from openlp.core.lib.ui import UiStrings, create_action
|
from openlp.plugins.bibles.endpoint import api_bibles_endpoint, bibles_endpoint
|
||||||
|
from openlp.core.lib.ui import create_action
|
||||||
from openlp.plugins.bibles.lib import BibleManager, BiblesTab, BibleMediaItem, LayoutStyle, DisplayStyle, \
|
from openlp.plugins.bibles.lib import BibleManager, BiblesTab, BibleMediaItem, LayoutStyle, DisplayStyle, \
|
||||||
LanguageSelection
|
LanguageSelection
|
||||||
from openlp.plugins.bibles.lib.mediaitem import BibleSearch
|
from openlp.plugins.bibles.lib.mediaitem import BibleSearch
|
||||||
@ -38,7 +41,7 @@ __default_settings__ = {
|
|||||||
'bibles/db password': '',
|
'bibles/db password': '',
|
||||||
'bibles/db hostname': '',
|
'bibles/db hostname': '',
|
||||||
'bibles/db database': '',
|
'bibles/db database': '',
|
||||||
'bibles/last search type': BibleSearch.Combined,
|
'bibles/last used search type': BibleSearch.Combined,
|
||||||
'bibles/reset to combined quick search': True,
|
'bibles/reset to combined quick search': True,
|
||||||
'bibles/verse layout style': LayoutStyle.VersePerSlide,
|
'bibles/verse layout style': LayoutStyle.VersePerSlide,
|
||||||
'bibles/book name language': LanguageSelection.Bible,
|
'bibles/book name language': LanguageSelection.Bible,
|
||||||
@ -56,7 +59,7 @@ __default_settings__ = {
|
|||||||
'bibles/range separator': '',
|
'bibles/range separator': '',
|
||||||
'bibles/list separator': '',
|
'bibles/list separator': '',
|
||||||
'bibles/end separator': '',
|
'bibles/end separator': '',
|
||||||
'bibles/last directory import': '',
|
'bibles/last directory import': None,
|
||||||
'bibles/hide combined quick error': False,
|
'bibles/hide combined quick error': False,
|
||||||
'bibles/is search while typing enabled': True
|
'bibles/is search while typing enabled': True
|
||||||
}
|
}
|
||||||
@ -74,6 +77,8 @@ class BiblePlugin(Plugin):
|
|||||||
self.icon_path = ':/plugins/plugin_bibles.png'
|
self.icon_path = ':/plugins/plugin_bibles.png'
|
||||||
self.icon = build_icon(self.icon_path)
|
self.icon = build_icon(self.icon_path)
|
||||||
self.manager = BibleManager(self)
|
self.manager = BibleManager(self)
|
||||||
|
register_endpoint(bibles_endpoint)
|
||||||
|
register_endpoint(api_bibles_endpoint)
|
||||||
|
|
||||||
def initialise(self):
|
def initialise(self):
|
||||||
"""
|
"""
|
||||||
|
100
openlp/plugins/bibles/endpoint.py
Normal file
100
openlp/plugins/bibles/endpoint.py
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# OpenLP - Open Source Lyrics Projection #
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# Copyright (c) 2008-2017 OpenLP Developers #
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# This program is free software; you can redistribute it and/or modify it #
|
||||||
|
# under the terms of the GNU General Public License as published by the Free #
|
||||||
|
# Software Foundation; version 2 of the License. #
|
||||||
|
# #
|
||||||
|
# This program is distributed in the hope that it will be useful, but WITHOUT #
|
||||||
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
|
||||||
|
# more details. #
|
||||||
|
# #
|
||||||
|
# You should have received a copy of the GNU General Public License along #
|
||||||
|
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||||
|
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||||
|
###############################################################################
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from openlp.core.api.http.endpoint import Endpoint
|
||||||
|
from openlp.core.api.http.errors import NotFound
|
||||||
|
from openlp.core.api.endpoint.pluginhelpers import search, live, service
|
||||||
|
from openlp.core.api.http import requires_auth
|
||||||
|
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
bibles_endpoint = Endpoint('bibles')
|
||||||
|
api_bibles_endpoint = Endpoint('api')
|
||||||
|
|
||||||
|
|
||||||
|
@bibles_endpoint.route('search')
|
||||||
|
def bibles_search(request):
|
||||||
|
"""
|
||||||
|
Handles requests for searching the bibles plugin
|
||||||
|
|
||||||
|
:param request: The http request object.
|
||||||
|
"""
|
||||||
|
return search(request, 'bibles', log)
|
||||||
|
|
||||||
|
|
||||||
|
@bibles_endpoint.route('live')
|
||||||
|
@requires_auth
|
||||||
|
def bibles_live(request):
|
||||||
|
"""
|
||||||
|
Handles requests for making a song live
|
||||||
|
|
||||||
|
:param request: The http request object.
|
||||||
|
"""
|
||||||
|
return live(request, 'bibles', log)
|
||||||
|
|
||||||
|
|
||||||
|
@bibles_endpoint.route('add')
|
||||||
|
@requires_auth
|
||||||
|
def bibles_service(request):
|
||||||
|
"""
|
||||||
|
Handles requests for adding a song to the service
|
||||||
|
|
||||||
|
:param request: The http request object.
|
||||||
|
"""
|
||||||
|
service(request, 'bibles', log)
|
||||||
|
|
||||||
|
|
||||||
|
@api_bibles_endpoint.route('bibles/search')
|
||||||
|
def bibles_search_api(request):
|
||||||
|
"""
|
||||||
|
Handles requests for searching the bibles plugin
|
||||||
|
|
||||||
|
:param request: The http request object.
|
||||||
|
"""
|
||||||
|
return search(request, 'bibles', log)
|
||||||
|
|
||||||
|
|
||||||
|
@api_bibles_endpoint.route('bibles/live')
|
||||||
|
@requires_auth
|
||||||
|
def bibles_live_api(request):
|
||||||
|
"""
|
||||||
|
Handles requests for making a song live
|
||||||
|
|
||||||
|
:param request: The http request object.
|
||||||
|
"""
|
||||||
|
return live(request, 'bibles', log)
|
||||||
|
|
||||||
|
|
||||||
|
@api_bibles_endpoint.route('bibles/add')
|
||||||
|
@requires_auth
|
||||||
|
def bibles_service_api(request):
|
||||||
|
"""
|
||||||
|
Handles requests for adding a song to the service
|
||||||
|
|
||||||
|
:param request: The http request object.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
search(request, 'bibles', log)
|
||||||
|
except NotFound:
|
||||||
|
return {'results': {'items': []}}
|
@ -421,8 +421,8 @@ class BibleImportForm(OpenLPWizard):
|
|||||||
Allow for localisation of the bible import wizard.
|
Allow for localisation of the bible import wizard.
|
||||||
"""
|
"""
|
||||||
self.setWindowTitle(translate('BiblesPlugin.ImportWizardForm', 'Bible Import Wizard'))
|
self.setWindowTitle(translate('BiblesPlugin.ImportWizardForm', 'Bible Import Wizard'))
|
||||||
self.title_label.setText(WizardStrings.HeaderStyle % translate('OpenLP.Ui',
|
self.title_label.setText(WizardStrings.HeaderStyle.format(text=translate('OpenLP.Ui',
|
||||||
'Welcome to the Bible Import Wizard'))
|
'Welcome to the Bible Import Wizard')))
|
||||||
self.information_label.setText(
|
self.information_label.setText(
|
||||||
translate('BiblesPlugin.ImportWizardForm',
|
translate('BiblesPlugin.ImportWizardForm',
|
||||||
'This wizard will help you to import Bibles from a variety of '
|
'This wizard will help you to import Bibles from a variety of '
|
||||||
@ -584,7 +584,7 @@ class BibleImportForm(OpenLPWizard):
|
|||||||
elif self.currentPage() == self.license_details_page:
|
elif self.currentPage() == self.license_details_page:
|
||||||
license_version = self.field('license_version')
|
license_version = self.field('license_version')
|
||||||
license_copyright = self.field('license_copyright')
|
license_copyright = self.field('license_copyright')
|
||||||
path = AppLocation.get_section_data_path('bibles')
|
path = str(AppLocation.get_section_data_path('bibles'))
|
||||||
if not license_version:
|
if not license_version:
|
||||||
critical_error_message_box(
|
critical_error_message_box(
|
||||||
UiStrings().EmptyField,
|
UiStrings().EmptyField,
|
||||||
|
@ -49,7 +49,8 @@ class BookNameForm(QDialog, Ui_BookNameDialog):
|
|||||||
"""
|
"""
|
||||||
Constructor
|
Constructor
|
||||||
"""
|
"""
|
||||||
super(BookNameForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint)
|
super(BookNameForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint |
|
||||||
|
QtCore.Qt.WindowCloseButtonHint)
|
||||||
self.setupUi(self)
|
self.setupUi(self)
|
||||||
self.custom_signals()
|
self.custom_signals()
|
||||||
self.book_names = BibleStrings().BookNames
|
self.book_names = BibleStrings().BookNames
|
||||||
|
@ -45,7 +45,8 @@ class EditBibleForm(QtWidgets.QDialog, Ui_EditBibleDialog, RegistryProperties):
|
|||||||
"""
|
"""
|
||||||
Constructor
|
Constructor
|
||||||
"""
|
"""
|
||||||
super(EditBibleForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint)
|
super(EditBibleForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint |
|
||||||
|
QtCore.Qt.WindowCloseButtonHint)
|
||||||
self.media_item = media_item
|
self.media_item = media_item
|
||||||
self.book_names = BibleStrings().BookNames
|
self.book_names = BibleStrings().BookNames
|
||||||
self.setupUi(self)
|
self.setupUi(self)
|
||||||
|
@ -47,7 +47,8 @@ class LanguageForm(QDialog, Ui_LanguageDialog):
|
|||||||
"""
|
"""
|
||||||
Constructor
|
Constructor
|
||||||
"""
|
"""
|
||||||
super(LanguageForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint)
|
super(LanguageForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint |
|
||||||
|
QtCore.Qt.WindowCloseButtonHint)
|
||||||
self.setupUi(self)
|
self.setupUi(self)
|
||||||
|
|
||||||
def exec(self, bible_name):
|
def exec(self, bible_name):
|
||||||
|
@ -221,18 +221,16 @@ def update_reference_separators():
|
|||||||
REFERENCE_SEPARATORS['sep_{role}'.format(role=role)] = '\s*(?:{source})\s*'.format(source=source_string)
|
REFERENCE_SEPARATORS['sep_{role}'.format(role=role)] = '\s*(?:{source})\s*'.format(source=source_string)
|
||||||
REFERENCE_SEPARATORS['sep_{role}_default'.format(role=role)] = default_separators[index]
|
REFERENCE_SEPARATORS['sep_{role}_default'.format(role=role)] = default_separators[index]
|
||||||
# verse range match: (<chapter>:)?<verse>(-((<chapter>:)?<verse>|end)?)?
|
# verse range match: (<chapter>:)?<verse>(-((<chapter>:)?<verse>|end)?)?
|
||||||
# TODO: Check before converting this string
|
range_regex = '(?:(?P<from_chapter>[0-9]+){sep_v})?' \
|
||||||
range_regex = '(?:(?P<from_chapter>[0-9]+)%(sep_v)s)?' \
|
'(?P<from_verse>[0-9]+)(?P<range_to>{sep_r}(?:(?:(?P<to_chapter>' \
|
||||||
'(?P<from_verse>[0-9]+)(?P<range_to>%(sep_r)s(?:(?:(?P<to_chapter>' \
|
'[0-9]+){sep_v})?(?P<to_verse>[0-9]+)|{sep_e})?)?'.format_map(REFERENCE_SEPARATORS)
|
||||||
'[0-9]+)%(sep_v)s)?(?P<to_verse>[0-9]+)|%(sep_e)s)?)?' % REFERENCE_SEPARATORS
|
REFERENCE_MATCHES['range'] = re.compile(r'^\s*{range}\s*$'.format(range=range_regex), re.UNICODE)
|
||||||
# TODO: Test before converting re.compile strings
|
|
||||||
REFERENCE_MATCHES['range'] = re.compile('^\s*%s\s*$' % range_regex, re.UNICODE)
|
|
||||||
REFERENCE_MATCHES['range_separator'] = re.compile(REFERENCE_SEPARATORS['sep_l'], re.UNICODE)
|
REFERENCE_MATCHES['range_separator'] = re.compile(REFERENCE_SEPARATORS['sep_l'], re.UNICODE)
|
||||||
# full reference match: <book>(<range>(,(?!$)|(?=$)))+
|
# full reference match: <book>(<range>(,(?!$)|(?=$)))+
|
||||||
REFERENCE_MATCHES['full'] = \
|
REFERENCE_MATCHES['full'] = \
|
||||||
re.compile('^\s*(?!\s)(?P<book>[\d]*[^\d\.]+)\.*(?<!\s)\s*'
|
re.compile(r'^\s*(?!\s)(?P<book>[\d]*[.]?[^\d\.]+)\.*(?<!\s)\s*'
|
||||||
'(?P<ranges>(?:%(range_regex)s(?:%(sep_l)s(?!\s*$)|(?=\s*$)))+)\s*$'
|
r'(?P<ranges>(?:{range_regex}(?:{sep_l}(?!\s*$)|(?=\s*$)))+)\s*$'.format(
|
||||||
% dict(list(REFERENCE_SEPARATORS.items()) + [('range_regex', range_regex)]), re.UNICODE)
|
range_regex=range_regex, sep_l=REFERENCE_SEPARATORS['sep_l']), re.UNICODE)
|
||||||
|
|
||||||
|
|
||||||
def get_reference_separator(separator_type):
|
def get_reference_separator(separator_type):
|
||||||
@ -326,7 +324,7 @@ def parse_reference(reference, bible, language_selection, book_ref_id=False):
|
|||||||
|
|
||||||
``^\s*(?!\s)(?P<book>[\d]*[^\d]+)(?<!\s)\s*``
|
``^\s*(?!\s)(?P<book>[\d]*[^\d]+)(?<!\s)\s*``
|
||||||
The ``book`` group starts with the first non-whitespace character. There are optional leading digits followed by
|
The ``book`` group starts with the first non-whitespace character. There are optional leading digits followed by
|
||||||
non-digits. The group ends before the whitspace, or a full stop in front of the next digit.
|
non-digits. The group ends before the whitespace, or a full stop in front of the next digit.
|
||||||
|
|
||||||
``(?P<ranges>(?:%(range_regex)s(?:%(sep_l)s(?!\s*$)|(?=\s*$)))+)\s*$``
|
``(?P<ranges>(?:%(range_regex)s(?:%(sep_l)s(?!\s*$)|(?=\s*$)))+)\s*$``
|
||||||
The second group contains all ``ranges``. This can be multiple declarations of range_regex separated by a list
|
The second group contains all ``ranges``. This can be multiple declarations of range_regex separated by a list
|
||||||
|
@ -306,9 +306,8 @@ class BibleDB(Manager):
|
|||||||
book_escaped = book
|
book_escaped = book
|
||||||
for character in RESERVED_CHARACTERS:
|
for character in RESERVED_CHARACTERS:
|
||||||
book_escaped = book_escaped.replace(character, '\\' + character)
|
book_escaped = book_escaped.replace(character, '\\' + character)
|
||||||
# TODO: Verify regex patters before using format()
|
regex_book = re.compile('\\s*{book}\\s*'.format(book='\\s*'.join(book_escaped.split())),
|
||||||
regex_book = re.compile('\s*%s\s*' % '\s*'.join(
|
re.UNICODE | re.IGNORECASE)
|
||||||
book_escaped.split()), re.UNICODE | re.IGNORECASE)
|
|
||||||
if language_selection == LanguageSelection.Bible:
|
if language_selection == LanguageSelection.Bible:
|
||||||
db_book = self.get_book(book)
|
db_book = self.get_book(book)
|
||||||
if db_book:
|
if db_book:
|
||||||
@ -471,7 +470,7 @@ class BiblesResourcesDB(QtCore.QObject, Manager):
|
|||||||
Return the cursor object. Instantiate one if it doesn't exist yet.
|
Return the cursor object. Instantiate one if it doesn't exist yet.
|
||||||
"""
|
"""
|
||||||
if BiblesResourcesDB.cursor is None:
|
if BiblesResourcesDB.cursor is None:
|
||||||
file_path = os.path.join(AppLocation.get_directory(AppLocation.PluginsDir),
|
file_path = os.path.join(str(AppLocation.get_directory(AppLocation.PluginsDir)),
|
||||||
'bibles', 'resources', 'bibles_resources.sqlite')
|
'bibles', 'resources', 'bibles_resources.sqlite')
|
||||||
conn = sqlite3.connect(file_path)
|
conn = sqlite3.connect(file_path)
|
||||||
BiblesResourcesDB.cursor = conn.cursor()
|
BiblesResourcesDB.cursor = conn.cursor()
|
||||||
@ -760,7 +759,7 @@ class AlternativeBookNamesDB(QtCore.QObject, Manager):
|
|||||||
"""
|
"""
|
||||||
if AlternativeBookNamesDB.cursor is None:
|
if AlternativeBookNamesDB.cursor is None:
|
||||||
file_path = os.path.join(
|
file_path = os.path.join(
|
||||||
AppLocation.get_directory(AppLocation.DataDir), 'bibles', 'alternative_book_names.sqlite')
|
str(AppLocation.get_directory(AppLocation.DataDir)), 'bibles', 'alternative_book_names.sqlite')
|
||||||
if not os.path.exists(file_path):
|
if not os.path.exists(file_path):
|
||||||
# create new DB, create table alternative_book_names
|
# create new DB, create table alternative_book_names
|
||||||
AlternativeBookNamesDB.conn = sqlite3.connect(file_path)
|
AlternativeBookNamesDB.conn = sqlite3.connect(file_path)
|
||||||
|
@ -53,6 +53,7 @@ import csv
|
|||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
|
||||||
from openlp.core.common import get_file_encoding, translate
|
from openlp.core.common import get_file_encoding, translate
|
||||||
|
from openlp.core.common.path import Path
|
||||||
from openlp.core.lib.exceptions import ValidationError
|
from openlp.core.lib.exceptions import ValidationError
|
||||||
from openlp.plugins.bibles.lib.bibleimport import BibleImport
|
from openlp.plugins.bibles.lib.bibleimport import BibleImport
|
||||||
|
|
||||||
@ -100,7 +101,7 @@ class CSVBible(BibleImport):
|
|||||||
:return: An iterable yielding namedtuples of type results_tuple
|
:return: An iterable yielding namedtuples of type results_tuple
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
encoding = get_file_encoding(filename)['encoding']
|
encoding = get_file_encoding(Path(filename))['encoding']
|
||||||
with open(filename, 'r', encoding=encoding, newline='') as csv_file:
|
with open(filename, 'r', encoding=encoding, newline='') as csv_file:
|
||||||
csv_reader = csv.reader(csv_file, delimiter=',', quotechar='"')
|
csv_reader = csv.reader(csv_file, delimiter=',', quotechar='"')
|
||||||
return [results_tuple(*line) for line in csv_reader]
|
return [results_tuple(*line) for line in csv_reader]
|
||||||
|
@ -90,6 +90,8 @@ class BGExtract(RegistryProperties):
|
|||||||
"""
|
"""
|
||||||
Extract verses from BibleGateway
|
Extract verses from BibleGateway
|
||||||
"""
|
"""
|
||||||
|
NAME = 'BibleGateway'
|
||||||
|
|
||||||
def __init__(self, proxy_url=None):
|
def __init__(self, proxy_url=None):
|
||||||
log.debug('BGExtract.init("{url}")'.format(url=proxy_url))
|
log.debug('BGExtract.init("{url}")'.format(url=proxy_url))
|
||||||
self.proxy_url = proxy_url
|
self.proxy_url = proxy_url
|
||||||
@ -253,7 +255,7 @@ class BGExtract(RegistryProperties):
|
|||||||
chapter=chapter,
|
chapter=chapter,
|
||||||
version=version)
|
version=version)
|
||||||
soup = get_soup_for_bible_ref(
|
soup = get_soup_for_bible_ref(
|
||||||
'http://biblegateway.com/passage/?{url}'.format(url=url_params),
|
'http://www.biblegateway.com/passage/?{url}'.format(url=url_params),
|
||||||
pre_parse_regex=r'<meta name.*?/>', pre_parse_substitute='')
|
pre_parse_regex=r'<meta name.*?/>', pre_parse_substitute='')
|
||||||
if not soup:
|
if not soup:
|
||||||
return None
|
return None
|
||||||
@ -282,7 +284,7 @@ class BGExtract(RegistryProperties):
|
|||||||
"""
|
"""
|
||||||
log.debug('BGExtract.get_books_from_http("{version}")'.format(version=version))
|
log.debug('BGExtract.get_books_from_http("{version}")'.format(version=version))
|
||||||
url_params = urllib.parse.urlencode({'action': 'getVersionInfo', 'vid': '{version}'.format(version=version)})
|
url_params = urllib.parse.urlencode({'action': 'getVersionInfo', 'vid': '{version}'.format(version=version)})
|
||||||
reference_url = 'http://biblegateway.com/versions/?{url}#books'.format(url=url_params)
|
reference_url = 'http://www.biblegateway.com/versions/?{url}#books'.format(url=url_params)
|
||||||
page = get_web_page(reference_url)
|
page = get_web_page(reference_url)
|
||||||
if not page:
|
if not page:
|
||||||
send_error_message('download')
|
send_error_message('download')
|
||||||
@ -323,7 +325,7 @@ class BGExtract(RegistryProperties):
|
|||||||
returns a list in the form [(biblename, biblekey, language_code)]
|
returns a list in the form [(biblename, biblekey, language_code)]
|
||||||
"""
|
"""
|
||||||
log.debug('BGExtract.get_bibles_from_http')
|
log.debug('BGExtract.get_bibles_from_http')
|
||||||
bible_url = 'https://biblegateway.com/versions/'
|
bible_url = 'https://www.biblegateway.com/versions/'
|
||||||
soup = get_soup_for_bible_ref(bible_url)
|
soup = get_soup_for_bible_ref(bible_url)
|
||||||
if not soup:
|
if not soup:
|
||||||
return None
|
return None
|
||||||
@ -357,6 +359,8 @@ class BSExtract(RegistryProperties):
|
|||||||
"""
|
"""
|
||||||
Extract verses from Bibleserver.com
|
Extract verses from Bibleserver.com
|
||||||
"""
|
"""
|
||||||
|
NAME = 'BibleServer'
|
||||||
|
|
||||||
def __init__(self, proxy_url=None):
|
def __init__(self, proxy_url=None):
|
||||||
log.debug('BSExtract.init("{url}")'.format(url=proxy_url))
|
log.debug('BSExtract.init("{url}")'.format(url=proxy_url))
|
||||||
self.proxy_url = proxy_url
|
self.proxy_url = proxy_url
|
||||||
@ -458,6 +462,8 @@ class CWExtract(RegistryProperties):
|
|||||||
"""
|
"""
|
||||||
Extract verses from CrossWalk/BibleStudyTools
|
Extract verses from CrossWalk/BibleStudyTools
|
||||||
"""
|
"""
|
||||||
|
NAME = 'Crosswalk'
|
||||||
|
|
||||||
def __init__(self, proxy_url=None):
|
def __init__(self, proxy_url=None):
|
||||||
log.debug('CWExtract.init("{url}")'.format(url=proxy_url))
|
log.debug('CWExtract.init("{url}")'.format(url=proxy_url))
|
||||||
self.proxy_url = proxy_url
|
self.proxy_url = proxy_url
|
||||||
@ -767,7 +773,7 @@ def get_soup_for_bible_ref(reference_url, header=None, pre_parse_regex=None, pre
|
|||||||
return None
|
return None
|
||||||
try:
|
try:
|
||||||
page = get_web_page(reference_url, header, True)
|
page = get_web_page(reference_url, header, True)
|
||||||
except:
|
except Exception as e:
|
||||||
page = None
|
page = None
|
||||||
if not page:
|
if not page:
|
||||||
send_error_message('download')
|
send_error_message('download')
|
||||||
|
@ -21,9 +21,9 @@
|
|||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
|
||||||
|
|
||||||
from openlp.core.common import AppLocation, OpenLPMixin, RegistryProperties, Settings, translate, delete_file, UiStrings
|
from openlp.core.common import AppLocation, OpenLPMixin, RegistryProperties, Settings, translate, delete_file, UiStrings
|
||||||
|
from openlp.core.common.path import Path
|
||||||
from openlp.plugins.bibles.lib import LanguageSelection, parse_reference
|
from openlp.plugins.bibles.lib import LanguageSelection, parse_reference
|
||||||
from openlp.plugins.bibles.lib.db import BibleDB, BibleMeta
|
from openlp.plugins.bibles.lib.db import BibleDB, BibleMeta
|
||||||
from .importers.csvbible import CSVBible
|
from .importers.csvbible import CSVBible
|
||||||
@ -111,7 +111,7 @@ class BibleManager(OpenLPMixin, RegistryProperties):
|
|||||||
self.settings_section = 'bibles'
|
self.settings_section = 'bibles'
|
||||||
self.web = 'Web'
|
self.web = 'Web'
|
||||||
self.db_cache = None
|
self.db_cache = None
|
||||||
self.path = AppLocation.get_section_data_path(self.settings_section)
|
self.path = str(AppLocation.get_section_data_path(self.settings_section))
|
||||||
self.proxy_name = Settings().value(self.settings_section + '/proxy name')
|
self.proxy_name = Settings().value(self.settings_section + '/proxy name')
|
||||||
self.suffix = '.sqlite'
|
self.suffix = '.sqlite'
|
||||||
self.import_wizard = None
|
self.import_wizard = None
|
||||||
@ -124,7 +124,7 @@ class BibleManager(OpenLPMixin, RegistryProperties):
|
|||||||
of HTTPBible is loaded instead of the BibleDB class.
|
of HTTPBible is loaded instead of the BibleDB class.
|
||||||
"""
|
"""
|
||||||
log.debug('Reload bibles')
|
log.debug('Reload bibles')
|
||||||
files = AppLocation.get_files(self.settings_section, self.suffix)
|
files = [str(file) for file in AppLocation.get_files(self.settings_section, self.suffix)]
|
||||||
if 'alternative_book_names.sqlite' in files:
|
if 'alternative_book_names.sqlite' in files:
|
||||||
files.remove('alternative_book_names.sqlite')
|
files.remove('alternative_book_names.sqlite')
|
||||||
log.debug('Bible Files {text}'.format(text=files))
|
log.debug('Bible Files {text}'.format(text=files))
|
||||||
@ -137,7 +137,7 @@ class BibleManager(OpenLPMixin, RegistryProperties):
|
|||||||
# Remove corrupted files.
|
# Remove corrupted files.
|
||||||
if name is None:
|
if name is None:
|
||||||
bible.session.close_all()
|
bible.session.close_all()
|
||||||
delete_file(os.path.join(self.path, filename))
|
delete_file(Path(self.path, filename))
|
||||||
continue
|
continue
|
||||||
log.debug('Bible Name: "{name}"'.format(name=name))
|
log.debug('Bible Name: "{name}"'.format(name=name))
|
||||||
self.db_cache[name] = bible
|
self.db_cache[name] = bible
|
||||||
@ -185,7 +185,7 @@ class BibleManager(OpenLPMixin, RegistryProperties):
|
|||||||
bible = self.db_cache[name]
|
bible = self.db_cache[name]
|
||||||
bible.session.close_all()
|
bible.session.close_all()
|
||||||
bible.session = None
|
bible.session = None
|
||||||
return delete_file(os.path.join(bible.path, bible.file))
|
return delete_file(Path(bible.path, bible.file))
|
||||||
|
|
||||||
def get_bibles(self):
|
def get_bibles(self):
|
||||||
"""
|
"""
|
||||||
@ -305,13 +305,10 @@ class BibleManager(OpenLPMixin, RegistryProperties):
|
|||||||
"""
|
"""
|
||||||
Does a verse search for the given bible and text.
|
Does a verse search for the given bible and text.
|
||||||
|
|
||||||
:param bible: The bible to search
|
:param str bible: The bible to search
|
||||||
:type bible: str
|
:param str text: The text to search for
|
||||||
:param text: The text to search for
|
|
||||||
:type text: str
|
|
||||||
|
|
||||||
:return: The search results if valid, or None if the search is invalid.
|
:return: The search results if valid, or None if the search is invalid.
|
||||||
:rtype: None, list
|
:rtype: None | list
|
||||||
"""
|
"""
|
||||||
log.debug('BibleManager.verse_search("{bible}", "{text}")'.format(bible=bible, text=text))
|
log.debug('BibleManager.verse_search("{bible}", "{text}")'.format(bible=bible, text=text))
|
||||||
if not text:
|
if not text:
|
||||||
|
@ -414,7 +414,9 @@ class BibleMediaItem(MediaManagerItem):
|
|||||||
if self.bible:
|
if self.bible:
|
||||||
book_data = self.get_common_books(self.bible, self.second_bible)
|
book_data = self.get_common_books(self.bible, self.second_bible)
|
||||||
language_selection = self.plugin.manager.get_language_selection(self.bible.name)
|
language_selection = self.plugin.manager.get_language_selection(self.bible.name)
|
||||||
books = [book.get_name(language_selection) for book in book_data]
|
# Get book names + add a space to the end. Thus Psalm23 becomes Psalm 23
|
||||||
|
# when auto complete is used and user does not need to add the space manually.
|
||||||
|
books = [book.get_name(language_selection) + ' ' for book in book_data]
|
||||||
books.sort(key=get_locale_key)
|
books.sort(key=get_locale_key)
|
||||||
set_case_insensitive_completer(books, self.search_edit)
|
set_case_insensitive_completer(books, self.search_edit)
|
||||||
|
|
||||||
@ -463,8 +465,7 @@ class BibleMediaItem(MediaManagerItem):
|
|||||||
"""
|
"""
|
||||||
Show the selected tab and set focus to it
|
Show the selected tab and set focus to it
|
||||||
|
|
||||||
:param index: The tab selected
|
:param int index: The tab selected
|
||||||
:type index: int
|
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
if index == SearchTabs.Search or index == SearchTabs.Select:
|
if index == SearchTabs.Search or index == SearchTabs.Select:
|
||||||
@ -481,7 +482,7 @@ class BibleMediaItem(MediaManagerItem):
|
|||||||
Update list_widget with the contents of the selected list
|
Update list_widget with the contents of the selected list
|
||||||
|
|
||||||
:param index: The index of the tab that has been changed to. (int)
|
:param index: The index of the tab that has been changed to. (int)
|
||||||
:return: None
|
:rtype: None
|
||||||
"""
|
"""
|
||||||
if index == ResultsTab.Saved:
|
if index == ResultsTab.Saved:
|
||||||
self.add_built_results_to_list_widget(self.saved_results)
|
self.add_built_results_to_list_widget(self.saved_results)
|
||||||
|
@ -26,8 +26,10 @@ for the Custom Slides plugin.
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from openlp.core.api.http import register_endpoint
|
||||||
from openlp.core.lib import Plugin, StringContent, build_icon, translate
|
from openlp.core.lib import Plugin, StringContent, build_icon, translate
|
||||||
from openlp.core.lib.db import Manager
|
from openlp.core.lib.db import Manager
|
||||||
|
from openlp.plugins.custom.endpoint import api_custom_endpoint, custom_endpoint
|
||||||
from openlp.plugins.custom.lib import CustomMediaItem, CustomTab
|
from openlp.plugins.custom.lib import CustomMediaItem, CustomTab
|
||||||
from openlp.plugins.custom.lib.db import CustomSlide, init_schema
|
from openlp.plugins.custom.lib.db import CustomSlide, init_schema
|
||||||
from openlp.plugins.custom.lib.mediaitem import CustomSearch
|
from openlp.plugins.custom.lib.mediaitem import CustomSearch
|
||||||
@ -40,7 +42,7 @@ __default_settings__ = {
|
|||||||
'custom/db password': '',
|
'custom/db password': '',
|
||||||
'custom/db hostname': '',
|
'custom/db hostname': '',
|
||||||
'custom/db database': '',
|
'custom/db database': '',
|
||||||
'custom/last search type': CustomSearch.Titles,
|
'custom/last used search type': CustomSearch.Titles,
|
||||||
'custom/display footer': True,
|
'custom/display footer': True,
|
||||||
'custom/add custom from service': True
|
'custom/add custom from service': True
|
||||||
}
|
}
|
||||||
@ -61,6 +63,8 @@ class CustomPlugin(Plugin):
|
|||||||
self.db_manager = Manager('custom', init_schema)
|
self.db_manager = Manager('custom', init_schema)
|
||||||
self.icon_path = ':/plugins/plugin_custom.png'
|
self.icon_path = ':/plugins/plugin_custom.png'
|
||||||
self.icon = build_icon(self.icon_path)
|
self.icon = build_icon(self.icon_path)
|
||||||
|
register_endpoint(custom_endpoint)
|
||||||
|
register_endpoint(api_custom_endpoint)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def about():
|
def about():
|
||||||
|
100
openlp/plugins/custom/endpoint.py
Normal file
100
openlp/plugins/custom/endpoint.py
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# OpenLP - Open Source Lyrics Projection #
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# Copyright (c) 2008-2017 OpenLP Developers #
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# This program is free software; you can redistribute it and/or modify it #
|
||||||
|
# under the terms of the GNU General Public License as published by the Free #
|
||||||
|
# Software Foundation; version 2 of the License. #
|
||||||
|
# #
|
||||||
|
# This program is distributed in the hope that it will be useful, but WITHOUT #
|
||||||
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
|
||||||
|
# more details. #
|
||||||
|
# #
|
||||||
|
# You should have received a copy of the GNU General Public License along #
|
||||||
|
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||||
|
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||||
|
###############################################################################
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from openlp.core.api.http.endpoint import Endpoint
|
||||||
|
from openlp.core.api.http.errors import NotFound
|
||||||
|
from openlp.core.api.endpoint.pluginhelpers import search, live, service
|
||||||
|
from openlp.core.api.http import requires_auth
|
||||||
|
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
custom_endpoint = Endpoint('custom')
|
||||||
|
api_custom_endpoint = Endpoint('api')
|
||||||
|
|
||||||
|
|
||||||
|
@custom_endpoint.route('search')
|
||||||
|
def custom_search(request):
|
||||||
|
"""
|
||||||
|
Handles requests for searching the custom plugin
|
||||||
|
|
||||||
|
:param request: The http request object.
|
||||||
|
"""
|
||||||
|
return search(request, 'custom', log)
|
||||||
|
|
||||||
|
|
||||||
|
@custom_endpoint.route('live')
|
||||||
|
@requires_auth
|
||||||
|
def custom_live(request):
|
||||||
|
"""
|
||||||
|
Handles requests for making a song live
|
||||||
|
|
||||||
|
:param request: The http request object.
|
||||||
|
"""
|
||||||
|
return live(request, 'custom', log)
|
||||||
|
|
||||||
|
|
||||||
|
@custom_endpoint.route('add')
|
||||||
|
@requires_auth
|
||||||
|
def custom_service(request):
|
||||||
|
"""
|
||||||
|
Handles requests for adding a song to the service
|
||||||
|
|
||||||
|
:param request: The http request object.
|
||||||
|
"""
|
||||||
|
service(request, 'custom', log)
|
||||||
|
|
||||||
|
|
||||||
|
@api_custom_endpoint.route('custom/search')
|
||||||
|
def custom_search_api(request):
|
||||||
|
"""
|
||||||
|
Handles requests for searching the custom plugin
|
||||||
|
|
||||||
|
:param request: The http request object.
|
||||||
|
"""
|
||||||
|
return search(request, 'custom', log)
|
||||||
|
|
||||||
|
|
||||||
|
@api_custom_endpoint.route('custom/live')
|
||||||
|
@requires_auth
|
||||||
|
def custom_live_api(request):
|
||||||
|
"""
|
||||||
|
Handles requests for making a song live
|
||||||
|
|
||||||
|
:param request: The http request object.
|
||||||
|
"""
|
||||||
|
return live(request, 'custom', log)
|
||||||
|
|
||||||
|
|
||||||
|
@api_custom_endpoint.route('custom/add')
|
||||||
|
@requires_auth
|
||||||
|
def custom_service_api(request):
|
||||||
|
"""
|
||||||
|
Handles requests for adding a song to the service
|
||||||
|
|
||||||
|
:param request: The http request object.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
search(request, 'custom', log)
|
||||||
|
except NotFound:
|
||||||
|
return {'results': {'items': []}}
|
@ -44,7 +44,8 @@ class EditCustomForm(QtWidgets.QDialog, Ui_CustomEditDialog):
|
|||||||
"""
|
"""
|
||||||
Constructor
|
Constructor
|
||||||
"""
|
"""
|
||||||
super(EditCustomForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint)
|
super(EditCustomForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint |
|
||||||
|
QtCore.Qt.WindowCloseButtonHint)
|
||||||
self.manager = manager
|
self.manager = manager
|
||||||
self.media_item = media_item
|
self.media_item = media_item
|
||||||
self.setupUi(self)
|
self.setupUi(self)
|
||||||
|
@ -39,7 +39,8 @@ class EditCustomSlideForm(QtWidgets.QDialog, Ui_CustomSlideEditDialog):
|
|||||||
"""
|
"""
|
||||||
Constructor
|
Constructor
|
||||||
"""
|
"""
|
||||||
super(EditCustomSlideForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint)
|
super(EditCustomSlideForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint |
|
||||||
|
QtCore.Qt.WindowCloseButtonHint)
|
||||||
self.setupUi(self)
|
self.setupUi(self)
|
||||||
# Connecting signals and slots
|
# Connecting signals and slots
|
||||||
self.insert_button.clicked.connect(self.on_insert_button_clicked)
|
self.insert_button.clicked.connect(self.on_insert_button_clicked)
|
||||||
|
@ -190,9 +190,7 @@ class CustomMediaItem(MediaManagerItem):
|
|||||||
translate('CustomPlugin.MediaItem',
|
translate('CustomPlugin.MediaItem',
|
||||||
'Are you sure you want to delete the "{items:d}" '
|
'Are you sure you want to delete the "{items:d}" '
|
||||||
'selected custom slide(s)?').format(items=len(items)),
|
'selected custom slide(s)?').format(items=len(items)),
|
||||||
QtWidgets.QMessageBox.StandardButtons(
|
defaultButton=QtWidgets.QMessageBox.Yes) == QtWidgets.QMessageBox.No:
|
||||||
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No),
|
|
||||||
QtWidgets.QMessageBox.Yes) == QtWidgets.QMessageBox.No:
|
|
||||||
return
|
return
|
||||||
row_list = [item.row() for item in self.list_view.selectedIndexes()]
|
row_list = [item.row() for item in self.list_view.selectedIndexes()]
|
||||||
row_list.sort(reverse=True)
|
row_list.sort(reverse=True)
|
||||||
|
113
openlp/plugins/images/endpoint.py
Normal file
113
openlp/plugins/images/endpoint.py
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# OpenLP - Open Source Lyrics Projection #
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# Copyright (c) 2008-2017 OpenLP Developers #
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# This program is free software; you can redistribute it and/or modify it #
|
||||||
|
# under the terms of the GNU General Public License as published by the Free #
|
||||||
|
# Software Foundation; version 2 of the License. #
|
||||||
|
# #
|
||||||
|
# This program is distributed in the hope that it will be useful, but WITHOUT #
|
||||||
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
|
||||||
|
# more details. #
|
||||||
|
# #
|
||||||
|
# You should have received a copy of the GNU General Public License along #
|
||||||
|
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||||
|
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||||
|
###############################################################################
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from openlp.core.api.http.endpoint import Endpoint
|
||||||
|
from openlp.core.api.http.errors import NotFound
|
||||||
|
from openlp.core.api.endpoint.pluginhelpers import search, live, service, display_thumbnails
|
||||||
|
from openlp.core.api.http import requires_auth
|
||||||
|
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
images_endpoint = Endpoint('images')
|
||||||
|
api_images_endpoint = Endpoint('api')
|
||||||
|
|
||||||
|
|
||||||
|
# images/thumbnails/320x240/1.jpg
|
||||||
|
@images_endpoint.route('thumbnails/{dimensions}/{file_name}')
|
||||||
|
def images_thumbnails(request, dimensions, file_name):
|
||||||
|
"""
|
||||||
|
Return an image to a web page based on a URL
|
||||||
|
:param request: Request object
|
||||||
|
:param dimensions: the image size eg 88x88
|
||||||
|
:param file_name: the individual image name
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
return display_thumbnails(request, 'images', log, dimensions, file_name)
|
||||||
|
|
||||||
|
|
||||||
|
@images_endpoint.route('search')
|
||||||
|
def images_search(request):
|
||||||
|
"""
|
||||||
|
Handles requests for searching the images plugin
|
||||||
|
|
||||||
|
:param request: The http request object.
|
||||||
|
"""
|
||||||
|
return search(request, 'images', log)
|
||||||
|
|
||||||
|
|
||||||
|
@images_endpoint.route('live')
|
||||||
|
@requires_auth
|
||||||
|
def images_live(request):
|
||||||
|
"""
|
||||||
|
Handles requests for making a song live
|
||||||
|
|
||||||
|
:param request: The http request object.
|
||||||
|
"""
|
||||||
|
return live(request, 'images', log)
|
||||||
|
|
||||||
|
|
||||||
|
@images_endpoint.route('add')
|
||||||
|
@requires_auth
|
||||||
|
def images_service(request):
|
||||||
|
"""
|
||||||
|
Handles requests for adding a song to the service
|
||||||
|
|
||||||
|
:param request: The http request object.
|
||||||
|
"""
|
||||||
|
service(request, 'images', log)
|
||||||
|
|
||||||
|
|
||||||
|
@api_images_endpoint.route('images/search')
|
||||||
|
def images_search_api(request):
|
||||||
|
"""
|
||||||
|
Handles requests for searching the images plugin
|
||||||
|
|
||||||
|
:param request: The http request object.
|
||||||
|
"""
|
||||||
|
return search(request, 'images', log)
|
||||||
|
|
||||||
|
|
||||||
|
@api_images_endpoint.route('images/live')
|
||||||
|
@requires_auth
|
||||||
|
def images_live_api(request):
|
||||||
|
"""
|
||||||
|
Handles requests for making a song live
|
||||||
|
|
||||||
|
:param request: The http request object.
|
||||||
|
"""
|
||||||
|
return live(request, 'images', log)
|
||||||
|
|
||||||
|
|
||||||
|
@api_images_endpoint.route('images/add')
|
||||||
|
@requires_auth
|
||||||
|
def images_service_api(request):
|
||||||
|
"""
|
||||||
|
Handles requests for adding a song to the service
|
||||||
|
|
||||||
|
:param request: The http request object.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
search(request, 'images', log)
|
||||||
|
except NotFound:
|
||||||
|
return {'results': {'items': []}}
|
@ -35,7 +35,8 @@ class AddGroupForm(QtWidgets.QDialog, Ui_AddGroupDialog):
|
|||||||
"""
|
"""
|
||||||
Constructor
|
Constructor
|
||||||
"""
|
"""
|
||||||
super(AddGroupForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint)
|
super(AddGroupForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint |
|
||||||
|
QtCore.Qt.WindowCloseButtonHint)
|
||||||
self.setupUi(self)
|
self.setupUi(self)
|
||||||
|
|
||||||
def exec(self, clear=True, show_top_level_group=False, selected_group=None):
|
def exec(self, clear=True, show_top_level_group=False, selected_group=None):
|
||||||
|
@ -33,7 +33,8 @@ class ChooseGroupForm(QtWidgets.QDialog, Ui_ChooseGroupDialog):
|
|||||||
"""
|
"""
|
||||||
Constructor
|
Constructor
|
||||||
"""
|
"""
|
||||||
super(ChooseGroupForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint)
|
super(ChooseGroupForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint |
|
||||||
|
QtCore.Qt.WindowCloseButtonHint)
|
||||||
self.setupUi(self)
|
self.setupUi(self)
|
||||||
|
|
||||||
def exec(self, selected_group=None):
|
def exec(self, selected_group=None):
|
||||||
|
@ -24,9 +24,11 @@ from PyQt5 import QtGui
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from openlp.core.api.http import register_endpoint
|
||||||
from openlp.core.common import Settings, translate
|
from openlp.core.common import Settings, translate
|
||||||
from openlp.core.lib import Plugin, StringContent, ImageSource, build_icon
|
from openlp.core.lib import Plugin, StringContent, ImageSource, build_icon
|
||||||
from openlp.core.lib.db import Manager
|
from openlp.core.lib.db import Manager
|
||||||
|
from openlp.plugins.images.endpoint import api_images_endpoint, images_endpoint
|
||||||
from openlp.plugins.images.lib import ImageMediaItem, ImageTab
|
from openlp.plugins.images.lib import ImageMediaItem, ImageTab
|
||||||
from openlp.plugins.images.lib.db import init_schema
|
from openlp.plugins.images.lib.db import init_schema
|
||||||
|
|
||||||
@ -51,6 +53,8 @@ class ImagePlugin(Plugin):
|
|||||||
self.weight = -7
|
self.weight = -7
|
||||||
self.icon_path = ':/plugins/plugin_images.png'
|
self.icon_path = ':/plugins/plugin_images.png'
|
||||||
self.icon = build_icon(self.icon_path)
|
self.icon = build_icon(self.icon_path)
|
||||||
|
register_endpoint(images_endpoint)
|
||||||
|
register_endpoint(api_images_endpoint)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def about():
|
def about():
|
||||||
@ -67,14 +71,6 @@ class ImagePlugin(Plugin):
|
|||||||
'provided by the theme.')
|
'provided by the theme.')
|
||||||
return about_text
|
return about_text
|
||||||
|
|
||||||
def upgrade_settings(self, settings):
|
|
||||||
"""
|
|
||||||
Upgrade the settings of this plugin.
|
|
||||||
|
|
||||||
:param settings: The Settings object containing the old settings.
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def set_plugin_text_strings(self):
|
def set_plugin_text_strings(self):
|
||||||
"""
|
"""
|
||||||
Called to define all translatable texts of the plugin.
|
Called to define all translatable texts of the plugin.
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user