forked from openlp/openlp
HEAD
This commit is contained in:
commit
b521b9aeba
@ -1 +1 @@
|
||||
2.5.0
|
||||
2.9.0
|
||||
|
@ -91,6 +91,7 @@ def controller_text(request):
|
||||
item['text'] = str(frame['title'])
|
||||
item['html'] = str(frame['title'])
|
||||
item['selected'] = (live_controller.selected_row == index)
|
||||
item['title'] = current_item.title
|
||||
data.append(item)
|
||||
json_data = {'results': {'slides': data}}
|
||||
if current_item:
|
||||
@ -117,12 +118,11 @@ def controller_set(request):
|
||||
return {'results': {'success': True}}
|
||||
|
||||
|
||||
@controller_endpoint.route('{action:next|previous}')
|
||||
@controller_endpoint.route('{controller}/{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.
|
||||
@ -137,7 +137,7 @@ def controller_direction(request, controller, action):
|
||||
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.
|
||||
|
@ -38,7 +38,6 @@ from PyQt5 import QtCore, QtWidgets, QtWebEngineWidgets
|
||||
from openlp.core.common import is_macosx, is_win
|
||||
from openlp.core.common.applocation import AppLocation
|
||||
from openlp.core.common.i18n import LanguageManager, UiStrings, translate
|
||||
from openlp.core.common.mixins import LogMixin
|
||||
from openlp.core.common.path import create_paths, copytree
|
||||
from openlp.core.common.registry import Registry
|
||||
from openlp.core.common.settings import Settings
|
||||
@ -50,6 +49,7 @@ from openlp.core.ui.firsttimeform import FirstTimeForm
|
||||
from openlp.core.ui.firsttimelanguageform import FirstTimeLanguageForm
|
||||
from openlp.core.ui.mainwindow import MainWindow
|
||||
from openlp.core.ui.style import get_application_stylesheet
|
||||
from openlp.core.server import Server
|
||||
from openlp.core.version import check_for_update, get_version
|
||||
|
||||
__all__ = ['OpenLP', 'main']
|
||||
@ -58,7 +58,7 @@ __all__ = ['OpenLP', 'main']
|
||||
log = logging.getLogger()
|
||||
|
||||
|
||||
class OpenLP(QtWidgets.QApplication, LogMixin):
|
||||
class OpenLP(QtWidgets.QApplication):
|
||||
"""
|
||||
The core application class. This class inherits from Qt's QApplication
|
||||
class in order to provide the core of the application.
|
||||
@ -72,7 +72,7 @@ class OpenLP(QtWidgets.QApplication, LogMixin):
|
||||
"""
|
||||
self.is_event_loop_active = True
|
||||
result = QtWidgets.QApplication.exec()
|
||||
self.shared_memory.detach()
|
||||
self.server.close_server()
|
||||
return result
|
||||
|
||||
def run(self, args):
|
||||
@ -135,23 +135,16 @@ class OpenLP(QtWidgets.QApplication, LogMixin):
|
||||
self.main_window.app_startup()
|
||||
return self.exec()
|
||||
|
||||
def is_already_running(self):
|
||||
@staticmethod
|
||||
def is_already_running():
|
||||
"""
|
||||
Look to see if OpenLP is already running and ask if a 2nd instance is to be started.
|
||||
Tell the user there is a 2nd instance running.
|
||||
"""
|
||||
self.shared_memory = QtCore.QSharedMemory('OpenLP')
|
||||
if self.shared_memory.attach():
|
||||
status = QtWidgets.QMessageBox.critical(None, UiStrings().Error, UiStrings().OpenLPStart,
|
||||
QtWidgets.QMessageBox.StandardButtons(QtWidgets.QMessageBox.Yes |
|
||||
QtWidgets.QMessageBox.No))
|
||||
if status == QtWidgets.QMessageBox.No:
|
||||
return True
|
||||
return False
|
||||
else:
|
||||
self.shared_memory.create(1)
|
||||
return False
|
||||
QtWidgets.QMessageBox.critical(None, UiStrings().Error, UiStrings().OpenLPStart,
|
||||
QtWidgets.QMessageBox.StandardButtons(QtWidgets.QMessageBox.Ok))
|
||||
|
||||
def is_data_path_missing(self):
|
||||
@staticmethod
|
||||
def is_data_path_missing():
|
||||
"""
|
||||
Check if the data folder path exists.
|
||||
"""
|
||||
@ -301,10 +294,7 @@ def parse_options(args=None):
|
||||
parser.add_argument('-l', '--log-level', dest='loglevel', default='warning', metavar='LEVEL',
|
||||
help='Set logging to LEVEL level. Valid values are "debug", "info", "warning".')
|
||||
parser.add_argument('-p', '--portable', dest='portable', action='store_true',
|
||||
help='Specify if this should be run as a portable app, '
|
||||
'off a USB flash drive (not implemented).')
|
||||
parser.add_argument('-d', '--dev-version', dest='dev_version', action='store_true',
|
||||
help='Ignore the version file and pull the version directly from Bazaar')
|
||||
help='Specify if this should be run as a portable app, ')
|
||||
parser.add_argument('-w', '--no-web-server', dest='no_web_server', action='store_true',
|
||||
help='Turn off the Web and Socket Server ')
|
||||
parser.add_argument('rargs', nargs='?', default=[])
|
||||
@ -384,11 +374,17 @@ def main(args=None):
|
||||
Registry().set_flag('no_web_server', args.no_web_server)
|
||||
application.setApplicationVersion(get_version()['version'])
|
||||
# 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():
|
||||
server = Server()
|
||||
if server.is_another_instance_running():
|
||||
application.is_already_running()
|
||||
server.post_to_server(qt_args)
|
||||
sys.exit()
|
||||
else:
|
||||
server.start_server()
|
||||
application.server = server
|
||||
# If the custom data path is missing and the user wants to restore the data path, quit OpenLP.
|
||||
if application.is_data_path_missing():
|
||||
application.shared_memory.detach()
|
||||
server.close_server()
|
||||
sys.exit()
|
||||
# Upgrade settings.
|
||||
settings = Settings()
|
||||
|
@ -62,7 +62,7 @@ def get_local_ip4():
|
||||
"""
|
||||
# Get the local IPv4 active address(es) that are NOT localhost (lo or '127.0.0.1')
|
||||
log.debug('Getting local IPv4 interface(es) information')
|
||||
MY_IP4 = {}
|
||||
my_ip4 = {}
|
||||
for iface in QNetworkInterface.allInterfaces():
|
||||
if not iface.isValid() or not (iface.flags() & (QNetworkInterface.IsUp | QNetworkInterface.IsRunning)):
|
||||
continue
|
||||
@ -70,8 +70,8 @@ def get_local_ip4():
|
||||
ip = address.ip()
|
||||
# NOTE: Next line will skip if interface is localhost - keep for now until we decide about it later
|
||||
# if (ip.protocol() == QAbstractSocket.IPv4Protocol) and (ip != QHostAddress.LocalHost):
|
||||
if (ip.protocol() == QAbstractSocket.IPv4Protocol):
|
||||
MY_IP4[iface.name()] = {'ip': ip.toString(),
|
||||
if ip.protocol() == QAbstractSocket.IPv4Protocol:
|
||||
my_ip4[iface.name()] = {'ip': ip.toString(),
|
||||
'broadcast': address.broadcast().toString(),
|
||||
'netmask': address.netmask().toString(),
|
||||
'prefix': address.prefixLength(),
|
||||
@ -79,16 +79,16 @@ def get_local_ip4():
|
||||
ip.toIPv4Address()).toString()
|
||||
}
|
||||
log.debug('Adding {iface} to active list'.format(iface=iface.name()))
|
||||
if len(MY_IP4) == 1:
|
||||
if 'lo' in MY_IP4:
|
||||
if len(my_ip4) == 1:
|
||||
if 'lo' in my_ip4:
|
||||
# No active interfaces - so leave localhost in there
|
||||
log.warning('No active IPv4 interfaces found except localhost')
|
||||
else:
|
||||
# Since we have a valid IP4 interface, remove localhost
|
||||
log.debug('Found at least one IPv4 interface, removing localhost')
|
||||
MY_IP4.pop('lo')
|
||||
my_ip4.pop('lo')
|
||||
|
||||
return MY_IP4
|
||||
return my_ip4
|
||||
|
||||
|
||||
def trace_error_handler(logger):
|
||||
|
@ -415,7 +415,7 @@ class UiStrings(object):
|
||||
self.NoResults = translate('OpenLP.Ui', 'No Search Results')
|
||||
self.OpenLP = translate('OpenLP.Ui', 'OpenLP')
|
||||
self.OpenLPv2AndUp = translate('OpenLP.Ui', 'OpenLP 2.0 and up')
|
||||
self.OpenLPStart = translate('OpenLP.Ui', 'OpenLP is already running. Do you wish to continue?')
|
||||
self.OpenLPStart = translate('OpenLP.Ui', 'OpenLP is already running on this machine. \nClosing this instance')
|
||||
self.OpenService = translate('OpenLP.Ui', 'Open service.')
|
||||
self.OptionalShowInFooter = translate('OpenLP.Ui', 'Optional, this will be displayed in footer.')
|
||||
self.OptionalHideInFooter = translate('OpenLP.Ui', 'Optional, this won\'t be displayed in footer.')
|
||||
|
@ -121,6 +121,9 @@ class ItemCapabilities(object):
|
||||
|
||||
``HasThumbnails``
|
||||
The item has related thumbnails available
|
||||
|
||||
``HasMetaData``
|
||||
The item has Meta Data about item
|
||||
"""
|
||||
CanPreview = 1
|
||||
CanEdit = 2
|
||||
@ -143,6 +146,7 @@ class ItemCapabilities(object):
|
||||
HasDisplayTitle = 19
|
||||
HasNotes = 20
|
||||
HasThumbnails = 21
|
||||
HasMetaData = 22
|
||||
|
||||
|
||||
class ServiceItem(RegistryProperties):
|
||||
@ -201,6 +205,7 @@ class ServiceItem(RegistryProperties):
|
||||
self.will_auto_start = False
|
||||
self.has_original_files = True
|
||||
self._new_item()
|
||||
self.metadata = []
|
||||
|
||||
def _new_item(self):
|
||||
"""
|
||||
@ -376,7 +381,8 @@ class ServiceItem(RegistryProperties):
|
||||
'background_audio': self.background_audio,
|
||||
'theme_overwritten': self.theme_overwritten,
|
||||
'will_auto_start': self.will_auto_start,
|
||||
'processor': self.processor
|
||||
'processor': self.processor,
|
||||
'metadata': self.metadata
|
||||
}
|
||||
service_data = []
|
||||
if self.service_item_type == ServiceItemType.Text:
|
||||
@ -427,6 +433,7 @@ class ServiceItem(RegistryProperties):
|
||||
self.will_auto_start = header.get('will_auto_start', False)
|
||||
self.processor = header.get('processor', None)
|
||||
self.has_original_files = True
|
||||
self.metadata = header.get('item_meta_data', [])
|
||||
if 'background_audio' in header:
|
||||
self.background_audio = []
|
||||
for file_path in header['background_audio']:
|
||||
|
109
openlp/core/server.py
Normal file
109
openlp/core/server.py
Normal file
@ -0,0 +1,109 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||
|
||||
###############################################################################
|
||||
# OpenLP - Open Source Lyrics Projection #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Copyright (c) 2008-2018 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 PyQt5 import QtCore, QtNetwork
|
||||
|
||||
from openlp.core.common.registry import Registry
|
||||
from openlp.core.common.mixins import LogMixin
|
||||
|
||||
|
||||
class Server(QtCore.QObject, LogMixin):
|
||||
"""
|
||||
The local server to handle OpenLP running in more than one instance and allows file
|
||||
handles to be transferred from the new to the existing one.
|
||||
"""
|
||||
def __init__(self):
|
||||
super(Server, self).__init__()
|
||||
self.out_socket = QtNetwork.QLocalSocket()
|
||||
self.server = None
|
||||
self.id = 'OpenLPDual'
|
||||
|
||||
def is_another_instance_running(self):
|
||||
"""
|
||||
Check the see if an other instance is running
|
||||
:return: True of False
|
||||
"""
|
||||
# Is there another instance running?
|
||||
self.out_socket.connectToServer(self.id)
|
||||
return self.out_socket.waitForConnected()
|
||||
|
||||
def post_to_server(self, args):
|
||||
"""
|
||||
Post the file name to the over instance
|
||||
:param args: The passed arguments including maybe a file name
|
||||
"""
|
||||
if 'OpenLP' in args:
|
||||
args.remove('OpenLP')
|
||||
# Yes, there is.
|
||||
self.out_stream = QtCore.QTextStream(self.out_socket)
|
||||
self.out_stream.setCodec('UTF-8')
|
||||
self.out_socket.write(str.encode("".join(args)))
|
||||
if not self.out_socket.waitForBytesWritten(10):
|
||||
raise Exception(str(self.out_socket.errorString()))
|
||||
self.out_socket.disconnectFromServer()
|
||||
|
||||
def start_server(self):
|
||||
"""
|
||||
Start the socket server to allow inter app communication
|
||||
:return:
|
||||
"""
|
||||
self.out_socket = None
|
||||
self.out_stream = None
|
||||
self.in_socket = None
|
||||
self.in_stream = None
|
||||
self.server = QtNetwork.QLocalServer()
|
||||
self.server.listen(self.id)
|
||||
self.server.newConnection.connect(self._on_new_connection)
|
||||
return True
|
||||
|
||||
def _on_new_connection(self):
|
||||
"""
|
||||
Handle a new connection to the server
|
||||
:return:
|
||||
"""
|
||||
if self.in_socket:
|
||||
self.in_socket.readyRead.disconnect(self._on_ready_read)
|
||||
self.in_socket = self.server.nextPendingConnection()
|
||||
if not self.in_socket:
|
||||
return
|
||||
self.in_stream = QtCore.QTextStream(self.in_socket)
|
||||
self.in_stream.setCodec('UTF-8')
|
||||
self.in_socket.readyRead.connect(self._on_ready_read)
|
||||
|
||||
def _on_ready_read(self):
|
||||
"""
|
||||
Read a record passed to the server and pass to the service manager to handle
|
||||
:return:
|
||||
"""
|
||||
msg = self.in_stream.readLine()
|
||||
if msg:
|
||||
self.log_debug("socket msg = " + msg)
|
||||
Registry().get('service_manager').on_load_service_clicked(msg)
|
||||
|
||||
def close_server(self):
|
||||
"""
|
||||
Shutdown to local socket server and make sure the server is removed.
|
||||
:return:
|
||||
"""
|
||||
if self.server:
|
||||
self.server.close()
|
||||
# Make sure the server file is removed.
|
||||
QtNetwork.QLocalServer.removeServer(self.id)
|
@ -127,9 +127,6 @@ class UiFirstTimeWizard(object):
|
||||
self.media_check_box.setChecked(True)
|
||||
self.media_check_box.setObjectName('media_check_box')
|
||||
self.plugin_layout.addWidget(self.media_check_box)
|
||||
self.remote_check_box = QtWidgets.QCheckBox(self.plugin_page)
|
||||
self.remote_check_box.setObjectName('remote_check_box')
|
||||
self.plugin_layout.addWidget(self.remote_check_box)
|
||||
self.song_usage_check_box = QtWidgets.QCheckBox(self.plugin_page)
|
||||
self.song_usage_check_box.setChecked(True)
|
||||
self.song_usage_check_box.setObjectName('song_usage_check_box')
|
||||
@ -138,13 +135,6 @@ class UiFirstTimeWizard(object):
|
||||
self.alert_check_box.setChecked(True)
|
||||
self.alert_check_box.setObjectName('alert_check_box')
|
||||
self.plugin_layout.addWidget(self.alert_check_box)
|
||||
self.projectors_check_box = QtWidgets.QCheckBox(self.plugin_page)
|
||||
# If visibility setting for projector panel is True, check the box.
|
||||
if Settings().value('projector/show after wizard'):
|
||||
self.projectors_check_box.setChecked(True)
|
||||
self.projectors_check_box.setObjectName('projectors_check_box')
|
||||
self.projectors_check_box.clicked.connect(self.on_projectors_check_box_clicked)
|
||||
self.plugin_layout.addWidget(self.projectors_check_box)
|
||||
first_time_wizard.setPage(FirstTimePage.Plugins, self.plugin_page)
|
||||
# The song samples page
|
||||
self.songs_page = QtWidgets.QWizardPage()
|
||||
@ -256,13 +246,9 @@ class UiFirstTimeWizard(object):
|
||||
self.presentation_check_box.setText(translate('OpenLP.FirstTimeWizard',
|
||||
'Presentations – Show .ppt, .odp and .pdf files'))
|
||||
self.media_check_box.setText(translate('OpenLP.FirstTimeWizard', 'Media – Playback of Audio and Video files'))
|
||||
self.remote_check_box.setText(str(UiStrings().WebDownloadText))
|
||||
self.song_usage_check_box.setText(translate('OpenLP.FirstTimeWizard', 'Song Usage Monitor'))
|
||||
self.alert_check_box.setText(translate('OpenLP.FirstTimeWizard',
|
||||
'Alerts – Display informative messages while showing other slides'))
|
||||
self.projectors_check_box.setText(translate('OpenLP.FirstTimeWizard',
|
||||
'Projector Controller – Control PJLink compatible projects on your'
|
||||
' network from OpenLP'))
|
||||
self.no_internet_page.setTitle(translate('OpenLP.FirstTimeWizard', 'No Internet Connection'))
|
||||
self.no_internet_page.setSubTitle(
|
||||
translate('OpenLP.FirstTimeWizard', 'Unable to detect an Internet connection.'))
|
||||
|
@ -179,7 +179,6 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
|
||||
"""
|
||||
Check to see if we have any media Player's available.
|
||||
"""
|
||||
log.debug('_check_available_media_players')
|
||||
controller_dir = os.path.join('core', 'ui', 'media')
|
||||
# Find all files that do not begin with '.' (lp:#1738047) and end with player.py
|
||||
glob_pattern = os.path.join(controller_dir, '[!.]*player.py')
|
||||
@ -460,26 +459,16 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
|
||||
log.debug('use %s controller' % self.current_media_players[controller.controller_type].display_name)
|
||||
return True
|
||||
|
||||
def media_length(self, service_item):
|
||||
@staticmethod
|
||||
def media_length(service_item):
|
||||
"""
|
||||
Loads and starts a media item to obtain the media length
|
||||
Uses Media Info to obtain the media length
|
||||
|
||||
:param service_item: The ServiceItem containing the details to be played.
|
||||
"""
|
||||
media_info = MediaInfo()
|
||||
media_info.volume = 0
|
||||
media_info.file_info = QtCore.QFileInfo(service_item.get_frame_path())
|
||||
# display = controller.preview_display
|
||||
suffix = '*.%s' % media_info.file_info.suffix().lower()
|
||||
used_players = get_media_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:
|
||||
# Media could not be loaded correctly
|
||||
critical_error_message_box(
|
||||
translate('MediaPlugin.MediaItem', 'Unsupported Media File'),
|
||||
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
|
||||
media_data = MediaInfoWrapper.parse(service_item.get_frame_path())
|
||||
# duration returns in milli seconds
|
||||
service_item.set_media_length(media_data.tracks[0].duration)
|
||||
@ -574,16 +563,6 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
|
||||
if not title:
|
||||
continue
|
||||
player = self.media_players[title]
|
||||
# The system player may not return what files it can play so add it now
|
||||
# and check whether it can play the file later
|
||||
if title == 'system':
|
||||
if not controller.media_info.is_background or controller.media_info.is_background and \
|
||||
player.can_background:
|
||||
self.resize(display, player)
|
||||
if player.load(display):
|
||||
self.current_media_players[controller.controller_type] = player
|
||||
controller.media_info.media_type = MediaType.Video
|
||||
return True
|
||||
if suffix in player.video_extensions_list:
|
||||
if not controller.media_info.is_background or controller.media_info.is_background and \
|
||||
player.can_background:
|
||||
|
@ -66,7 +66,15 @@ def get_vlc():
|
||||
"""
|
||||
if 'openlp.core.ui.media.vendor.vlc' in sys.modules:
|
||||
# If VLC has already been imported, no need to do all the stuff below again
|
||||
is_vlc_available = False
|
||||
try:
|
||||
is_vlc_available = bool(sys.modules['openlp.core.ui.media.vendor.vlc'].get_default_instance())
|
||||
except:
|
||||
pass
|
||||
if is_vlc_available:
|
||||
return sys.modules['openlp.core.ui.media.vendor.vlc']
|
||||
else:
|
||||
return None
|
||||
is_vlc_available = False
|
||||
try:
|
||||
if is_macosx():
|
||||
@ -89,6 +97,7 @@ def get_vlc():
|
||||
except (ImportError, NameError, NotImplementedError):
|
||||
pass
|
||||
except OSError as e:
|
||||
# this will get raised the first time
|
||||
if is_win():
|
||||
if not isinstance(e, WindowsError) and e.winerror != 126:
|
||||
raise
|
||||
|
@ -753,7 +753,7 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi
|
||||
|
||||
def context_menu(self, point):
|
||||
"""
|
||||
The Right click context menu from the Serviceitem list
|
||||
The Right click context menu from the Service item list
|
||||
|
||||
:param point: The location of the cursor.
|
||||
"""
|
||||
@ -1138,7 +1138,6 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi
|
||||
def on_delete_from_service(self):
|
||||
"""
|
||||
Remove the current ServiceItem from the list.
|
||||
:param field:
|
||||
"""
|
||||
item = self.find_service_item()[0]
|
||||
if item != -1:
|
||||
@ -1207,6 +1206,9 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi
|
||||
tips.append('<strong>{text1}: </strong> {text2}'.format(text1=text1, text2=text2))
|
||||
if item['service_item'].is_capable(ItemCapabilities.HasVariableStartTime):
|
||||
tips.append(item['service_item'].get_media_time())
|
||||
if item['service_item'].is_capable(ItemCapabilities.HasMetaData):
|
||||
for meta in item['service_item'].metadata:
|
||||
tips.append(meta)
|
||||
tree_widget_item.setToolTip(0, '<br>'.join(tips))
|
||||
tree_widget_item.setData(0, QtCore.Qt.UserRole, item['order'])
|
||||
tree_widget_item.setSelected(item['selected'])
|
||||
@ -1367,7 +1369,6 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi
|
||||
def make_preview(self):
|
||||
"""
|
||||
Send the current item to the Preview slide controller
|
||||
:param field:
|
||||
"""
|
||||
self.application.set_busy_cursor()
|
||||
item, child = self.find_service_item()
|
||||
@ -1392,7 +1393,6 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi
|
||||
def on_double_click_live(self):
|
||||
"""
|
||||
Send the current item to the Live slide controller but triggered by a tablewidget click event.
|
||||
:param field:
|
||||
"""
|
||||
self.list_double_clicked = True
|
||||
self.make_live()
|
||||
@ -1401,7 +1401,6 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi
|
||||
"""
|
||||
If single click previewing is enabled, and triggered by a tablewidget click event,
|
||||
start a timeout to verify a double-click hasn't triggered.
|
||||
:param field:
|
||||
"""
|
||||
if Settings().value('advanced/single click service preview'):
|
||||
if not self.list_double_clicked:
|
||||
@ -1412,7 +1411,6 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi
|
||||
def on_single_click_preview_timeout(self):
|
||||
"""
|
||||
If a single click ok, but double click not triggered, send the current item to the Preview slide controller.
|
||||
:param field:
|
||||
"""
|
||||
if self.list_double_clicked:
|
||||
# If a double click has registered, clear it.
|
||||
@ -1452,7 +1450,6 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi
|
||||
def remote_edit(self):
|
||||
"""
|
||||
Triggers a remote edit to a plugin to allow item to be edited.
|
||||
:param field:
|
||||
"""
|
||||
item = self.find_service_item()[0]
|
||||
if self.service_items[item]['service_item'].is_capable(ItemCapabilities.CanEdit):
|
||||
@ -1464,8 +1461,6 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi
|
||||
def on_service_item_rename(self):
|
||||
"""
|
||||
Opens a dialog to rename the service item.
|
||||
|
||||
:param field: Not used, but PyQt needs this.
|
||||
"""
|
||||
item = self.find_service_item()[0]
|
||||
if not self.service_items[item]['service_item'].is_capable(ItemCapabilities.CanEditTitle):
|
||||
@ -1482,7 +1477,6 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi
|
||||
def create_custom(self):
|
||||
"""
|
||||
Saves the current text item as a custom slide
|
||||
:param field:
|
||||
"""
|
||||
item = self.find_service_item()[0]
|
||||
Registry().execute('custom_create_from_service', self.service_items[item]['service_item'])
|
||||
@ -1603,8 +1597,6 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi
|
||||
def on_theme_change_action(self):
|
||||
"""
|
||||
Handles theme change events
|
||||
|
||||
:param field:
|
||||
"""
|
||||
theme = self.sender().objectName()
|
||||
# No object name means that the "Default" theme is supposed to be used.
|
||||
|
@ -318,6 +318,10 @@ class SlideController(QtWidgets.QWidget, LogMixin, RegistryProperties):
|
||||
tooltip=translate('OpenLP.SlideController',
|
||||
'Edit and reload song preview.'),
|
||||
triggers=self.on_edit_song)
|
||||
self.toolbar.add_toolbar_action('clear', icon=':/general/general_delete.png',
|
||||
tooltip=translate('OpenLP.SlideController',
|
||||
'Clear'),
|
||||
triggers=self.on_clear)
|
||||
self.controller_layout.addWidget(self.toolbar)
|
||||
# Build the Media Toolbar
|
||||
self.media_controller.register_controller(self)
|
||||
@ -356,7 +360,7 @@ class SlideController(QtWidgets.QWidget, LogMixin, RegistryProperties):
|
||||
self.audio_time_label.setObjectName('audio_time_label')
|
||||
self.toolbar.add_toolbar_widget(self.audio_time_label)
|
||||
self.toolbar.set_widget_visible(AUDIO_LIST, False)
|
||||
self.toolbar.set_widget_visible(['song_menu'], False)
|
||||
self.toolbar.set_widget_visible('song_menu', False)
|
||||
# Screen preview area
|
||||
self.preview_frame = QtWidgets.QFrame(self.splitter)
|
||||
self.preview_frame.setGeometry(QtCore.QRect(0, 0, 300, 300 * self.ratio))
|
||||
@ -412,7 +416,8 @@ class SlideController(QtWidgets.QWidget, LogMixin, RegistryProperties):
|
||||
self.__add_actions_to_widget(self.controller)
|
||||
else:
|
||||
self.preview_widget.doubleClicked.connect(self.on_preview_double_click)
|
||||
self.toolbar.set_widget_visible(['editSong'], False)
|
||||
self.toolbar.set_widget_visible('editSong', False)
|
||||
self.toolbar.set_widget_visible('clear', False)
|
||||
self.controller.addActions([self.next_item, self.previous_item])
|
||||
Registry().register_function('slidecontroller_{text}_stop_loop'.format(text=self.type_prefix),
|
||||
self.on_stop_loop)
|
||||
@ -673,7 +678,7 @@ class SlideController(QtWidgets.QWidget, LogMixin, RegistryProperties):
|
||||
self.mediabar.hide()
|
||||
self.song_menu.hide()
|
||||
self.toolbar.set_widget_visible(LOOP_LIST, False)
|
||||
self.toolbar.set_widget_visible(['song_menu'], False)
|
||||
self.toolbar.set_widget_visible('song_menu', False)
|
||||
# Reset the button
|
||||
self.play_slides_once.setChecked(False)
|
||||
self.play_slides_once.setIcon(build_icon(':/media/media_time.png'))
|
||||
@ -684,7 +689,7 @@ class SlideController(QtWidgets.QWidget, LogMixin, RegistryProperties):
|
||||
if item.is_text():
|
||||
if (Settings().value(self.main_window.songs_settings_section + '/display songbar') and
|
||||
not self.song_menu.menu().isEmpty()):
|
||||
self.toolbar.set_widget_visible(['song_menu'], True)
|
||||
self.toolbar.set_widget_visible('song_menu', True)
|
||||
if item.is_capable(ItemCapabilities.CanLoop) and len(item.slides) > 1:
|
||||
self.toolbar.set_widget_visible(LOOP_LIST)
|
||||
if item.is_media():
|
||||
@ -709,9 +714,10 @@ class SlideController(QtWidgets.QWidget, LogMixin, RegistryProperties):
|
||||
# See bug #791050
|
||||
self.toolbar.hide()
|
||||
self.mediabar.hide()
|
||||
self.toolbar.set_widget_visible(['editSong'], False)
|
||||
self.toolbar.set_widget_visible('editSong', False)
|
||||
self.toolbar.set_widget_visible('clear', True)
|
||||
if item.is_capable(ItemCapabilities.CanEdit) and item.from_plugin:
|
||||
self.toolbar.set_widget_visible(['editSong'])
|
||||
self.toolbar.set_widget_visible('editSong')
|
||||
elif item.is_media():
|
||||
self.mediabar.show()
|
||||
self.previous_item.setVisible(not item.is_media())
|
||||
@ -1360,6 +1366,14 @@ class SlideController(QtWidgets.QWidget, LogMixin, RegistryProperties):
|
||||
if new_item:
|
||||
self.add_service_item(new_item)
|
||||
|
||||
def on_clear(self):
|
||||
"""
|
||||
Clear the preview bar.
|
||||
"""
|
||||
self.preview_widget.clear_list()
|
||||
self.toolbar.set_widget_visible('editSong', False)
|
||||
self.toolbar.set_widget_visible('clear', False)
|
||||
|
||||
def on_preview_add_to_service(self):
|
||||
"""
|
||||
From the preview display request the Item to be added to service
|
||||
|
@ -428,8 +428,8 @@ class ThemeManager(QtWidgets.QWidget, RegistryBase, Ui_ThemeManager, LogMixin, R
|
||||
self.log_exception('Export Theme Failed')
|
||||
critical_error_message_box(translate('OpenLP.ThemeManager', 'Theme Export Failed'),
|
||||
translate('OpenLP.ThemeManager',
|
||||
'The theme_name export failed because this error occurred: {err}')
|
||||
.format(err=ose.strerror))
|
||||
'The {theme_name} export failed because this error occurred: {err}')
|
||||
.format(theme_name=theme_name, err=ose.strerror))
|
||||
if theme_path.exists():
|
||||
theme_path.rmtree(ignore_errors=True)
|
||||
return False
|
||||
|
@ -150,42 +150,6 @@ def get_version():
|
||||
global APPLICATION_VERSION
|
||||
if APPLICATION_VERSION:
|
||||
return APPLICATION_VERSION
|
||||
if '--dev-version' in sys.argv or '-d' in sys.argv:
|
||||
# NOTE: The following code is a duplicate of the code in setup.py. Any fix applied here should also be applied
|
||||
# there.
|
||||
|
||||
# Get the revision of this tree.
|
||||
bzr = Popen(('bzr', 'revno'), stdout=PIPE)
|
||||
tree_revision, error = bzr.communicate()
|
||||
tree_revision = tree_revision.decode()
|
||||
code = bzr.wait()
|
||||
if code != 0:
|
||||
raise Exception('Error running bzr log')
|
||||
|
||||
# Get all tags.
|
||||
bzr = Popen(('bzr', 'tags'), stdout=PIPE)
|
||||
output, error = bzr.communicate()
|
||||
code = bzr.wait()
|
||||
if code != 0:
|
||||
raise Exception('Error running bzr tags')
|
||||
tags = list(map(bytes.decode, output.splitlines()))
|
||||
if not tags:
|
||||
tag_version = '0.0.0'
|
||||
tag_revision = '0'
|
||||
else:
|
||||
# Remove any tag that has "?" as revision number. A "?" as revision number indicates, that this tag is from
|
||||
# another series.
|
||||
tags = [tag for tag in tags if tag.split()[-1].strip() != '?']
|
||||
# Get the last tag and split it in a revision and tag name.
|
||||
tag_version, tag_revision = tags[-1].split()
|
||||
# If they are equal, then this tree is tarball with the source for the release. We do not want the revision
|
||||
# number in the full version.
|
||||
if tree_revision == tag_revision:
|
||||
full_version = tag_version.strip()
|
||||
else:
|
||||
full_version = '{tag}-bzr{tree}'.format(tag=tag_version.strip(), tree=tree_revision.strip())
|
||||
else:
|
||||
# We're not running the development version, let's use the file.
|
||||
file_path = AppLocation.get_directory(AppLocation.VersionDir) / '.version'
|
||||
try:
|
||||
full_version = file_path.read_text().rstrip()
|
||||
|
@ -67,14 +67,20 @@ class OpenLPToolbar(QtWidgets.QToolBar):
|
||||
"""
|
||||
Set the visibility for a widget or a list of widgets.
|
||||
|
||||
:param widgets: A list of string with widget object names.
|
||||
:param widgets: A list of strings or individual string with widget object names.
|
||||
:param visible: The new state as bool.
|
||||
"""
|
||||
if isinstance(widgets, list):
|
||||
for handle in widgets:
|
||||
if handle in self.actions:
|
||||
self.actions[handle].setVisible(visible)
|
||||
else:
|
||||
log.warning('No handle "%s" in actions list.', str(handle))
|
||||
else:
|
||||
if widgets in self.actions:
|
||||
self.actions[widgets].setVisible(visible)
|
||||
else:
|
||||
log.warning('No handle "%s" in actions list.', str(widgets))
|
||||
|
||||
def set_widget_enabled(self, widgets, enabled=True):
|
||||
"""
|
||||
|
@ -147,6 +147,14 @@ class ListPreviewWidget(QtWidgets.QTableWidget, RegistryProperties):
|
||||
self.screen_ratio = screen_ratio
|
||||
self.__recalculate_layout()
|
||||
|
||||
def clear_list(self):
|
||||
"""
|
||||
Clear the preview list
|
||||
:return:
|
||||
"""
|
||||
self.setRowCount(0)
|
||||
self.clear()
|
||||
|
||||
def replace_service_item(self, service_item, width, slide_number):
|
||||
"""
|
||||
Replace the current preview items with the ones in service_item and display the given slide
|
||||
@ -157,8 +165,7 @@ class ListPreviewWidget(QtWidgets.QTableWidget, RegistryProperties):
|
||||
"""
|
||||
self.service_item = service_item
|
||||
self.setRowCount(0)
|
||||
self.clear()
|
||||
self.setColumnWidth(0, width)
|
||||
self.clear_list()
|
||||
row = 0
|
||||
text = []
|
||||
for slide_index, slide in enumerate(self.service_item.slides):
|
||||
|
@ -30,6 +30,7 @@ from openlp.core.common.registry import Registry
|
||||
from openlp.core.common.settings import Settings
|
||||
from openlp.core.lib import MediaManagerItem, ItemCapabilities, ServiceItemContext, PluginStatus, \
|
||||
check_item_selected
|
||||
from openlp.core.lib.ui import create_widget_action
|
||||
from openlp.plugins.custom.forms.editcustomform import EditCustomForm
|
||||
from openlp.plugins.custom.lib import CustomXMLParser, CustomXMLBuilder
|
||||
from openlp.plugins.custom.lib.db import CustomSlide
|
||||
@ -84,6 +85,12 @@ class CustomMediaItem(MediaManagerItem):
|
||||
Registry().register_function('custom_preview', self.on_preview_click)
|
||||
Registry().register_function('custom_create_from_service', self.create_from_service_item)
|
||||
|
||||
def add_custom_context_actions(self):
|
||||
create_widget_action(self.list_view, separator=True)
|
||||
create_widget_action(
|
||||
self.list_view, text=translate('OpenLP.MediaManagerItem', '&Clone'), icon=':/general/general_clone.png',
|
||||
triggers=self.on_clone_click)
|
||||
|
||||
def config_update(self):
|
||||
"""
|
||||
Config has been updated so reload values
|
||||
@ -243,6 +250,23 @@ class CustomMediaItem(MediaManagerItem):
|
||||
service_item.raw_footer.append('')
|
||||
return True
|
||||
|
||||
def on_clone_click(self):
|
||||
"""
|
||||
Clone the selected Custom item
|
||||
"""
|
||||
item = self.list_view.currentItem()
|
||||
item_id = item.data(QtCore.Qt.UserRole)
|
||||
old_custom_slide = self.plugin.db_manager.get_object(CustomSlide, item_id)
|
||||
new_custom_slide = CustomSlide()
|
||||
new_custom_slide.title = '{title} <{text}>'.format(title=old_custom_slide.title,
|
||||
text=translate('CustomPlugin.MediaItem',
|
||||
'copy', 'For item cloning'))
|
||||
new_custom_slide.text = old_custom_slide.text
|
||||
new_custom_slide.credits = old_custom_slide.credits
|
||||
new_custom_slide.theme_name = old_custom_slide.theme_name
|
||||
self.plugin.db_manager.save_object(new_custom_slide)
|
||||
self.on_search_text_button_clicked()
|
||||
|
||||
def on_search_text_button_clicked(self):
|
||||
"""
|
||||
Search the plugin database
|
||||
|
@ -164,7 +164,7 @@ class Song(BaseModel):
|
||||
"""
|
||||
Add a Songbook Entry to the song if it not yet exists
|
||||
|
||||
:param songbook_name: Name of the Songbook.
|
||||
:param songbook: Name of the Songbook.
|
||||
:param entry: Entry in the Songbook (usually a number)
|
||||
"""
|
||||
for songbook_entry in self.songbook_entries:
|
||||
|
@ -572,10 +572,19 @@ class SongMediaItem(MediaManagerItem):
|
||||
service_item.add_capability(ItemCapabilities.OnLoadUpdate)
|
||||
service_item.add_capability(ItemCapabilities.AddIfNewItem)
|
||||
service_item.add_capability(ItemCapabilities.CanSoftBreak)
|
||||
service_item.add_capability(ItemCapabilities.HasMetaData)
|
||||
song = self.plugin.manager.get_object(Song, item_id)
|
||||
service_item.theme = song.theme_name
|
||||
service_item.edit_id = item_id
|
||||
verse_list = SongXML().get_verses(song.lyrics)
|
||||
if Settings().value('songs/add songbook slide') and song.songbook_entries:
|
||||
first_slide = '\n'
|
||||
for songbook_entry in song.songbook_entries:
|
||||
first_slide = first_slide + '{book}/{num}/{pub}\n\n'.format(book=songbook_entry.songbook.name,
|
||||
num=songbook_entry.entry,
|
||||
pub=songbook_entry.songbook.publisher)
|
||||
|
||||
service_item.add_from_text(first_slide, 'O1')
|
||||
# no verse list or only 1 space (in error)
|
||||
verse_tags_translated = False
|
||||
if VerseType.from_translated_string(str(verse_list[0][0]['type'])) is not None:
|
||||
@ -622,6 +631,9 @@ class SongMediaItem(MediaManagerItem):
|
||||
if song.media_files:
|
||||
service_item.add_capability(ItemCapabilities.HasBackgroundAudio)
|
||||
service_item.background_audio = [m.file_path for m in song.media_files]
|
||||
item.metadata.append('<em>{label}:</em> {media}'.
|
||||
format(label=translate('SongsPlugin.MediaItem', 'Media'),
|
||||
media=service_item.background_audio))
|
||||
return True
|
||||
|
||||
def generate_footer(self, item, song):
|
||||
@ -685,6 +697,23 @@ class SongMediaItem(MediaManagerItem):
|
||||
if Settings().value('core/ccli number'):
|
||||
item.raw_footer.append(translate('SongsPlugin.MediaItem',
|
||||
'CCLI License: ') + Settings().value('core/ccli number'))
|
||||
item.metadata.append('<em>{label}:</em> {title}'.format(label=translate('SongsPlugin.MediaItem', 'Title'),
|
||||
title=song.title))
|
||||
if song.alternate_title:
|
||||
item.metadata.append('<em>{label}:</em> {title}'.
|
||||
format(label=translate('SongsPlugin.MediaItem', 'Alt Title'),
|
||||
title=song.alternate_title))
|
||||
if song.songbook_entries:
|
||||
for songbook_entry in song.songbook_entries:
|
||||
item.metadata.append('<em>{label}:</em> {book}/{num}/{pub}'.
|
||||
format(label=translate('SongsPlugin.MediaItem', 'Songbook'),
|
||||
book=songbook_entry.songbook.name,
|
||||
num=songbook_entry.entry,
|
||||
pub=songbook_entry.songbook.publisher))
|
||||
if song.topics:
|
||||
for topics in song.topics:
|
||||
item.metadata.append('<em>{label}:</em> {topic}'.
|
||||
format(label=translate('SongsPlugin.MediaItem', 'Topic'), topic=topics.name))
|
||||
return authors_all
|
||||
|
||||
def service_load(self, item):
|
||||
|
@ -51,6 +51,9 @@ class SongsTab(SettingsTab):
|
||||
self.add_from_service_check_box = QtWidgets.QCheckBox(self.mode_group_box)
|
||||
self.add_from_service_check_box.setObjectName('add_from_service_check_box')
|
||||
self.mode_layout.addWidget(self.add_from_service_check_box)
|
||||
self.songbook_slide_check_box = QtWidgets.QCheckBox(self.mode_group_box)
|
||||
self.songbook_slide_check_box.setObjectName('songbook_slide_check_box')
|
||||
self.mode_layout.addWidget(self.songbook_slide_check_box)
|
||||
self.display_songbook_check_box = QtWidgets.QCheckBox(self.mode_group_box)
|
||||
self.display_songbook_check_box.setObjectName('songbook_check_box')
|
||||
self.mode_layout.addWidget(self.display_songbook_check_box)
|
||||
@ -95,6 +98,7 @@ class SongsTab(SettingsTab):
|
||||
self.tool_bar_active_check_box.stateChanged.connect(self.on_tool_bar_active_check_box_changed)
|
||||
self.update_on_edit_check_box.stateChanged.connect(self.on_update_on_edit_check_box_changed)
|
||||
self.add_from_service_check_box.stateChanged.connect(self.on_add_from_service_check_box_changed)
|
||||
self.songbook_slide_check_box.stateChanged.connect(self.on_songbook_slide_check_box_changed)
|
||||
self.display_songbook_check_box.stateChanged.connect(self.on_songbook_check_box_changed)
|
||||
self.display_written_by_check_box.stateChanged.connect(self.on_written_by_check_box_changed)
|
||||
self.display_copyright_check_box.stateChanged.connect(self.on_copyright_check_box_changed)
|
||||
@ -111,6 +115,8 @@ class SongsTab(SettingsTab):
|
||||
self.update_on_edit_check_box.setText(translate('SongsPlugin.SongsTab', 'Update service from song edit'))
|
||||
self.add_from_service_check_box.setText(translate('SongsPlugin.SongsTab',
|
||||
'Import missing songs from Service files'))
|
||||
self.songbook_slide_check_box.setText(translate('SongsPlugin.SongsTab',
|
||||
'Add Songbooks as first side'))
|
||||
self.display_songbook_check_box.setText(translate('SongsPlugin.SongsTab', 'Display songbook in footer'))
|
||||
self.display_written_by_check_box.setText(translate(
|
||||
'SongsPlugin.SongsTab', 'Show "Written by:" in footer for unspecified authors'))
|
||||
@ -141,6 +147,9 @@ class SongsTab(SettingsTab):
|
||||
def on_add_from_service_check_box_changed(self, check_state):
|
||||
self.update_load = (check_state == QtCore.Qt.Checked)
|
||||
|
||||
def on_songbook_slide_check_box_changed(self, check_state):
|
||||
self.songbook_slide = (check_state == QtCore.Qt.Checked)
|
||||
|
||||
def on_songbook_check_box_changed(self, check_state):
|
||||
self.display_songbook = (check_state == QtCore.Qt.Checked)
|
||||
|
||||
@ -171,6 +180,7 @@ class SongsTab(SettingsTab):
|
||||
self.tool_bar = settings.value('display songbar')
|
||||
self.update_edit = settings.value('update service on edit')
|
||||
self.update_load = settings.value('add song from service')
|
||||
self.songbook_slide = settings.value('add songbook slide')
|
||||
self.display_songbook = settings.value('display songbook')
|
||||
self.display_written_by = settings.value('display written by')
|
||||
self.display_copyright_symbol = settings.value('display copyright symbol')
|
||||
@ -208,6 +218,7 @@ class SongsTab(SettingsTab):
|
||||
settings.setValue('mainview chords', self.mainview_chords)
|
||||
settings.setValue('disable chords import', self.disable_chords_import)
|
||||
settings.setValue('chord notation', self.chord_notation)
|
||||
settings.setValue('add songbook slide', self.songbook_slide)
|
||||
settings.endGroup()
|
||||
if self.tab_visited:
|
||||
self.settings_form.register_post_process('songs_config_updated')
|
||||
|
@ -61,6 +61,7 @@ __default_settings__ = {
|
||||
'songs/last import type': SongFormat.OpenLyrics,
|
||||
'songs/update service on edit': False,
|
||||
'songs/add song from service': True,
|
||||
'songs/add songbook slide': False,
|
||||
'songs/display songbar': True,
|
||||
'songs/display songbook': False,
|
||||
'songs/display written by': True,
|
||||
|
@ -25,6 +25,7 @@ import asyncio
|
||||
import websockets
|
||||
import random
|
||||
|
||||
|
||||
async def tester():
|
||||
async with websockets.connect('ws://localhost:4317/poll') as websocket:
|
||||
|
||||
|
@ -20,10 +20,26 @@
|
||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||
###############################################################################
|
||||
from unittest import TestCase
|
||||
from unittest.mock import MagicMock, patch
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from PyQt5 import QtCore
|
||||
|
||||
from openlp.core.common.registry import Registry
|
||||
from openlp.core.api.endpoint.controller import controller_text
|
||||
from openlp.core.api.endpoint.controller import controller_text, controller_direction
|
||||
from openlp.core.display.renderer import Renderer
|
||||
from openlp.core.display.screens import ScreenList
|
||||
from openlp.core.lib import ServiceItem
|
||||
|
||||
from tests.utils import convert_file_service_item
|
||||
from tests.utils.constants import RESOURCE_PATH
|
||||
|
||||
TEST_PATH = str(RESOURCE_PATH / 'service')
|
||||
|
||||
SCREEN = {
|
||||
'primary': False,
|
||||
'number': 1,
|
||||
'size': QtCore.QRect(0, 0, 1024, 768)
|
||||
}
|
||||
|
||||
|
||||
class TestController(TestCase):
|
||||
@ -38,11 +54,18 @@ class TestController(TestCase):
|
||||
Registry.create()
|
||||
self.registry = Registry()
|
||||
self.mocked_live_controller = MagicMock()
|
||||
self.desktop = MagicMock()
|
||||
self.desktop.primaryScreen.return_value = SCREEN['primary']
|
||||
self.desktop.screenCount.return_value = SCREEN['number']
|
||||
self.desktop.screenGeometry.return_value = SCREEN['size']
|
||||
self.screens = ScreenList.create(self.desktop)
|
||||
renderer = Renderer()
|
||||
renderer.empty_height = 1000
|
||||
Registry().register('live_controller', self.mocked_live_controller)
|
||||
|
||||
def test_controller_text(self):
|
||||
def test_controller_text_empty(self):
|
||||
"""
|
||||
Remote Deploy tests - test the dummy zip file is processed correctly
|
||||
Remote API Tests : test the controller text method can be called with empty service item
|
||||
"""
|
||||
# GIVEN: A mocked service with a dummy service item
|
||||
self.mocked_live_controller.service_item = MagicMock()
|
||||
@ -52,3 +75,41 @@ class TestController(TestCase):
|
||||
results = ret['results']
|
||||
assert isinstance(results['item'], MagicMock)
|
||||
assert len(results['slides']) == 0
|
||||
|
||||
def test_controller_text(self):
|
||||
"""
|
||||
Remote API Tests : test the controller text method can be called with a real service item
|
||||
"""
|
||||
# GIVEN: A mocked service with a dummy service item
|
||||
line = convert_file_service_item(TEST_PATH, 'serviceitem_custom_1.osj')
|
||||
self.mocked_live_controller.service_item = ServiceItem(None)
|
||||
self.mocked_live_controller.service_item.set_from_service(line)
|
||||
self.mocked_live_controller.service_item.render(True)
|
||||
# WHEN: I trigger the method
|
||||
ret = controller_text("SomeText")
|
||||
# THEN: I get a basic set of results
|
||||
results = ret['results']
|
||||
assert isinstance(ret, dict)
|
||||
assert len(results['slides']) == 2
|
||||
|
||||
def test_controller_direction_next(self):
|
||||
"""
|
||||
Text the live next method is triggered
|
||||
"""
|
||||
# GIVEN: A mocked service with a dummy service item
|
||||
self.mocked_live_controller.service_item = MagicMock()
|
||||
# WHEN: I trigger the method
|
||||
controller_direction(None, 'live', 'next')
|
||||
# THEN: The correct method is called
|
||||
self.mocked_live_controller.slidecontroller_live_next.emit.assert_called_once_with()
|
||||
|
||||
def test_controller_direction_previous(self):
|
||||
"""
|
||||
Text the live next method is triggered
|
||||
"""
|
||||
# GIVEN: A mocked service with a dummy service item
|
||||
self.mocked_live_controller.service_item = MagicMock()
|
||||
# WHEN: I trigger the method
|
||||
controller_direction(None, 'live', 'previous')
|
||||
# THEN: The correct method is called
|
||||
self.mocked_live_controller.slidecontroller_live_previous.emit.assert_called_once_with()
|
||||
|
151
tests/functional/openlp_core/api/http/test_init.py
Normal file
151
tests/functional/openlp_core/api/http/test_init.py
Normal file
@ -0,0 +1,151 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||
|
||||
###############################################################################
|
||||
# OpenLP - Open Source Lyrics Projection #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Copyright (c) 2008-2018 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 #
|
||||
###############################################################################
|
||||
"""
|
||||
Functional tests to test the Http init.
|
||||
"""
|
||||
from unittest import TestCase
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from openlp.core.api.http import check_auth, requires_auth, authenticate
|
||||
from openlp.core.common.registry import Registry
|
||||
from openlp.core.common.settings import Settings
|
||||
|
||||
from tests.helpers.testmixin import TestMixin
|
||||
|
||||
|
||||
class TestInit(TestCase, TestMixin):
|
||||
"""
|
||||
A test suite to test the functions on the init
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
"""
|
||||
Create the UI
|
||||
"""
|
||||
Registry().create()
|
||||
Registry().register('service_list', MagicMock())
|
||||
self.build_settings()
|
||||
self.password = 'c3VwZXJmbHk6bGFtYXM='
|
||||
|
||||
def tearDown(self):
|
||||
self.destroy_settings()
|
||||
|
||||
def test_auth(self):
|
||||
"""
|
||||
Test the check_auth method with a match
|
||||
:return:
|
||||
"""
|
||||
# GIVEN: a known user
|
||||
Settings().setValue('api/user id', "superfly")
|
||||
Settings().setValue('api/password', "lamas")
|
||||
|
||||
# WHEN : I check the authorisation
|
||||
is_valid = check_auth(['aaaaa', self.password])
|
||||
|
||||
# THEN:
|
||||
assert is_valid is True
|
||||
|
||||
def test_auth_falure(self):
|
||||
"""
|
||||
Test the check_auth method with a match
|
||||
:return:
|
||||
"""
|
||||
# GIVEN: a known user
|
||||
Settings().setValue('api/user id', 'superfly')
|
||||
Settings().setValue('api/password', 'lamas')
|
||||
|
||||
# WHEN : I check the authorisation
|
||||
is_valid = check_auth(['aaaaa', 'monkey123'])
|
||||
|
||||
# THEN:
|
||||
assert is_valid is False
|
||||
|
||||
def test_requires_auth_disabled(self):
|
||||
"""
|
||||
Test the requires_auth wrapper with disabled security
|
||||
:return:
|
||||
"""
|
||||
# GIVEN: A disabled security
|
||||
Settings().setValue('api/authentication enabled', False)
|
||||
|
||||
# WHEN: I call the function
|
||||
wrapped_function = requires_auth(func)
|
||||
value = wrapped_function()
|
||||
|
||||
# THEN: the result will be as expected
|
||||
assert value == 'called'
|
||||
|
||||
def test_requires_auth_enabled(self):
|
||||
"""
|
||||
Test the requires_auth wrapper with enabled security
|
||||
:return:
|
||||
"""
|
||||
# GIVEN: A disabled security
|
||||
Settings().setValue('api/authentication enabled', True)
|
||||
|
||||
# WHEN: I call the function
|
||||
wrapped_function = requires_auth(func)
|
||||
req = MagicMock()
|
||||
value = wrapped_function(req)
|
||||
|
||||
# THEN: the result will be as expected
|
||||
assert str(value) == str(authenticate())
|
||||
|
||||
def test_requires_auth_enabled_auth_error(self):
|
||||
"""
|
||||
Test the requires_auth wrapper with enabled security and authorization taken place and and error
|
||||
:return:
|
||||
"""
|
||||
# GIVEN: A enabled security
|
||||
Settings().setValue('api/authentication enabled', True)
|
||||
|
||||
# WHEN: I call the function with the wrong password
|
||||
wrapped_function = requires_auth(func)
|
||||
req = MagicMock()
|
||||
req.authorization = ['Basic', 'cccccccc']
|
||||
value = wrapped_function(req)
|
||||
|
||||
# THEN: the result will be as expected - try again
|
||||
assert str(value) == str(authenticate())
|
||||
|
||||
def test_requires_auth_enabled_auth(self):
|
||||
"""
|
||||
Test the requires_auth wrapper with enabled security and authorization taken place and and error
|
||||
:return:
|
||||
"""
|
||||
# GIVEN: An enabled security and a known user
|
||||
Settings().setValue('api/authentication enabled', True)
|
||||
Settings().setValue('api/user id', 'superfly')
|
||||
Settings().setValue('api/password', 'lamas')
|
||||
|
||||
# WHEN: I call the function with the wrong password
|
||||
wrapped_function = requires_auth(func)
|
||||
req = MagicMock()
|
||||
req.authorization = ['Basic', self.password]
|
||||
value = wrapped_function(req)
|
||||
|
||||
# THEN: the result will be as expected - try again
|
||||
assert str(value) == 'called'
|
||||
|
||||
|
||||
def func(field=None):
|
||||
return 'called'
|
@ -41,7 +41,6 @@ def test_parse_options_basic():
|
||||
args = parse_options()
|
||||
|
||||
# THEN: the following fields will have been extracted.
|
||||
assert args.dev_version is False, 'The dev_version flag should be False'
|
||||
assert args.loglevel == 'warning', 'The log level should be set to warning'
|
||||
assert args.no_error_form is False, 'The no_error_form should be set to False'
|
||||
assert args.portable is False, 'The portable flag should be set to false'
|
||||
@ -59,7 +58,6 @@ def test_parse_options_debug():
|
||||
args = parse_options()
|
||||
|
||||
# THEN: the following fields will have been extracted.
|
||||
assert args.dev_version is False, 'The dev_version flag should be False'
|
||||
assert args.loglevel == ' debug', 'The log level should be set to debug'
|
||||
assert args.no_error_form is False, 'The no_error_form should be set to False'
|
||||
assert args.portable is False, 'The portable flag should be set to false'
|
||||
@ -77,7 +75,6 @@ def test_parse_options_debug_and_portable():
|
||||
args = parse_options()
|
||||
|
||||
# THEN: the following fields will have been extracted.
|
||||
assert args.dev_version is False, 'The dev_version flag should be False'
|
||||
assert args.loglevel == 'warning', 'The log level should be set to warning'
|
||||
assert args.no_error_form is False, 'The no_error_form should be set to False'
|
||||
assert args.portable is True, 'The portable flag should be set to true'
|
||||
@ -89,16 +86,15 @@ def test_parse_options_all_no_file():
|
||||
Test the parse options process works with two options
|
||||
"""
|
||||
# GIVEN: a a set of system arguments.
|
||||
sys.argv[1:] = ['-l debug', '-d']
|
||||
sys.argv[1:] = ['-l debug', '-p']
|
||||
|
||||
# WHEN: We we parse them to expand to options
|
||||
args = parse_options()
|
||||
|
||||
# THEN: the following fields will have been extracted.
|
||||
assert args.dev_version is True, 'The dev_version flag should be True'
|
||||
assert args.loglevel == ' debug', 'The log level should be set to debug'
|
||||
assert args.no_error_form is False, 'The no_error_form should be set to False'
|
||||
assert args.portable is False, 'The portable flag should be set to false'
|
||||
assert args.portable is True, 'The portable flag should be set to false'
|
||||
assert args.rargs == [], 'The service file should be blank'
|
||||
|
||||
|
||||
@ -113,7 +109,6 @@ def test_parse_options_file():
|
||||
args = parse_options()
|
||||
|
||||
# THEN: the following fields will have been extracted.
|
||||
assert args.dev_version is False, 'The dev_version flag should be False'
|
||||
assert args.loglevel == 'warning', 'The log level should be set to warning'
|
||||
assert args.no_error_form is False, 'The no_error_form should be set to False'
|
||||
assert args.portable is False, 'The portable flag should be set to false'
|
||||
@ -131,7 +126,6 @@ def test_parse_options_file_and_debug():
|
||||
args = parse_options()
|
||||
|
||||
# THEN: the following fields will have been extracted.
|
||||
assert args.dev_version is False, 'The dev_version flag should be False'
|
||||
assert args.loglevel == ' debug', 'The log level should be set to debug'
|
||||
assert args.no_error_form is False, 'The no_error_form should be set to False'
|
||||
assert args.portable is False, 'The portable flag should be set to false'
|
||||
|
114
tests/functional/openlp_core/test_server.py
Normal file
114
tests/functional/openlp_core/test_server.py
Normal file
@ -0,0 +1,114 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||
|
||||
###############################################################################
|
||||
# OpenLP - Open Source Lyrics Projection #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Copyright (c) 2008-2018 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 unittest import TestCase
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from openlp.core.server import Server
|
||||
from openlp.core.common.registry import Registry
|
||||
|
||||
from tests.helpers.testmixin import TestMixin
|
||||
|
||||
|
||||
class TestServer(TestCase, TestMixin):
|
||||
"""
|
||||
Test the Server Class used to check if OpenLP is running.
|
||||
"""
|
||||
def setUp(self):
|
||||
Registry.create()
|
||||
with patch('PyQt5.QtNetwork.QLocalSocket'):
|
||||
self.server = Server()
|
||||
|
||||
def tearDown(self):
|
||||
self.server.close_server()
|
||||
|
||||
def test_is_another_instance_running(self):
|
||||
"""
|
||||
Run a test as if this was the first time and no instance is running
|
||||
"""
|
||||
# GIVEN: A running Server
|
||||
|
||||
# WHEN: I ask for it to start
|
||||
value = self.server.is_another_instance_running()
|
||||
|
||||
# THEN the following is called
|
||||
self.server.out_socket.waitForConnected.assert_called_once_with()
|
||||
self.server.out_socket.connectToServer.assert_called_once_with(self.server.id)
|
||||
assert isinstance(value, MagicMock)
|
||||
|
||||
def test_is_another_instance_running_true(self):
|
||||
"""
|
||||
Run a test as if there is another instance running
|
||||
"""
|
||||
# GIVEN: A running Server
|
||||
self.server.out_socket.waitForConnected.return_value = True
|
||||
|
||||
# WHEN: I ask for it to start
|
||||
value = self.server.is_another_instance_running()
|
||||
|
||||
# THEN the following is called
|
||||
self.server.out_socket.waitForConnected.assert_called_once_with()
|
||||
self.server.out_socket.connectToServer.assert_called_once_with(self.server.id)
|
||||
assert value is True
|
||||
|
||||
def test_on_read_ready(self):
|
||||
"""
|
||||
Test the on_read_ready method calls the service_manager
|
||||
"""
|
||||
# GIVEN: A server with a service manager
|
||||
self.server.in_stream = MagicMock()
|
||||
service_manager = MagicMock()
|
||||
Registry().register('service_manager', service_manager)
|
||||
|
||||
# WHEN: a file is added to the socket and the method called
|
||||
file_name = '\\home\\superfly\\'
|
||||
self.server.in_stream.readLine.return_value = file_name
|
||||
self.server._on_ready_read()
|
||||
|
||||
# THEN: the service will be loaded
|
||||
assert service_manager.on_load_service_clicked.call_count == 1
|
||||
service_manager.on_load_service_clicked.assert_called_once_with(file_name)
|
||||
|
||||
@patch("PyQt5.QtCore.QTextStream")
|
||||
def test_post_to_server(self, mocked_stream):
|
||||
"""
|
||||
A Basic test with a post to the service
|
||||
:return:
|
||||
"""
|
||||
# GIVEN: A server
|
||||
# WHEN: I post to a server
|
||||
self.server.post_to_server(['l', 'a', 'm', 'a', 's'])
|
||||
|
||||
# THEN: the file should be passed out to the socket
|
||||
self.server.out_socket.write.assert_called_once_with(b'lamas')
|
||||
|
||||
@patch("PyQt5.QtCore.QTextStream")
|
||||
def test_post_to_server_openlp(self, mocked_stream):
|
||||
"""
|
||||
A Basic test with a post to the service with OpenLP
|
||||
:return:
|
||||
"""
|
||||
# GIVEN: A server
|
||||
# WHEN: I post to a server
|
||||
self.server.post_to_server(['l', 'a', 'm', 'a', 's', 'OpenLP'])
|
||||
|
||||
# THEN: the file should be passed out to the socket
|
||||
self.server.out_socket.write.assert_called_once_with(b'lamas')
|
@ -431,14 +431,16 @@ class TestMediaItem(TestCase, TestMixin):
|
||||
# GIVEN: A Song and a Service Item
|
||||
song = Song()
|
||||
song.title = 'My Song'
|
||||
song.alternate_title = ''
|
||||
song.copyright = 'My copyright'
|
||||
song.authors_songs = []
|
||||
song.songbook_entries = []
|
||||
song.ccli_number = ''
|
||||
song.topics = None
|
||||
book1 = MagicMock()
|
||||
book1.name = "My songbook"
|
||||
book1.name = 'My songbook'
|
||||
book2 = MagicMock()
|
||||
book2.name = "Thy songbook"
|
||||
book2.name = 'Thy songbook'
|
||||
song.songbookentries = []
|
||||
song.add_songbook_entry(book1, '12')
|
||||
song.add_songbook_entry(book2, '502A')
|
||||
|
Loading…
Reference in New Issue
Block a user