mirror of https://gitlab.com/openlp/openlp.git
Merged trunk on 28.4.16, removed broken test.
This commit is contained in:
commit
aa2720da40
|
@ -45,3 +45,4 @@ cover
|
|||
*.kdev4
|
||||
coverage
|
||||
tags
|
||||
output
|
||||
|
|
|
@ -24,6 +24,7 @@ The :mod:`common` module contains most of the components and libraries that make
|
|||
OpenLP work.
|
||||
"""
|
||||
import hashlib
|
||||
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
|
@ -31,6 +32,7 @@ import sys
|
|||
import traceback
|
||||
from ipaddress import IPv4Address, IPv6Address, AddressValueError
|
||||
from shutil import which
|
||||
from subprocess import check_output, CalledProcessError, STDOUT
|
||||
|
||||
from PyQt5 import QtCore, QtGui
|
||||
from PyQt5.QtCore import QCryptographicHash as QHash
|
||||
|
@ -247,6 +249,9 @@ from .applocation import AppLocation
|
|||
from .actions import ActionList
|
||||
from .languagemanager import LanguageManager
|
||||
|
||||
if is_win():
|
||||
from subprocess import STARTUPINFO, STARTF_USESHOWWINDOW
|
||||
|
||||
|
||||
def add_actions(target, actions):
|
||||
"""
|
||||
|
@ -371,3 +376,28 @@ def clean_filename(filename):
|
|||
if not isinstance(filename, str):
|
||||
filename = str(filename, 'utf-8')
|
||||
return INVALID_FILE_CHARS.sub('_', CONTROL_CHARS.sub('', filename))
|
||||
|
||||
|
||||
def check_binary_exists(program_path):
|
||||
"""
|
||||
Function that checks whether a binary exists.
|
||||
|
||||
:param program_path:The full path to the binary to check.
|
||||
:return: program output to be parsed
|
||||
"""
|
||||
log.debug('testing program_path: %s', program_path)
|
||||
try:
|
||||
# Setup startupinfo options for check_output to avoid console popping up on windows
|
||||
if is_win():
|
||||
startupinfo = STARTUPINFO()
|
||||
startupinfo.dwFlags |= STARTF_USESHOWWINDOW
|
||||
else:
|
||||
startupinfo = None
|
||||
runlog = check_output([program_path, '--help'], stderr=STDOUT, startupinfo=startupinfo)
|
||||
except CalledProcessError as e:
|
||||
runlog = e.output
|
||||
except Exception:
|
||||
trace_error_handler(log)
|
||||
runlog = ''
|
||||
log.debug('check_output returned: %s' % runlog)
|
||||
return runlog
|
||||
|
|
|
@ -107,10 +107,9 @@ class Settings(QtCore.QSettings):
|
|||
__default_settings__ = {
|
||||
'advanced/add page break': False,
|
||||
'advanced/alternate rows': not is_win(),
|
||||
'advanced/autoscrolling': {'dist': 1, 'pos': 0},
|
||||
'advanced/current media plugin': -1,
|
||||
'advanced/data path': '',
|
||||
'advanced/default color': '#ffffff',
|
||||
'advanced/default image': ':/graphics/openlp-splash-screen.png',
|
||||
# 7 stands for now, 0 to 6 is Monday to Sunday.
|
||||
'advanced/default service day': 7,
|
||||
'advanced/default service enabled': True,
|
||||
|
@ -121,7 +120,6 @@ class Settings(QtCore.QSettings):
|
|||
'advanced/double click live': False,
|
||||
'advanced/enable exit confirmation': True,
|
||||
'advanced/expand service item': False,
|
||||
'advanced/slide max height': 0,
|
||||
'advanced/hide mouse': True,
|
||||
'advanced/is portable': False,
|
||||
'advanced/max recent files': 20,
|
||||
|
@ -131,6 +129,7 @@ class Settings(QtCore.QSettings):
|
|||
'advanced/recent file count': 4,
|
||||
'advanced/save current plugin': False,
|
||||
'advanced/slide limits': SlideLimits.End,
|
||||
'advanced/slide max height': 0,
|
||||
'advanced/single click preview': False,
|
||||
'advanced/single click service preview': False,
|
||||
'advanced/x11 bypass wm': X11_BYPASS_DEFAULT,
|
||||
|
@ -152,6 +151,9 @@ class Settings(QtCore.QSettings):
|
|||
'core/save prompt': False,
|
||||
'core/screen blank': False,
|
||||
'core/show splash': True,
|
||||
'core/logo background color': '#ffffff',
|
||||
'core/logo file': ':/graphics/openlp-splash-screen.png',
|
||||
'core/logo hide on startup': False,
|
||||
'core/songselect password': '',
|
||||
'core/songselect username': '',
|
||||
'core/update check': True,
|
||||
|
@ -180,13 +182,15 @@ class Settings(QtCore.QSettings):
|
|||
'themes/wrap footer': False,
|
||||
'user interface/live panel': True,
|
||||
'user interface/live splitter geometry': QtCore.QByteArray(),
|
||||
'user interface/lock panel': False,
|
||||
'user interface/lock panel': True,
|
||||
'user interface/main window geometry': QtCore.QByteArray(),
|
||||
'user interface/main window position': QtCore.QPoint(0, 0),
|
||||
'user interface/main window splitter geometry': QtCore.QByteArray(),
|
||||
'user interface/main window state': QtCore.QByteArray(),
|
||||
'user interface/preview panel': True,
|
||||
'user interface/preview splitter geometry': QtCore.QByteArray(),
|
||||
'user interface/is preset layout': False,
|
||||
'projector/show after wizard': False,
|
||||
'projector/db type': 'sqlite',
|
||||
'projector/db username': '',
|
||||
'projector/db password': '',
|
||||
|
@ -207,7 +211,9 @@ class Settings(QtCore.QSettings):
|
|||
# ('general/recent files', 'core/recent files', [(recent_files_conv, None)]),
|
||||
('songs/search as type', 'advanced/search as type', []),
|
||||
('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 image', '/core/logo file', []) # Default image renamed + moved to general after 2.4.
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
|
|
|
@ -57,6 +57,7 @@ class UiStrings(object):
|
|||
self.AllFiles = translate('OpenLP.Ui', 'All Files')
|
||||
self.Automatic = translate('OpenLP.Ui', 'Automatic')
|
||||
self.BackgroundColor = translate('OpenLP.Ui', 'Background Color')
|
||||
self.BackgroundColorColon = translate('OpenLP.Ui', 'Background color:')
|
||||
self.Bottom = translate('OpenLP.Ui', 'Bottom')
|
||||
self.Browse = translate('OpenLP.Ui', 'Browse...')
|
||||
self.Cancel = translate('OpenLP.Ui', 'Cancel')
|
||||
|
|
|
@ -312,12 +312,9 @@ def create_separated_list(string_list):
|
|||
return translate('OpenLP.core.lib', '%s, %s', 'Locale list separator: start') % (string_list[0], merged)
|
||||
|
||||
|
||||
from .colorbutton import ColorButton
|
||||
from .exceptions import ValidationError
|
||||
from .filedialog import FileDialog
|
||||
from .screen import ScreenList
|
||||
from .listwidgetwithdnd import ListWidgetWithDnD
|
||||
from .treewidgetwithdnd import TreeWidgetWithDnD
|
||||
from .formattingtags import FormattingTags
|
||||
from .spelltextedit import SpellTextEdit
|
||||
from .plugin import PluginStatus, StringContent, Plugin
|
||||
|
@ -325,8 +322,6 @@ from .pluginmanager import PluginManager
|
|||
from .settingstab import SettingsTab
|
||||
from .serviceitem import ServiceItem, ServiceItemType, ItemCapabilities
|
||||
from .htmlbuilder import build_html, build_lyrics_format_css, build_lyrics_outline_css
|
||||
from .toolbar import OpenLPToolbar
|
||||
from .dockwidget import OpenLPDockWidget
|
||||
from .imagemanager import ImageManager
|
||||
from .renderer import Renderer
|
||||
from .mediamanageritem import MediaManagerItem
|
||||
|
|
|
@ -29,10 +29,11 @@ import re
|
|||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
from openlp.core.common import Registry, RegistryProperties, Settings, UiStrings, translate
|
||||
from openlp.core.lib import FileDialog, OpenLPToolbar, ServiceItem, StringContent, ListWidgetWithDnD, \
|
||||
ServiceItemContext
|
||||
from openlp.core.lib import FileDialog, ServiceItem, StringContent, ServiceItemContext
|
||||
from openlp.core.lib.searchedit import SearchEdit
|
||||
from openlp.core.lib.ui import create_widget_action, critical_error_message_box
|
||||
from openlp.core.ui.lib.listwidgetwithdnd import ListWidgetWithDnD
|
||||
from openlp.core.ui.lib.toolbar import OpenLPToolbar
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
|
|
@ -74,7 +74,7 @@ class Manufacturer(CommonBase, Base):
|
|||
"""
|
||||
Returns a basic representation of a Manufacturer table entry.
|
||||
"""
|
||||
return '<Manufacturer(name="%s")>' % self.name
|
||||
return '<Manufacturer(name="{name}")>'.format(name=self.name)
|
||||
|
||||
name = Column(String(30))
|
||||
models = relationship('Model',
|
||||
|
@ -101,7 +101,7 @@ class Model(CommonBase, Base):
|
|||
"""
|
||||
Returns a basic representation of a Model table entry.
|
||||
"""
|
||||
return '<Model(name=%s)>' % self.name
|
||||
return '<Model(name={name})>'.format(name=self.name)
|
||||
|
||||
manufacturer_id = Column(Integer, ForeignKey('manufacturer.id'))
|
||||
name = Column(String(20))
|
||||
|
@ -131,8 +131,9 @@ class Source(CommonBase, Base):
|
|||
"""
|
||||
Return basic representation of Source table entry.
|
||||
"""
|
||||
return '<Source(pjlink_name="%s", pjlink_code="%s", text="%s")>' % \
|
||||
(self.pjlink_name, self.pjlink_code, self.text)
|
||||
return '<Source(pjlink_name="{name}", pjlink_code="{code}", text="{Text}")>'.format(name=self.pjlink_name,
|
||||
code=self.pjlink_code,
|
||||
text=self.text)
|
||||
model_id = Column(Integer, ForeignKey('model.id'))
|
||||
pjlink_name = Column(String(15))
|
||||
pjlink_code = Column(String(2))
|
||||
|
@ -162,11 +163,22 @@ class Projector(CommonBase, Base):
|
|||
"""
|
||||
Return basic representation of Source table entry.
|
||||
"""
|
||||
return '< Projector(id="%s", ip="%s", port="%s", pin="%s", name="%s", location="%s",' \
|
||||
'notes="%s", pjlink_name="%s", manufacturer="%s", model="%s", other="%s",' \
|
||||
'sources="%s", source_list="%s") >' % (self.id, self.ip, self.port, self.pin, self.name, self.location,
|
||||
self.notes, self.pjlink_name, self.manufacturer, self.model,
|
||||
self.other, self.sources, self.source_list)
|
||||
return '< Projector(id="{data}", ip="{ip}", port="{port}", pin="{pin}", name="{name}", ' \
|
||||
'location="{location}", notes="{notes}", pjlink_name="{pjlink_name}", ' \
|
||||
'manufacturer="{manufacturer}", model="{model}", other="{other}", ' \
|
||||
'sources="{sources}", source_list="{source_list}") >'.format(data=self.id,
|
||||
ip=self.ip,
|
||||
port=self.port,
|
||||
pin=self.pin,
|
||||
name=self.name,
|
||||
location=self.location,
|
||||
notes=self.notes,
|
||||
pjlink_name=self.pjlink_name,
|
||||
manufacturer=self.manufacturer,
|
||||
model=self.model,
|
||||
other=self.other,
|
||||
sources=self.sources,
|
||||
source_list=self.source_list)
|
||||
ip = Column(String(100))
|
||||
port = Column(String(8))
|
||||
pin = Column(String(20))
|
||||
|
@ -203,10 +215,11 @@ class ProjectorSource(CommonBase, Base):
|
|||
"""
|
||||
Return basic representation of Source table entry.
|
||||
"""
|
||||
return '<ProjectorSource(id="%s", code="%s", text="%s", projector_id="%s")>' % (self.id,
|
||||
self.code,
|
||||
self.text,
|
||||
self.projector_id)
|
||||
return '<ProjectorSource(id="{data}", code="{code}", text="{text}", ' \
|
||||
'projector_id="{projector_id}")>'.format(data=self.id,
|
||||
code=self.code,
|
||||
text=self.text,
|
||||
projector_id=self.projector_id)
|
||||
code = Column(String(3))
|
||||
text = Column(String(20))
|
||||
projector_id = Column(Integer, ForeignKey('projector.id'))
|
||||
|
@ -217,10 +230,10 @@ class ProjectorDB(Manager):
|
|||
Class to access the projector database.
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
log.debug('ProjectorDB().__init__(args="%s", kwargs="%s")' % (args, kwargs))
|
||||
log.debug('ProjectorDB().__init__(args="{arg}", kwargs="{kwarg}")'.format(arg=args, kwarg=kwargs))
|
||||
super().__init__(plugin_name='projector', init_schema=self.init_schema)
|
||||
log.debug('ProjectorDB() Initialized using db url %s' % self.db_url)
|
||||
log.debug('Session: %s', self.session)
|
||||
log.debug('ProjectorDB() Initialized using db url {db}'.format(db=self.db_url))
|
||||
log.debug('Session: {session}'.format(session=self.session))
|
||||
|
||||
def init_schema(self, *args, **kwargs):
|
||||
"""
|
||||
|
@ -240,13 +253,14 @@ class ProjectorDB(Manager):
|
|||
:param dbid: DB record id
|
||||
:returns: Projector() instance
|
||||
"""
|
||||
log.debug('get_projector_by_id(id="%s")' % dbid)
|
||||
log.debug('get_projector_by_id(id="{data}")'.format(data=dbid))
|
||||
projector = self.get_object_filtered(Projector, Projector.id == dbid)
|
||||
if projector is None:
|
||||
# Not found
|
||||
log.warn('get_projector_by_id() did not find %s' % id)
|
||||
log.warn('get_projector_by_id() did not find {data}'.format(data=id))
|
||||
return None
|
||||
log.debug('get_projectorby_id() returning 1 entry for "%s" id="%s"' % (dbid, projector.id))
|
||||
log.debug('get_projectorby_id() returning 1 entry for "{entry}" id="{data}"'.format(entry=dbid,
|
||||
data=projector.id))
|
||||
return projector
|
||||
|
||||
def get_projector_all(self):
|
||||
|
@ -262,7 +276,7 @@ class ProjectorDB(Manager):
|
|||
return return_list
|
||||
for new_projector in new_list:
|
||||
return_list.append(new_projector)
|
||||
log.debug('get_all() returning %s item(s)' % len(return_list))
|
||||
log.debug('get_all() returning {items} item(s)'.format(items=len(return_list)))
|
||||
return return_list
|
||||
|
||||
def get_projector_by_ip(self, ip):
|
||||
|
@ -276,9 +290,10 @@ class ProjectorDB(Manager):
|
|||
projector = self.get_object_filtered(Projector, Projector.ip == ip)
|
||||
if projector is None:
|
||||
# Not found
|
||||
log.warn('get_projector_by_ip() did not find %s' % ip)
|
||||
log.warn('get_projector_by_ip() did not find {ip}'.format(ip=ip))
|
||||
return None
|
||||
log.debug('get_projectorby_ip() returning 1 entry for "%s" id="%s"' % (ip, projector.id))
|
||||
log.debug('get_projectorby_ip() returning 1 entry for "{ip}" id="{data}"'.format(ip=ip,
|
||||
data=projector.id))
|
||||
return projector
|
||||
|
||||
def get_projector_by_name(self, name):
|
||||
|
@ -288,13 +303,14 @@ class ProjectorDB(Manager):
|
|||
:param name: Name of projector
|
||||
:returns: Projector() instance
|
||||
"""
|
||||
log.debug('get_projector_by_name(name="%s")' % name)
|
||||
log.debug('get_projector_by_name(name="{name}")'.format(name=name))
|
||||
projector = self.get_object_filtered(Projector, Projector.name == name)
|
||||
if projector is None:
|
||||
# Not found
|
||||
log.warn('get_projector_by_name() did not find "%s"' % name)
|
||||
log.warn('get_projector_by_name() did not find "{name}"'.format(name=name))
|
||||
return None
|
||||
log.debug('get_projector_by_name() returning one entry for "%s" id="%s"' % (name, projector.id))
|
||||
log.debug('get_projector_by_name() returning one entry for "{name}" id="{data}"'.format(name=name,
|
||||
data=projector.id))
|
||||
return projector
|
||||
|
||||
def add_projector(self, projector):
|
||||
|
@ -308,13 +324,13 @@ class ProjectorDB(Manager):
|
|||
"""
|
||||
old_projector = self.get_object_filtered(Projector, Projector.ip == projector.ip)
|
||||
if old_projector is not None:
|
||||
log.warn('add_new() skipping entry ip="%s" (Already saved)' % old_projector.ip)
|
||||
log.warn('add_new() skipping entry ip="{ip}" (Already saved)'.format(ip=old_projector.ip))
|
||||
return False
|
||||
log.debug('add_new() saving new entry')
|
||||
log.debug('ip="%s", name="%s", location="%s"' % (projector.ip,
|
||||
projector.name,
|
||||
projector.location))
|
||||
log.debug('notes="%s"' % projector.notes)
|
||||
log.debug('ip="{ip}", name="{name}", location="{location}"'.format(ip=projector.ip,
|
||||
name=projector.name,
|
||||
location=projector.location))
|
||||
log.debug('notes="{notes}"'.format(notes=projector.notes))
|
||||
return self.save_object(projector)
|
||||
|
||||
def update_projector(self, projector=None):
|
||||
|
@ -333,7 +349,7 @@ class ProjectorDB(Manager):
|
|||
if old_projector is None:
|
||||
log.error('Edit called on projector instance not in database - cancelled')
|
||||
return False
|
||||
log.debug('(%s) Updating projector with dbid=%s' % (projector.ip, projector.id))
|
||||
log.debug('({ip}) Updating projector with dbid={dbid}'.format(ip=projector.ip, dbid=projector.id))
|
||||
old_projector.ip = projector.ip
|
||||
old_projector.name = projector.name
|
||||
old_projector.location = projector.location
|
||||
|
@ -357,9 +373,9 @@ class ProjectorDB(Manager):
|
|||
"""
|
||||
deleted = self.delete_object(Projector, projector.id)
|
||||
if deleted:
|
||||
log.debug('delete_by_id() Removed entry id="%s"' % projector.id)
|
||||
log.debug('delete_by_id() Removed entry id="{data}"'.format(data=projector.id))
|
||||
else:
|
||||
log.error('delete_by_id() Entry id="%s" not deleted for some reason' % projector.id)
|
||||
log.error('delete_by_id() Entry id="{data}" not deleted for some reason'.format(data=projector.id))
|
||||
return deleted
|
||||
|
||||
def get_source_list(self, projector):
|
||||
|
@ -395,9 +411,9 @@ class ProjectorDB(Manager):
|
|||
source_entry = self.get_object_filtered(ProjetorSource, ProjectorSource.id == source)
|
||||
if source_entry is None:
|
||||
# Not found
|
||||
log.warn('get_source_by_id() did not find "%s"' % source)
|
||||
log.warn('get_source_by_id() did not find "{source}"'.format(source=source))
|
||||
return None
|
||||
log.debug('get_source_by_id() returning one entry for "%s""' % (source))
|
||||
log.debug('get_source_by_id() returning one entry for "{source}""'.format(source=source))
|
||||
return source_entry
|
||||
|
||||
def get_source_by_code(self, code, projector_id):
|
||||
|
@ -411,11 +427,14 @@ class ProjectorDB(Manager):
|
|||
source_entry = self.get_object_filtered(ProjectorSource,
|
||||
and_(ProjectorSource.code == code,
|
||||
ProjectorSource.projector_id == projector_id))
|
||||
|
||||
if source_entry is None:
|
||||
# Not found
|
||||
log.warn('get_source_by_id() did not find code="%s" projector_id="%s"' % (code, projector_id))
|
||||
log.warn('get_source_by_id() not found')
|
||||
log.warn('code="{code}" projector_id="{data}"'.format(code=code, data=projector_id))
|
||||
return None
|
||||
log.debug('get_source_by_id() returning one entry for code="%s" projector_id="%s"' % (code, projector_id))
|
||||
log.debug('get_source_by_id() returning one entry')
|
||||
log.debug('code="{code}" projector_id="{data}"'.format(code=code, data=projector_id))
|
||||
return source_entry
|
||||
|
||||
def add_source(self, source):
|
||||
|
@ -424,6 +443,6 @@ class ProjectorDB(Manager):
|
|||
|
||||
:param source: ProjectorSource() instance to add
|
||||
"""
|
||||
log.debug('Saving ProjectorSource(projector_id="%s" code="%s" text="%s")' % (source.projector_id,
|
||||
source.code, source.text))
|
||||
log.debug('Saving ProjectorSource(projector_id="{data}" '
|
||||
'code="{code}" text="{text}")'.format(data=source.projector_id, code=source.code, text=source.text))
|
||||
return self.save_object(source)
|
||||
|
|
|
@ -91,7 +91,7 @@ class PJLink1(QTcpSocket):
|
|||
:param poll_time: Time (in seconds) to poll connected projector
|
||||
:param socket_timeout: Time (in seconds) to abort the connection if no response
|
||||
"""
|
||||
log.debug('PJlink(args="%s" kwargs="%s")' % (args, kwargs))
|
||||
log.debug('PJlink(args={args} kwargs={kwargs})'.format(args=args, kwargs=kwargs))
|
||||
self.name = name
|
||||
self.ip = ip
|
||||
self.port = port
|
||||
|
@ -147,7 +147,7 @@ class PJLink1(QTcpSocket):
|
|||
"""
|
||||
Reset projector-specific information to default
|
||||
"""
|
||||
log.debug('(%s) reset_information() connect status is %s' % (self.ip, self.state()))
|
||||
log.debug('({ip}) reset_information() connect status is {state}'.format(ip=self.ip, state=self.state()))
|
||||
self.power = S_OFF
|
||||
self.pjlink_name = None
|
||||
self.manufacturer = None
|
||||
|
@ -170,7 +170,7 @@ class PJLink1(QTcpSocket):
|
|||
"""
|
||||
Connects signals to methods when thread is started.
|
||||
"""
|
||||
log.debug('(%s) Thread starting' % self.ip)
|
||||
log.debug('({ip}) Thread starting'.format(ip=self.ip))
|
||||
self.i_am_running = True
|
||||
self.connected.connect(self.check_login)
|
||||
self.disconnected.connect(self.disconnect_from_host)
|
||||
|
@ -180,7 +180,7 @@ class PJLink1(QTcpSocket):
|
|||
"""
|
||||
Cleanups when thread is stopped.
|
||||
"""
|
||||
log.debug('(%s) Thread stopped' % self.ip)
|
||||
log.debug('({ip}) Thread stopped'.format(ip=self.ip))
|
||||
try:
|
||||
self.connected.disconnect(self.check_login)
|
||||
except TypeError:
|
||||
|
@ -206,7 +206,7 @@ class PJLink1(QTcpSocket):
|
|||
Aborts connection and closes socket in case of brain-dead projectors.
|
||||
Should normally be called by socket_timer().
|
||||
"""
|
||||
log.debug('(%s) socket_abort() - Killing connection' % self.ip)
|
||||
log.debug('({ip}) socket_abort() - Killing connection'.format(ip=self.ip))
|
||||
self.disconnect_from_host(abort=True)
|
||||
|
||||
def poll_loop(self):
|
||||
|
@ -216,7 +216,7 @@ class PJLink1(QTcpSocket):
|
|||
"""
|
||||
if self.state() != self.ConnectedState:
|
||||
return
|
||||
log.debug('(%s) Updating projector status' % self.ip)
|
||||
log.debug('({ip}) Updating projector status'.format(ip=self.ip))
|
||||
# Reset timer in case we were called from a set command
|
||||
if self.timer.interval() < self.poll_time:
|
||||
# Reset timer to 5 seconds
|
||||
|
@ -276,11 +276,17 @@ class PJLink1(QTcpSocket):
|
|||
self.status_connect = S_CONNECTED
|
||||
self.projector_status = status
|
||||
(status_code, status_message) = self._get_status(self.status_connect)
|
||||
log.debug('(%s) status_connect: %s: %s' % (self.ip, status_code, status_message if msg is None else msg))
|
||||
log.debug('({ip}) status_connect: {code}: "{message}"'.format(ip=self.ip,
|
||||
code=status_code,
|
||||
message=status_message if msg is None else msg))
|
||||
(status_code, status_message) = self._get_status(self.projector_status)
|
||||
log.debug('(%s) projector_status: %s: %s' % (self.ip, status_code, status_message if msg is None else msg))
|
||||
log.debug('({ip}) projector_status: {code}: "{message}"'.format(ip=self.ip,
|
||||
code=status_code,
|
||||
message=status_message if msg is None else msg))
|
||||
(status_code, status_message) = self._get_status(self.error_status)
|
||||
log.debug('(%s) error_status: %s: %s' % (self.ip, status_code, status_message if msg is None else msg))
|
||||
log.debug('({ip}) error_status: {code}: "{message}"'.format(ip=self.ip,
|
||||
code=status_code,
|
||||
message=status_message if msg is None else msg))
|
||||
self.changeStatus.emit(self.ip, status, message)
|
||||
|
||||
@pyqtSlot()
|
||||
|
@ -291,27 +297,27 @@ class PJLink1(QTcpSocket):
|
|||
|
||||
:param data: Optional data if called from another routine
|
||||
"""
|
||||
log.debug('(%s) check_login(data="%s")' % (self.ip, data))
|
||||
log.debug('({ip}) check_login(data="{data}")'.format(ip=self.ip, data=data))
|
||||
if data is None:
|
||||
# Reconnected setup?
|
||||
if not self.waitForReadyRead(2000):
|
||||
# Possible timeout issue
|
||||
log.error('(%s) Socket timeout waiting for login' % self.ip)
|
||||
log.error('({ip}) Socket timeout waiting for login'.format(ip=self.ip))
|
||||
self.change_status(E_SOCKET_TIMEOUT)
|
||||
return
|
||||
read = self.readLine(self.maxSize)
|
||||
dontcare = self.readLine(self.maxSize) # Clean out the trailing \r\n
|
||||
if read is None:
|
||||
log.warn('(%s) read is None - socket error?' % self.ip)
|
||||
log.warn('({ip}) read is None - socket error?'.format(ip=self.ip))
|
||||
return
|
||||
elif len(read) < 8:
|
||||
log.warn('(%s) Not enough data read)' % self.ip)
|
||||
log.warn('({ip}) Not enough data read)'.format(ip=self.ip))
|
||||
return
|
||||
data = decode(read, 'ascii')
|
||||
# Possibility of extraneous data on input when reading.
|
||||
# Clean out extraneous characters in buffer.
|
||||
dontcare = self.readLine(self.maxSize)
|
||||
log.debug('(%s) check_login() read "%s"' % (self.ip, data.strip()))
|
||||
log.debug('({ip}) check_login() read "{data}"'.format(ip=self.ip, data=data.strip()))
|
||||
# At this point, we should only have the initial login prompt with
|
||||
# possible authentication
|
||||
# PJLink initial login will be:
|
||||
|
@ -326,25 +332,25 @@ class PJLink1(QTcpSocket):
|
|||
else:
|
||||
# Process initial connection
|
||||
data_check = data.strip().split(' ')
|
||||
log.debug('(%s) data_check="%s"' % (self.ip, data_check))
|
||||
log.debug('({ip}) data_check="{data}"'.format(ip=self.ip, data=data_check))
|
||||
# Check for projector reporting an error
|
||||
if data_check[1].upper() == 'ERRA':
|
||||
# Authentication error
|
||||
self.disconnect_from_host()
|
||||
self.change_status(E_AUTHENTICATION)
|
||||
log.debug('(%s) emitting projectorAuthentication() signal' % self.name)
|
||||
log.debug('({ip}) emitting projectorAuthentication() signal'.format(ip=self.name))
|
||||
return
|
||||
elif data_check[1] == '0' and self.pin is not None:
|
||||
# Pin set and no authentication needed
|
||||
self.disconnect_from_host()
|
||||
self.change_status(E_AUTHENTICATION)
|
||||
log.debug('(%s) emitting projectorNoAuthentication() signal' % self.name)
|
||||
log.debug('({ip}) emitting projectorNoAuthentication() signal'.format(ip=self.name))
|
||||
self.projectorNoAuthentication.emit(self.name)
|
||||
return
|
||||
elif data_check[1] == '1':
|
||||
# Authenticated login with salt
|
||||
log.debug('(%s) Setting hash with salt="%s"' % (self.ip, data_check[2]))
|
||||
log.debug('(%s) pin="%s"' % (self.ip, self.pin))
|
||||
log.debug('({ip}) Setting hash with salt="{data}"'.format(ip=self.ip, data=data_check[2]))
|
||||
log.debug('({ip}) pin="{data}"'.format(ip=self.ip, data=self.pin))
|
||||
salt = qmd5_hash(salt=data_check[2].encode('ascii'), data=self.pin.encode('ascii'))
|
||||
else:
|
||||
salt = None
|
||||
|
@ -355,7 +361,7 @@ class PJLink1(QTcpSocket):
|
|||
self.send_command(cmd='CLSS', salt=salt)
|
||||
self.waitForReadyRead()
|
||||
if (not self.no_poll) and (self.state() == self.ConnectedState):
|
||||
log.debug('(%s) Starting timer' % self.ip)
|
||||
log.debug('({ip}) Starting timer'.format(ip=self.ip))
|
||||
self.timer.setInterval(2000) # Set 2 seconds for initial information
|
||||
self.timer.start()
|
||||
|
||||
|
@ -364,15 +370,15 @@ class PJLink1(QTcpSocket):
|
|||
"""
|
||||
Socket interface to retrieve data.
|
||||
"""
|
||||
log.debug('(%s) get_data(): Reading data' % self.ip)
|
||||
log.debug('({ip}) get_data(): Reading data'.format(ip=self.ip))
|
||||
if self.state() != self.ConnectedState:
|
||||
log.debug('(%s) get_data(): Not connected - returning' % self.ip)
|
||||
log.debug('({ip}) get_data(): Not connected - returning'.format(ip=self.ip))
|
||||
self.send_busy = False
|
||||
return
|
||||
read = self.readLine(self.maxSize)
|
||||
if read == -1:
|
||||
# No data available
|
||||
log.debug('(%s) get_data(): No data available (-1)' % self.ip)
|
||||
log.debug('({ip}) get_data(): No data available (-1)'.format(ip=self.ip))
|
||||
self.send_busy = False
|
||||
self.projectorReceivedData.emit()
|
||||
return
|
||||
|
@ -382,11 +388,11 @@ class PJLink1(QTcpSocket):
|
|||
data = data_in.strip()
|
||||
if len(data) < 7:
|
||||
# Not enough data for a packet
|
||||
log.debug('(%s) get_data(): Packet length < 7: "%s"' % (self.ip, data))
|
||||
log.debug('({ip}) get_data(): Packet length < 7: "{data}"'.format(ip=self.ip, data=data))
|
||||
self.send_busy = False
|
||||
self.projectorReceivedData.emit()
|
||||
return
|
||||
log.debug('(%s) get_data(): Checking new data "%s"' % (self.ip, data))
|
||||
log.debug('({ip}) get_data(): Checking new data "{data}"'.format(ip=self.ip, data=data))
|
||||
if data.upper().startswith('PJLINK'):
|
||||
# Reconnected from remote host disconnect ?
|
||||
self.check_login(data)
|
||||
|
@ -394,7 +400,7 @@ class PJLink1(QTcpSocket):
|
|||
self.projectorReceivedData.emit()
|
||||
return
|
||||
elif '=' not in data:
|
||||
log.warn('(%s) get_data(): Invalid packet received' % self.ip)
|
||||
log.warn('({ip}) get_data(): Invalid packet received'.format(ip=self.ip))
|
||||
self.send_busy = False
|
||||
self.projectorReceivedData.emit()
|
||||
return
|
||||
|
@ -402,15 +408,15 @@ class PJLink1(QTcpSocket):
|
|||
try:
|
||||
(prefix, class_, cmd, data) = (data_split[0][0], data_split[0][1], data_split[0][2:], data_split[1])
|
||||
except ValueError as e:
|
||||
log.warn('(%s) get_data(): Invalid packet - expected header + command + data' % self.ip)
|
||||
log.warn('(%s) get_data(): Received data: "%s"' % (self.ip, read))
|
||||
log.warn('({ip}) get_data(): Invalid packet - expected header + command + data'.format(ip=self.ip))
|
||||
log.warn('({ip}) get_data(): Received data: "{data}"'.format(ip=self.ip, data=data_in.strip()))
|
||||
self.change_status(E_INVALID_DATA)
|
||||
self.send_busy = False
|
||||
self.projectorReceivedData.emit()
|
||||
return
|
||||
|
||||
if not (self.pjlink_class in PJLINK_VALID_CMD and cmd in PJLINK_VALID_CMD[self.pjlink_class]):
|
||||
log.warn('(%s) get_data(): Invalid packet - unknown command "%s"' % (self.ip, cmd))
|
||||
log.warn('({ip}) get_data(): Invalid packet - unknown command "{data}"'.format(ip=self.ip, data=cmd))
|
||||
self.send_busy = False
|
||||
self.projectorReceivedData.emit()
|
||||
return
|
||||
|
@ -424,7 +430,7 @@ class PJLink1(QTcpSocket):
|
|||
|
||||
:param err: Error code
|
||||
"""
|
||||
log.debug('(%s) get_error(err=%s): %s' % (self.ip, err, self.errorString()))
|
||||
log.debug('({ip}) get_error(err={error}): {data}'.format(ip=self.ip, error=err, data=self.errorString()))
|
||||
if err <= 18:
|
||||
# QSocket errors. Redefined in projector.constants so we don't mistake
|
||||
# them for system errors
|
||||
|
@ -453,32 +459,35 @@ class PJLink1(QTcpSocket):
|
|||
:param queue: Option to force add to queue rather than sending directly
|
||||
"""
|
||||
if self.state() != self.ConnectedState:
|
||||
log.warn('(%s) send_command(): Not connected - returning' % self.ip)
|
||||
log.warn('({ip}) send_command(): Not connected - returning'.format(ip=self.ip))
|
||||
self.send_queue = []
|
||||
return
|
||||
self.projectorNetwork.emit(S_NETWORK_SENDING)
|
||||
log.debug('(%s) send_command(): Building cmd="%s" opts="%s" %s' % (self.ip,
|
||||
cmd,
|
||||
opts,
|
||||
'' if salt is None else 'with hash'))
|
||||
if salt is None:
|
||||
out = '%s%s %s%s' % (PJLINK_HEADER, cmd, opts, CR)
|
||||
else:
|
||||
out = '%s%s%s %s%s' % (salt, PJLINK_HEADER, cmd, opts, CR)
|
||||
log.debug('({ip}) send_command(): Building cmd="{command}" opts="{data}"{salt}'.format(ip=self.ip,
|
||||
command=cmd,
|
||||
data=opts,
|
||||
salt='' if salt is None
|
||||
else ' with hash'))
|
||||
out = '{salt}{header}{command} {options}{suffix}'.format(salt="" if salt is None else salt,
|
||||
header=PJLINK_HEADER,
|
||||
command=cmd,
|
||||
options=opts,
|
||||
suffix=CR)
|
||||
if out in self.send_queue:
|
||||
# Already there, so don't add
|
||||
log.debug('(%s) send_command(out="%s") Already in queue - skipping' % (self.ip, out.strip()))
|
||||
log.debug('({ip}) send_command(out="{data}") Already in queue - skipping'.format(ip=self.ip,
|
||||
data=out.strip()))
|
||||
elif not queue and len(self.send_queue) == 0:
|
||||
# Nothing waiting to send, so just send it
|
||||
log.debug('(%s) send_command(out="%s") Sending data' % (self.ip, out.strip()))
|
||||
log.debug('({ip}) send_command(out="{data}") Sending data'.format(ip=self.ip, data=out.strip()))
|
||||
return self._send_command(data=out)
|
||||
else:
|
||||
log.debug('(%s) send_command(out="%s") adding to queue' % (self.ip, out.strip()))
|
||||
log.debug('({ip}) send_command(out="{data}") adding to queue'.format(ip=self.ip, data=out.strip()))
|
||||
self.send_queue.append(out)
|
||||
self.projectorReceivedData.emit()
|
||||
log.debug('(%s) send_command(): send_busy is %s' % (self.ip, self.send_busy))
|
||||
log.debug('({ip}) send_command(): send_busy is {data}'.format(ip=self.ip, data=self.send_busy))
|
||||
if not self.send_busy:
|
||||
log.debug('(%s) send_command() calling _send_string()')
|
||||
log.debug('({ip}) send_command() calling _send_string()'.format(ip=self.ip))
|
||||
self._send_command()
|
||||
|
||||
@pyqtSlot()
|
||||
|
@ -488,10 +497,10 @@ class PJLink1(QTcpSocket):
|
|||
|
||||
:param data: Immediate data to send
|
||||
"""
|
||||
log.debug('(%s) _send_string()' % self.ip)
|
||||
log.debug('(%s) _send_string(): Connection status: %s' % (self.ip, self.state()))
|
||||
log.debug('({ip}) _send_string()'.format(ip=self.ip))
|
||||
log.debug('({ip}) _send_string(): Connection status: {data}'.format(ip=self.ip, data=self.state()))
|
||||
if self.state() != self.ConnectedState:
|
||||
log.debug('(%s) _send_string() Not connected - abort' % self.ip)
|
||||
log.debug('({ip}) _send_string() Not connected - abort'.format(ip=self.ip))
|
||||
self.send_queue = []
|
||||
self.send_busy = False
|
||||
return
|
||||
|
@ -500,18 +509,18 @@ class PJLink1(QTcpSocket):
|
|||
return
|
||||
if data is not None:
|
||||
out = data
|
||||
log.debug('(%s) _send_string(data=%s)' % (self.ip, out.strip()))
|
||||
log.debug('({ip}) _send_string(data="{data}")'.format(ip=self.ip, data=out.strip()))
|
||||
elif len(self.send_queue) != 0:
|
||||
out = self.send_queue.pop(0)
|
||||
log.debug('(%s) _send_string(queued data=%s)' % (self.ip, out.strip()))
|
||||
log.debug('({ip}) _send_string(queued data="{data}"%s)'.format(ip=self.ip, data=out.strip()))
|
||||
else:
|
||||
# No data to send
|
||||
log.debug('(%s) _send_string(): No data to send' % self.ip)
|
||||
log.debug('({ip}) _send_string(): No data to send'.format(ip=self.ip))
|
||||
self.send_busy = False
|
||||
return
|
||||
self.send_busy = True
|
||||
log.debug('(%s) _send_string(): Sending "%s"' % (self.ip, out.strip()))
|
||||
log.debug('(%s) _send_string(): Queue = %s' % (self.ip, self.send_queue))
|
||||
log.debug('({ip}) _send_string(): Sending "{data}"'.format(ip=self.ip, data=out.strip()))
|
||||
log.debug('({ip}) _send_string(): Queue = {data}'.format(ip=self.ip, data=self.send_queue))
|
||||
self.socket_timer.start()
|
||||
self.projectorNetwork.emit(S_NETWORK_SENDING)
|
||||
sent = self.write(out.encode('ascii'))
|
||||
|
@ -528,19 +537,21 @@ class PJLink1(QTcpSocket):
|
|||
:param cmd: Command to process
|
||||
:param data: Data being processed
|
||||
"""
|
||||
log.debug('(%s) Processing command "%s"' % (self.ip, cmd))
|
||||
log.debug('({ip}) Processing command "{data}"'.format(ip=self.ip, data=cmd))
|
||||
if data in PJLINK_ERRORS:
|
||||
# Oops - projector error
|
||||
log.error('({ip}) Projector returned error "{data}"'.format(ip=self.ip, data=data))
|
||||
if data.upper() == 'ERRA':
|
||||
# Authentication error
|
||||
self.disconnect_from_host()
|
||||
self.change_status(E_AUTHENTICATION)
|
||||
log.debug('(%s) emitting projectorAuthentication() signal' % self.ip)
|
||||
log.debug('({ip}) emitting projectorAuthentication() signal'.format(ip=self.ip))
|
||||
self.projectorAuthentication.emit(self.name)
|
||||
elif data.upper() == 'ERR1':
|
||||
# Undefined command
|
||||
self.change_status(E_UNDEFINED, '%s "%s"' %
|
||||
(translate('OpenLP.PJLink1', 'Undefined command:'), cmd))
|
||||
self.change_status(E_UNDEFINED, '{error} "{data}"'.format(error=translate('OpenLP.PJLink1',
|
||||
'Undefined command:'),
|
||||
data=cmd))
|
||||
elif data.upper() == 'ERR2':
|
||||
# Invalid parameter
|
||||
self.change_status(E_PARAMETER)
|
||||
|
@ -555,7 +566,7 @@ class PJLink1(QTcpSocket):
|
|||
return
|
||||
# Command succeeded - no extra information
|
||||
elif data.upper() == 'OK':
|
||||
log.debug('(%s) Command returned OK' % self.ip)
|
||||
log.debug('({ip}) Command returned OK'.format(ip=self.ip))
|
||||
# A command returned successfully, recheck data
|
||||
self.send_busy = False
|
||||
self.projectorReceivedData.emit()
|
||||
|
@ -564,7 +575,7 @@ class PJLink1(QTcpSocket):
|
|||
if cmd in self.PJLINK1_FUNC:
|
||||
self.PJLINK1_FUNC[cmd](data)
|
||||
else:
|
||||
log.warn('(%s) Invalid command %s' % (self.ip, cmd))
|
||||
log.warn('({ip}) Invalid command {data}'.format(ip=self.ip, data=cmd))
|
||||
self.send_busy = False
|
||||
self.projectorReceivedData.emit()
|
||||
|
||||
|
@ -583,7 +594,7 @@ class PJLink1(QTcpSocket):
|
|||
fill = {'Hours': int(data_dict[0]), 'On': False if data_dict[1] == '0' else True}
|
||||
except ValueError:
|
||||
# In case of invalid entry
|
||||
log.warn('(%s) process_lamp(): Invalid data "%s"' % (self.ip, data))
|
||||
log.warn('({ip}) process_lamp(): Invalid data "{data}"'.format(ip=self.ip, data=data))
|
||||
return
|
||||
lamps.append(fill)
|
||||
data_dict.pop(0) # Remove lamp hours
|
||||
|
@ -610,7 +621,7 @@ class PJLink1(QTcpSocket):
|
|||
self.send_command('INST')
|
||||
else:
|
||||
# Log unknown status response
|
||||
log.warn('Unknown power response: %s' % data)
|
||||
log.warn('({ip}) Unknown power response: {data}'.format(ip=self.ip, data=data))
|
||||
return
|
||||
|
||||
def process_avmt(self, data):
|
||||
|
@ -635,7 +646,7 @@ class PJLink1(QTcpSocket):
|
|||
shutter = True
|
||||
mute = True
|
||||
else:
|
||||
log.warn('Unknown shutter response: %s' % data)
|
||||
log.warn('({ip}) Unknown shutter response: {data}'.format(ip=self.ip, data=data))
|
||||
update_icons = shutter != self.shutter
|
||||
update_icons = update_icons or mute != self.mute
|
||||
self.shutter = shutter
|
||||
|
@ -652,6 +663,7 @@ class PJLink1(QTcpSocket):
|
|||
:param data: Currently selected source
|
||||
"""
|
||||
self.source = data
|
||||
log.info('({ip}) Setting data source to "{data}"'.format(ip=self.ip, data=self.source))
|
||||
return
|
||||
|
||||
def process_clss(self, data):
|
||||
|
@ -670,7 +682,8 @@ class PJLink1(QTcpSocket):
|
|||
else:
|
||||
clss = data
|
||||
self.pjlink_class = clss
|
||||
log.debug('(%s) Setting pjlink_class for this projector to "%s"' % (self.ip, self.pjlink_class))
|
||||
log.debug('({ip}) Setting pjlink_class for this projector to "{data}"'.format(ip=self.ip,
|
||||
data=self.pjlink_class))
|
||||
return
|
||||
|
||||
def process_name(self, data):
|
||||
|
@ -681,6 +694,7 @@ class PJLink1(QTcpSocket):
|
|||
:param data: Projector name
|
||||
"""
|
||||
self.pjlink_name = data
|
||||
log.debug('({ip}) Setting projector PJLink name to "{data}"'.format(ip=self.ip, data=self.pjlink_name))
|
||||
return
|
||||
|
||||
def process_inf1(self, data):
|
||||
|
@ -691,6 +705,7 @@ class PJLink1(QTcpSocket):
|
|||
:param data: Projector manufacturer
|
||||
"""
|
||||
self.manufacturer = data
|
||||
log.debug('({ip}) Setting projector manufacturer data to "{data}"'.format(ip=self.ip, data=self.manufacturer))
|
||||
return
|
||||
|
||||
def process_inf2(self, data):
|
||||
|
@ -701,6 +716,7 @@ class PJLink1(QTcpSocket):
|
|||
:param data: Model name
|
||||
"""
|
||||
self.model = data
|
||||
log.debug('({ip}) Setting projector model to "{data}"'.format(ip=self.ip, data=self.model))
|
||||
return
|
||||
|
||||
def process_info(self, data):
|
||||
|
@ -711,6 +727,7 @@ class PJLink1(QTcpSocket):
|
|||
:param data: Projector other info
|
||||
"""
|
||||
self.other_info = data
|
||||
log.debug('({ip}) Setting projector other_info to "{data}"'.format(ip=self.ip, data=self.other_info))
|
||||
return
|
||||
|
||||
def process_inst(self, data):
|
||||
|
@ -727,6 +744,8 @@ class PJLink1(QTcpSocket):
|
|||
sources.sort()
|
||||
self.source_available = sources
|
||||
self.projectorUpdateIcons.emit()
|
||||
log.debug('({ip}) Setting projector sources_available to "{data}"'.format(ip=self.ip,
|
||||
data=self.source_available))
|
||||
return
|
||||
|
||||
def process_erst(self, data):
|
||||
|
@ -776,7 +795,7 @@ class PJLink1(QTcpSocket):
|
|||
Initiate connection to projector.
|
||||
"""
|
||||
if self.state() == self.ConnectedState:
|
||||
log.warn('(%s) connect_to_host(): Already connected - returning' % self.ip)
|
||||
log.warn('({ip}) connect_to_host(): Already connected - returning'.format(ip=self.ip))
|
||||
return
|
||||
self.change_status(S_CONNECTING)
|
||||
self.connectToHost(self.ip, self.port if type(self.port) is int else int(self.port))
|
||||
|
@ -788,9 +807,9 @@ class PJLink1(QTcpSocket):
|
|||
"""
|
||||
if abort or self.state() != self.ConnectedState:
|
||||
if abort:
|
||||
log.warn('(%s) disconnect_from_host(): Aborting connection' % self.ip)
|
||||
log.warn('({ip}) disconnect_from_host(): Aborting connection'.format(ip=self.ip))
|
||||
else:
|
||||
log.warn('(%s) disconnect_from_host(): Not connected - returning' % self.ip)
|
||||
log.warn('({ip}) disconnect_from_host(): Not connected - returning'.format(ip=self.ip))
|
||||
self.reset_information()
|
||||
self.disconnectFromHost()
|
||||
try:
|
||||
|
@ -800,8 +819,8 @@ class PJLink1(QTcpSocket):
|
|||
if abort:
|
||||
self.change_status(E_NOT_CONNECTED)
|
||||
else:
|
||||
log.debug('(%s) disconnect_from_host() Current status %s' % (self.ip,
|
||||
self._get_status(self.status_connect)[0]))
|
||||
log.debug('({ip}) disconnect_from_host() '
|
||||
'Current status {data}'.format(ip=self.ip, data=self._get_status(self.status_connect)[0]))
|
||||
if self.status_connect != E_NOT_CONNECTED:
|
||||
self.change_status(S_NOT_CONNECTED)
|
||||
self.reset_information()
|
||||
|
@ -811,60 +830,70 @@ class PJLink1(QTcpSocket):
|
|||
"""
|
||||
Send command to retrieve available source inputs.
|
||||
"""
|
||||
log.debug('({ip}) Sending INST command'.format(ip=self.ip))
|
||||
return self.send_command(cmd='INST')
|
||||
|
||||
def get_error_status(self):
|
||||
"""
|
||||
Send command to retrieve currently known errors.
|
||||
"""
|
||||
log.debug('({ip}) Sending ERST command'.format(ip=self.ip))
|
||||
return self.send_command(cmd='ERST')
|
||||
|
||||
def get_input_source(self):
|
||||
"""
|
||||
Send command to retrieve currently selected source input.
|
||||
"""
|
||||
log.debug('({ip}) Sending INPT command'.format(ip=self.ip))
|
||||
return self.send_command(cmd='INPT')
|
||||
|
||||
def get_lamp_status(self):
|
||||
"""
|
||||
Send command to return the lap status.
|
||||
"""
|
||||
log.debug('({ip}) Sending LAMP command'.format(ip=self.ip))
|
||||
return self.send_command(cmd='LAMP')
|
||||
|
||||
def get_manufacturer(self):
|
||||
"""
|
||||
Send command to retrieve manufacturer name.
|
||||
"""
|
||||
log.debug('({ip}) Sending INF1 command'.format(ip=self.ip))
|
||||
return self.send_command(cmd='INF1')
|
||||
|
||||
def get_model(self):
|
||||
"""
|
||||
Send command to retrieve the model name.
|
||||
"""
|
||||
log.debug('({ip}) Sending INF2 command'.format(ip=self.ip))
|
||||
return self.send_command(cmd='INF2')
|
||||
|
||||
def get_name(self):
|
||||
"""
|
||||
Send command to retrieve name as set by end-user (if set).
|
||||
"""
|
||||
log.debug('({ip}) Sending NAME command'.format(ip=self.ip))
|
||||
return self.send_command(cmd='NAME')
|
||||
|
||||
def get_other_info(self):
|
||||
"""
|
||||
Send command to retrieve extra info set by manufacturer.
|
||||
"""
|
||||
log.debug('({ip}) Sending INFO command'.format(ip=self.ip))
|
||||
return self.send_command(cmd='INFO')
|
||||
|
||||
def get_power_status(self):
|
||||
"""
|
||||
Send command to retrieve power status.
|
||||
"""
|
||||
log.debug('({ip}) Sending POWR command'.format(ip=self.ip))
|
||||
return self.send_command(cmd='POWR')
|
||||
|
||||
def get_shutter_status(self):
|
||||
"""
|
||||
Send command to retrieve shutter status.
|
||||
"""
|
||||
log.debug('({ip}) Sending AVMT command'.format(ip=self.ip))
|
||||
return self.send_command(cmd='AVMT')
|
||||
|
||||
def set_input_source(self, src=None):
|
||||
|
@ -874,12 +903,12 @@ class PJLink1(QTcpSocket):
|
|||
|
||||
:param src: Video source to select in projector
|
||||
"""
|
||||
log.debug('(%s) set_input_source(src=%s)' % (self.ip, src))
|
||||
log.debug('({ip}) set_input_source(src="{data}")'.format(ip=self.ip, data=src))
|
||||
if self.source_available is None:
|
||||
return
|
||||
elif src not in self.source_available:
|
||||
return
|
||||
log.debug('(%s) Setting input source to %s' % (self.ip, src))
|
||||
log.debug('({ip}) Setting input source to "{data}"'.format(ip=self.ip, data=src))
|
||||
self.send_command(cmd='INPT', opts=src)
|
||||
self.poll_loop()
|
||||
|
||||
|
@ -887,6 +916,7 @@ class PJLink1(QTcpSocket):
|
|||
"""
|
||||
Send command to turn power to on.
|
||||
"""
|
||||
log.debug('({ip}) Setting POWR to 1 (on)'.format(ip=self.ip))
|
||||
self.send_command(cmd='POWR', opts='1')
|
||||
self.poll_loop()
|
||||
|
||||
|
@ -894,6 +924,7 @@ class PJLink1(QTcpSocket):
|
|||
"""
|
||||
Send command to turn power to standby.
|
||||
"""
|
||||
log.debug('({ip}) Setting POWR to 0 (standby)'.format(ip=self.ip))
|
||||
self.send_command(cmd='POWR', opts='0')
|
||||
self.poll_loop()
|
||||
|
||||
|
@ -901,6 +932,7 @@ class PJLink1(QTcpSocket):
|
|||
"""
|
||||
Send command to set shutter to closed position.
|
||||
"""
|
||||
log.debug('({ip}) Setting AVMT to 11 (shutter closed)'.format(ip=self.ip))
|
||||
self.send_command(cmd='AVMT', opts='11')
|
||||
self.poll_loop()
|
||||
|
||||
|
@ -908,5 +940,6 @@ class PJLink1(QTcpSocket):
|
|||
"""
|
||||
Send command to set shutter to open position.
|
||||
"""
|
||||
log.debug('({ip}) Setting AVMT to "10" (shutter open)'.format(ip=self.ip))
|
||||
self.send_command(cmd='AVMT', opts='10')
|
||||
self.poll_loop()
|
||||
|
|
|
@ -610,7 +610,7 @@ class ServiceItem(RegistryProperties):
|
|||
str(datetime.timedelta(seconds=self.start_time))
|
||||
if self.media_length != 0:
|
||||
end = translate('OpenLP.ServiceItem', '<strong>Length</strong>: %s') % \
|
||||
str(datetime.timedelta(seconds=self.media_length))
|
||||
str(datetime.timedelta(seconds=self.media_length // 1000))
|
||||
if not start and not end:
|
||||
return ''
|
||||
elif start and not end:
|
||||
|
|
|
@ -68,7 +68,6 @@ class DisplayControllerType(object):
|
|||
"""
|
||||
Live = 0
|
||||
Preview = 1
|
||||
Plugin = 2
|
||||
|
||||
|
||||
class SingleColumnTableWidget(QtWidgets.QTableWidget):
|
||||
|
@ -114,7 +113,6 @@ from .settingsform import SettingsForm
|
|||
from .formattingtagform import FormattingTagForm
|
||||
from .formattingtagcontroller import FormattingTagController
|
||||
from .shortcutlistform import ShortcutListForm
|
||||
from .mediadockmanager import MediaDockManager
|
||||
from .servicemanager import ServiceManager
|
||||
from .thememanager import ThemeManager
|
||||
from .projector.manager import ProjectorManager
|
||||
|
@ -122,7 +120,7 @@ from .projector.tab import ProjectorTab
|
|||
from .projector.editform import ProjectorEditForm
|
||||
|
||||
__all__ = ['SplashScreen', 'AboutForm', 'SettingsForm', 'MainDisplay', 'SlideController', 'ServiceManager', 'ThemeForm',
|
||||
'ThemeManager', 'MediaDockManager', 'ServiceItemEditForm', 'FirstTimeForm', 'FirstTimeLanguageForm',
|
||||
'ThemeManager', 'ServiceItemEditForm', 'FirstTimeForm', 'FirstTimeLanguageForm',
|
||||
'Display', 'ServiceNoteForm', 'ThemeLayoutForm', 'FileRenameForm', 'StartTimeForm', 'MainDisplay',
|
||||
'SlideController', 'DisplayController', 'GeneralTab', 'ThemesTab', 'AdvancedTab', 'PluginForm',
|
||||
'FormattingTagForm', 'ShortcutListForm', 'FormattingTagController', 'SingleColumnTableWidget',
|
||||
|
|
|
@ -29,8 +29,8 @@ import sys
|
|||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
from openlp.core.common import AppLocation, Settings, SlideLimits, UiStrings, translate, get_images_filter
|
||||
from openlp.core.lib import ColorButton, SettingsTab, build_icon
|
||||
from openlp.core.common import AppLocation, Settings, SlideLimits, UiStrings, translate
|
||||
from openlp.core.lib import SettingsTab, build_icon
|
||||
from openlp.core.common.languagemanager import format_time
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
@ -45,10 +45,12 @@ class AdvancedTab(SettingsTab):
|
|||
"""
|
||||
Initialise the settings tab
|
||||
"""
|
||||
self.default_image = ':/graphics/openlp-splash-screen.png'
|
||||
self.default_color = '#ffffff'
|
||||
self.data_exists = False
|
||||
self.icon_path = ':/system/system_settings.png'
|
||||
self.autoscroll_map = [None, {'dist': -1, 'pos': 0}, {'dist': -1, 'pos': 1}, {'dist': -1, 'pos': 2},
|
||||
{'dist': 0, 'pos': 0}, {'dist': 0, 'pos': 1}, {'dist': 0, 'pos': 2},
|
||||
{'dist': 0, 'pos': 3}, {'dist': 1, 'pos': 0}, {'dist': 1, 'pos': 1},
|
||||
{'dist': 1, 'pos': 2}, {'dist': 1, 'pos': 3}]
|
||||
advanced_translated = translate('OpenLP.AdvancedTab', 'Advanced')
|
||||
super(AdvancedTab, self).__init__(parent, 'Advanced', advanced_translated)
|
||||
|
||||
|
@ -90,6 +92,13 @@ class AdvancedTab(SettingsTab):
|
|||
self.slide_max_height_spin_box.setRange(0, 1000)
|
||||
self.slide_max_height_spin_box.setSingleStep(20)
|
||||
self.ui_layout.addRow(self.slide_max_height_label, self.slide_max_height_spin_box)
|
||||
self.autoscroll_label = QtWidgets.QLabel(self.ui_group_box)
|
||||
self.autoscroll_label.setObjectName('autoscroll_label')
|
||||
self.autoscroll_combo_box = QtWidgets.QComboBox(self.ui_group_box)
|
||||
self.autoscroll_combo_box.addItems(['', '', '', '', '', '', '', '', '', '', '', ''])
|
||||
self.autoscroll_combo_box.setObjectName('autoscroll_combo_box')
|
||||
self.ui_layout.addRow(self.autoscroll_label)
|
||||
self.ui_layout.addRow(self.autoscroll_combo_box)
|
||||
self.search_as_type_check_box = QtWidgets.QCheckBox(self.ui_group_box)
|
||||
self.search_as_type_check_box.setObjectName('SearchAsType_check_box')
|
||||
self.ui_layout.addRow(self.search_as_type_check_box)
|
||||
|
@ -180,33 +189,6 @@ class AdvancedTab(SettingsTab):
|
|||
self.data_directory_layout.addRow(self.new_data_directory_has_files_label)
|
||||
self.left_layout.addWidget(self.data_directory_group_box)
|
||||
self.left_layout.addStretch()
|
||||
# Default Image
|
||||
self.default_image_group_box = QtWidgets.QGroupBox(self.right_column)
|
||||
self.default_image_group_box.setObjectName('default_image_group_box')
|
||||
self.default_image_layout = QtWidgets.QFormLayout(self.default_image_group_box)
|
||||
self.default_image_layout.setObjectName('default_image_layout')
|
||||
self.default_color_label = QtWidgets.QLabel(self.default_image_group_box)
|
||||
self.default_color_label.setObjectName('default_color_label')
|
||||
self.default_color_button = ColorButton(self.default_image_group_box)
|
||||
self.default_color_button.setObjectName('default_color_button')
|
||||
self.default_image_layout.addRow(self.default_color_label, self.default_color_button)
|
||||
self.default_file_label = QtWidgets.QLabel(self.default_image_group_box)
|
||||
self.default_file_label.setObjectName('default_file_label')
|
||||
self.default_file_edit = QtWidgets.QLineEdit(self.default_image_group_box)
|
||||
self.default_file_edit.setObjectName('default_file_edit')
|
||||
self.default_browse_button = QtWidgets.QToolButton(self.default_image_group_box)
|
||||
self.default_browse_button.setObjectName('default_browse_button')
|
||||
self.default_browse_button.setIcon(build_icon(':/general/general_open.png'))
|
||||
self.default_revert_button = QtWidgets.QToolButton(self.default_image_group_box)
|
||||
self.default_revert_button.setObjectName('default_revert_button')
|
||||
self.default_revert_button.setIcon(build_icon(':/general/general_revert.png'))
|
||||
self.default_file_layout = QtWidgets.QHBoxLayout()
|
||||
self.default_file_layout.setObjectName('default_file_layout')
|
||||
self.default_file_layout.addWidget(self.default_file_edit)
|
||||
self.default_file_layout.addWidget(self.default_browse_button)
|
||||
self.default_file_layout.addWidget(self.default_revert_button)
|
||||
self.default_image_layout.addRow(self.default_file_label, self.default_file_layout)
|
||||
self.right_layout.addWidget(self.default_image_group_box)
|
||||
# Hide mouse
|
||||
self.hide_mouse_group_box = QtWidgets.QGroupBox(self.right_column)
|
||||
self.hide_mouse_group_box.setObjectName('hide_mouse_group_box')
|
||||
|
@ -253,9 +235,6 @@ class AdvancedTab(SettingsTab):
|
|||
self.service_name_time.timeChanged.connect(self.update_service_name_example)
|
||||
self.service_name_edit.textChanged.connect(self.update_service_name_example)
|
||||
self.service_name_revert_button.clicked.connect(self.on_service_name_revert_button_clicked)
|
||||
self.default_color_button.colorChanged.connect(self.on_background_color_changed)
|
||||
self.default_browse_button.clicked.connect(self.on_default_browse_button_clicked)
|
||||
self.default_revert_button.clicked.connect(self.on_default_revert_button_clicked)
|
||||
self.alternate_rows_check_box.toggled.connect(self.on_alternate_rows_check_box_toggled)
|
||||
self.data_directory_browse_button.clicked.connect(self.on_data_directory_browse_button_clicked)
|
||||
self.data_directory_default_button.clicked.connect(self.on_data_directory_default_button_clicked)
|
||||
|
@ -287,6 +266,31 @@ class AdvancedTab(SettingsTab):
|
|||
self.slide_max_height_label.setText(translate('OpenLP.AdvancedTab',
|
||||
'Max height for non-text slides\nin slide controller:'))
|
||||
self.slide_max_height_spin_box.setSpecialValueText(translate('OpenLP.AdvancedTab', 'Disabled'))
|
||||
self.autoscroll_label.setText(translate('OpenLP.AdvancedTab',
|
||||
'When changing slides:'))
|
||||
self.autoscroll_combo_box.setItemText(0, translate('OpenLP.AdvancedTab', 'Do not auto-scroll'))
|
||||
self.autoscroll_combo_box.setItemText(1, translate('OpenLP.AdvancedTab',
|
||||
'Auto-scroll the previous slide into view'))
|
||||
self.autoscroll_combo_box.setItemText(2, translate('OpenLP.AdvancedTab',
|
||||
'Auto-scroll the previous slide to top'))
|
||||
self.autoscroll_combo_box.setItemText(3, translate('OpenLP.AdvancedTab',
|
||||
'Auto-scroll the previous slide to middle'))
|
||||
self.autoscroll_combo_box.setItemText(4, translate('OpenLP.AdvancedTab',
|
||||
'Auto-scroll the current slide into view'))
|
||||
self.autoscroll_combo_box.setItemText(5, translate('OpenLP.AdvancedTab',
|
||||
'Auto-scroll the current slide to top'))
|
||||
self.autoscroll_combo_box.setItemText(6, translate('OpenLP.AdvancedTab',
|
||||
'Auto-scroll the current slide to middle'))
|
||||
self.autoscroll_combo_box.setItemText(7, translate('OpenLP.AdvancedTab',
|
||||
'Auto-scroll the current slide to bottom'))
|
||||
self.autoscroll_combo_box.setItemText(8, translate('OpenLP.AdvancedTab',
|
||||
'Auto-scroll the next slide into view'))
|
||||
self.autoscroll_combo_box.setItemText(9, translate('OpenLP.AdvancedTab',
|
||||
'Auto-scroll the next slide to top'))
|
||||
self.autoscroll_combo_box.setItemText(10, translate('OpenLP.AdvancedTab',
|
||||
'Auto-scroll the next slide to middle'))
|
||||
self.autoscroll_combo_box.setItemText(11, translate('OpenLP.AdvancedTab',
|
||||
'Auto-scroll the next slide to bottom'))
|
||||
self.enable_auto_close_check_box.setText(translate('OpenLP.AdvancedTab',
|
||||
'Enable application exit confirmation'))
|
||||
self.service_name_group_box.setTitle(translate('OpenLP.AdvancedTab', 'Default Service Name'))
|
||||
|
@ -309,11 +313,6 @@ class AdvancedTab(SettingsTab):
|
|||
self.service_name_example_label.setText(translate('OpenLP.AdvancedTab', 'Example:'))
|
||||
self.hide_mouse_group_box.setTitle(translate('OpenLP.AdvancedTab', 'Mouse Cursor'))
|
||||
self.hide_mouse_check_box.setText(translate('OpenLP.AdvancedTab', 'Hide mouse cursor when over display window'))
|
||||
self.default_image_group_box.setTitle(translate('OpenLP.AdvancedTab', 'Default Image'))
|
||||
self.default_color_label.setText(translate('OpenLP.AdvancedTab', 'Background color:'))
|
||||
self.default_file_label.setText(translate('OpenLP.AdvancedTab', 'Image file:'))
|
||||
self.default_browse_button.setToolTip(translate('OpenLP.AdvancedTab', 'Browse for an image file to display.'))
|
||||
self.default_revert_button.setToolTip(translate('OpenLP.AdvancedTab', 'Revert to the default OpenLP logo.'))
|
||||
self.data_directory_current_label.setText(translate('OpenLP.AdvancedTab', 'Current path:'))
|
||||
self.data_directory_new_label.setText(translate('OpenLP.AdvancedTab', 'Custom path:'))
|
||||
self.data_directory_browse_button.setToolTip(translate('OpenLP.AdvancedTab',
|
||||
|
@ -357,6 +356,10 @@ class AdvancedTab(SettingsTab):
|
|||
self.single_click_service_preview_check_box.setChecked(settings.value('single click service preview'))
|
||||
self.expand_service_item_check_box.setChecked(settings.value('expand service item'))
|
||||
self.slide_max_height_spin_box.setValue(settings.value('slide max height'))
|
||||
autoscroll_value = settings.value('autoscrolling')
|
||||
for i in range(0, len(self.autoscroll_map)):
|
||||
if self.autoscroll_map[i] == autoscroll_value:
|
||||
self.autoscroll_combo_box.setCurrentIndex(i)
|
||||
self.enable_auto_close_check_box.setChecked(settings.value('enable exit confirmation'))
|
||||
self.hide_mouse_check_box.setChecked(settings.value('hide mouse'))
|
||||
self.service_name_day.setCurrentIndex(settings.value('default service day'))
|
||||
|
@ -368,8 +371,6 @@ class AdvancedTab(SettingsTab):
|
|||
self.service_name_check_box.setChecked(default_service_enabled)
|
||||
self.service_name_check_box_toggled(default_service_enabled)
|
||||
self.x11_bypass_check_box.setChecked(settings.value('x11 bypass wm'))
|
||||
self.default_color = settings.value('default color')
|
||||
self.default_file_edit.setText(settings.value('default image'))
|
||||
self.slide_limits = settings.value('slide limits')
|
||||
self.is_search_as_you_type_enabled = settings.value('search as type')
|
||||
self.search_as_type_check_box.setChecked(self.is_search_as_you_type_enabled)
|
||||
|
@ -411,7 +412,6 @@ class AdvancedTab(SettingsTab):
|
|||
self.current_data_path = AppLocation.get_data_path()
|
||||
log.warning('User requested data path set to default %s' % self.current_data_path)
|
||||
self.data_directory_label.setText(os.path.abspath(self.current_data_path))
|
||||
self.default_color_button.color = self.default_color
|
||||
# Don't allow data directory move if running portable.
|
||||
if settings.value('advanced/is portable'):
|
||||
self.data_directory_group_box.hide()
|
||||
|
@ -440,11 +440,10 @@ class AdvancedTab(SettingsTab):
|
|||
settings.setValue('single click service preview', self.single_click_service_preview_check_box.isChecked())
|
||||
settings.setValue('expand service item', self.expand_service_item_check_box.isChecked())
|
||||
settings.setValue('slide max height', self.slide_max_height_spin_box.value())
|
||||
settings.setValue('autoscrolling', self.autoscroll_map[self.autoscroll_combo_box.currentIndex()])
|
||||
settings.setValue('enable exit confirmation', self.enable_auto_close_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('default color', self.default_color)
|
||||
settings.setValue('default image', self.default_file_edit.text())
|
||||
settings.setValue('slide limits', self.slide_limits)
|
||||
if self.x11_bypass_check_box.isChecked() != settings.value('x11 bypass wm'):
|
||||
settings.setValue('x11 bypass wm', self.x11_bypass_check_box.isChecked())
|
||||
|
@ -522,24 +521,6 @@ class AdvancedTab(SettingsTab):
|
|||
self.service_name_edit.setText(UiStrings().DefaultServiceName)
|
||||
self.service_name_edit.setFocus()
|
||||
|
||||
def on_background_color_changed(self, color):
|
||||
"""
|
||||
Select the background colour of the default display screen.
|
||||
"""
|
||||
self.default_color = color
|
||||
|
||||
def on_default_browse_button_clicked(self):
|
||||
"""
|
||||
Select an image for the default display screen.
|
||||
"""
|
||||
file_filters = '%s;;%s (*.*)' % (get_images_filter(), UiStrings().AllFiles)
|
||||
filename, filter_used = QtWidgets.QFileDialog.getOpenFileName(self,
|
||||
translate('OpenLP.AdvancedTab', 'Open File'), '',
|
||||
file_filters)
|
||||
if filename:
|
||||
self.default_file_edit.setText(filename)
|
||||
self.default_file_edit.setFocus()
|
||||
|
||||
def on_data_directory_browse_button_clicked(self):
|
||||
"""
|
||||
Browse for a new data directory location.
|
||||
|
@ -657,13 +638,6 @@ class AdvancedTab(SettingsTab):
|
|||
self.data_directory_cancel_button.hide()
|
||||
self.new_data_directory_has_files_label.hide()
|
||||
|
||||
def on_default_revert_button_clicked(self):
|
||||
"""
|
||||
Revert the default screen back to the default settings.
|
||||
"""
|
||||
self.default_file_edit.setText(':/graphics/openlp-splash-screen.png')
|
||||
self.default_file_edit.setFocus()
|
||||
|
||||
def on_alternate_rows_check_box_toggled(self, checked):
|
||||
"""
|
||||
Notify user about required restart.
|
||||
|
|
|
@ -24,7 +24,7 @@ The UI widgets for the first time wizard.
|
|||
"""
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
from openlp.core.common import translate, is_macosx, clean_button_text
|
||||
from openlp.core.common import translate, is_macosx, clean_button_text, Settings
|
||||
from openlp.core.lib import build_icon
|
||||
from openlp.core.lib.ui import add_welcome_page
|
||||
|
||||
|
@ -136,6 +136,13 @@ 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()
|
||||
|
@ -232,27 +239,39 @@ class UiFirstTimeWizard(object):
|
|||
'downloaded.'))
|
||||
self.download_label.setText(translate('OpenLP.FirstTimeWizard', 'Please wait while OpenLP downloads the '
|
||||
'resource index file...'))
|
||||
self.plugin_page.setTitle(translate('OpenLP.FirstTimeWizard', 'Activate required Plugins'))
|
||||
self.plugin_page.setSubTitle(translate('OpenLP.FirstTimeWizard', 'Select the Plugins you wish to use. '))
|
||||
self.plugin_page.setTitle(translate('OpenLP.FirstTimeWizard', 'Select parts of the program you wish to use'))
|
||||
self.plugin_page.setSubTitle(translate('OpenLP.FirstTimeWizard',
|
||||
'You can also change these settings after the Wizard.'))
|
||||
self.songs_check_box.setText(translate('OpenLP.FirstTimeWizard', 'Songs'))
|
||||
self.custom_check_box.setText(translate('OpenLP.FirstTimeWizard', 'Custom Slides'))
|
||||
self.bible_check_box.setText(translate('OpenLP.FirstTimeWizard', 'Bible'))
|
||||
self.image_check_box.setText(translate('OpenLP.FirstTimeWizard', 'Images'))
|
||||
self.presentation_check_box.setText(translate('OpenLP.FirstTimeWizard', 'Presentations'))
|
||||
self.media_check_box.setText(translate('OpenLP.FirstTimeWizard', 'Media (Audio and Video)'))
|
||||
self.remote_check_box.setText(translate('OpenLP.FirstTimeWizard', 'Allow remote access'))
|
||||
self.song_usage_check_box.setText(translate('OpenLP.FirstTimeWizard', 'Monitor Song Usage'))
|
||||
self.alert_check_box.setText(translate('OpenLP.FirstTimeWizard', 'Allow Alerts'))
|
||||
self.custom_check_box.setText(translate('OpenLP.FirstTimeWizard',
|
||||
'Custom Slides – Easier to manage than songs and they have their own'
|
||||
' list of slides'))
|
||||
self.bible_check_box.setText(translate('OpenLP.FirstTimeWizard',
|
||||
'Bibles – Import and show Bibles'))
|
||||
self.image_check_box.setText(translate('OpenLP.FirstTimeWizard',
|
||||
'Images – Show images or replace background with them'))
|
||||
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(translate('OpenLP.FirstTimeWizard', 'Remote – Control OpenLP via browser or smart'
|
||||
'phone app'))
|
||||
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',
|
||||
'Projectors – 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.'))
|
||||
self.no_internet_text = translate('OpenLP.FirstTimeWizard',
|
||||
'No Internet connection was found. The First Time Wizard needs an Internet '
|
||||
'connection in order to be able to download sample songs, Bibles and themes.'
|
||||
' Click the Finish button now to start OpenLP with initial settings and '
|
||||
' Click the %s button now to start OpenLP with initial settings and '
|
||||
'no sample data.\n\nTo re-run the First Time Wizard and import this sample '
|
||||
'data at a later time, check your Internet connection and re-run this '
|
||||
'wizard by selecting "Tools/Re-run First Time Wizard" from OpenLP.')
|
||||
'wizard by selecting "Tools/Re-run First Time Wizard" from OpenLP.') % \
|
||||
clean_button_text(first_time_wizard.buttonText(QtWidgets.QWizard.FinishButton))
|
||||
self.cancel_wizard_text = translate('OpenLP.FirstTimeWizard',
|
||||
'\n\nTo cancel the First Time Wizard completely (and not start OpenLP), '
|
||||
'click the %s button now.') % \
|
||||
|
@ -272,5 +291,14 @@ class UiFirstTimeWizard(object):
|
|||
self.progress_page.setSubTitle(translate('OpenLP.FirstTimeWizard', 'Please wait while resources are downloaded '
|
||||
'and OpenLP is configured.'))
|
||||
self.progress_label.setText(translate('OpenLP.FirstTimeWizard', 'Starting configuration process...'))
|
||||
first_time_wizard.setButtonText(QtWidgets.QWizard.CustomButton1, translate('OpenLP.FirstTimeWizard', 'Finish'))
|
||||
first_time_wizard.setButtonText(QtWidgets.QWizard.CustomButton2, translate('OpenLP.FirstTimeWizard', 'Cancel'))
|
||||
first_time_wizard.setButtonText(QtWidgets.QWizard.CustomButton1,
|
||||
clean_button_text(first_time_wizard.buttonText(QtWidgets.QWizard.FinishButton)))
|
||||
first_time_wizard.setButtonText(QtWidgets.QWizard.CustomButton2,
|
||||
clean_button_text(first_time_wizard.buttonText(QtWidgets.QWizard.CancelButton)))
|
||||
|
||||
def on_projectors_check_box_clicked(self):
|
||||
# When clicking projectors_check box, change the visibility setting for Projectors panel.
|
||||
if Settings().value('projector/show after wizard'):
|
||||
Settings().setValue('projector/show after wizard', False)
|
||||
else:
|
||||
Settings().setValue('projector/show after wizard', True)
|
||||
|
|
|
@ -26,8 +26,9 @@ import logging
|
|||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
from openlp.core.common import Registry, Settings, UiStrings, translate
|
||||
from openlp.core.lib import SettingsTab, ScreenList
|
||||
from openlp.core.common import Registry, Settings, UiStrings, translate, get_images_filter
|
||||
from openlp.core.lib import SettingsTab, ScreenList, build_icon
|
||||
from openlp.core.ui.lib.colorbutton import ColorButton
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
@ -40,6 +41,8 @@ class GeneralTab(SettingsTab):
|
|||
"""
|
||||
Initialise the general settings tab
|
||||
"""
|
||||
self.logo_file = ':/graphics/openlp-splash-screen.png'
|
||||
self.logo_background_color = '#ffffff'
|
||||
self.screens = ScreenList()
|
||||
self.icon_path = ':/icon/openlp-logo-16x16.png'
|
||||
general_translated = translate('OpenLP.GeneralTab', 'General')
|
||||
|
@ -162,6 +165,39 @@ class GeneralTab(SettingsTab):
|
|||
self.check_for_updates_check_box.setVisible(False)
|
||||
self.startup_layout.addWidget(self.check_for_updates_check_box)
|
||||
self.right_layout.addWidget(self.startup_group_box)
|
||||
# Logo
|
||||
self.logo_group_box = QtWidgets.QGroupBox(self.right_column)
|
||||
self.logo_group_box.setObjectName('logo_group_box')
|
||||
self.logo_layout = QtWidgets.QFormLayout(self.logo_group_box)
|
||||
self.logo_layout.setObjectName('logo_layout')
|
||||
self.logo_file_label = QtWidgets.QLabel(self.logo_group_box)
|
||||
self.logo_file_label.setObjectName('logo_file_label')
|
||||
self.logo_file_edit = QtWidgets.QLineEdit(self.logo_group_box)
|
||||
self.logo_file_edit.setObjectName('logo_file_edit')
|
||||
self.logo_browse_button = QtWidgets.QToolButton(self.logo_group_box)
|
||||
self.logo_browse_button.setObjectName('logo_browse_button')
|
||||
self.logo_browse_button.setIcon(build_icon(':/general/general_open.png'))
|
||||
self.logo_revert_button = QtWidgets.QToolButton(self.logo_group_box)
|
||||
self.logo_revert_button.setObjectName('logo_revert_button')
|
||||
self.logo_revert_button.setIcon(build_icon(':/general/general_revert.png'))
|
||||
self.logo_file_layout = QtWidgets.QHBoxLayout()
|
||||
self.logo_file_layout.setObjectName('logo_file_layout')
|
||||
self.logo_file_layout.addWidget(self.logo_file_edit)
|
||||
self.logo_file_layout.addWidget(self.logo_browse_button)
|
||||
self.logo_file_layout.addWidget(self.logo_revert_button)
|
||||
self.logo_layout.addRow(self.logo_file_label, self.logo_file_layout)
|
||||
self.logo_color_label = QtWidgets.QLabel(self.logo_group_box)
|
||||
self.logo_color_label.setObjectName('logo_color_label')
|
||||
self.logo_color_button = ColorButton(self.logo_group_box)
|
||||
self.logo_color_button.setObjectName('logo_color_button')
|
||||
self.logo_layout.addRow(self.logo_color_label, self.logo_color_button)
|
||||
self.logo_hide_on_startup_check_box = QtWidgets.QCheckBox(self.logo_group_box)
|
||||
self.logo_hide_on_startup_check_box.setObjectName('logo_hide_on_startup_check_box')
|
||||
self.logo_layout.addRow(self.logo_hide_on_startup_check_box)
|
||||
self.right_layout.addWidget(self.logo_group_box)
|
||||
self.logo_color_button.colorChanged.connect(self.on_logo_background_color_changed)
|
||||
self.logo_browse_button.clicked.connect(self.on_logo_browse_button_clicked)
|
||||
self.logo_revert_button.clicked.connect(self.on_logo_revert_button_clicked)
|
||||
# Application Settings
|
||||
self.settings_group_box = QtWidgets.QGroupBox(self.right_column)
|
||||
self.settings_group_box.setObjectName('settings_group_box')
|
||||
|
@ -212,6 +248,12 @@ class GeneralTab(SettingsTab):
|
|||
self.warning_check_box.setText(translate('OpenLP.GeneralTab', 'Show blank screen warning'))
|
||||
self.auto_open_check_box.setText(translate('OpenLP.GeneralTab', 'Automatically open the last service'))
|
||||
self.show_splash_check_box.setText(translate('OpenLP.GeneralTab', 'Show the splash screen'))
|
||||
self.logo_group_box.setTitle(translate('OpenLP.GeneralTab', 'Logo'))
|
||||
self.logo_color_label.setText(UiStrings().BackgroundColorColon)
|
||||
self.logo_file_label.setText(translate('OpenLP.GeneralTab', 'Logo file:'))
|
||||
self.logo_browse_button.setToolTip(translate('OpenLP.GeneralTab', 'Browse for an image file to display.'))
|
||||
self.logo_revert_button.setToolTip(translate('OpenLP.GeneralTab', 'Revert to the default OpenLP logo.'))
|
||||
self.logo_hide_on_startup_check_box.setText(translate('OpenLP.GeneralTab', 'Don\'t show logo on startup'))
|
||||
self.check_for_updates_check_box.setText(translate('OpenLP.GeneralTab', 'Check for updates to OpenLP'))
|
||||
self.settings_group_box.setTitle(translate('OpenLP.GeneralTab', 'Application Settings'))
|
||||
self.save_check_service_check_box.setText(translate('OpenLP.GeneralTab',
|
||||
|
@ -254,6 +296,10 @@ class GeneralTab(SettingsTab):
|
|||
self.warning_check_box.setChecked(settings.value('blank warning'))
|
||||
self.auto_open_check_box.setChecked(settings.value('auto open'))
|
||||
self.show_splash_check_box.setChecked(settings.value('show splash'))
|
||||
self.logo_background_color = settings.value('logo background color')
|
||||
self.logo_file_edit.setText(settings.value('logo file'))
|
||||
self.logo_hide_on_startup_check_box.setChecked(settings.value('logo hide on startup'))
|
||||
self.logo_color_button.color = self.logo_background_color
|
||||
self.check_for_updates_check_box.setChecked(settings.value('update check'))
|
||||
self.auto_preview_check_box.setChecked(settings.value('auto preview'))
|
||||
self.timeout_spin_box.setValue(settings.value('loop delay'))
|
||||
|
@ -284,6 +330,9 @@ class GeneralTab(SettingsTab):
|
|||
settings.setValue('blank warning', self.warning_check_box.isChecked())
|
||||
settings.setValue('auto open', self.auto_open_check_box.isChecked())
|
||||
settings.setValue('show splash', self.show_splash_check_box.isChecked())
|
||||
settings.setValue('logo background color', self.logo_background_color)
|
||||
settings.setValue('logo file', self.logo_file_edit.text())
|
||||
settings.setValue('logo hide on startup', self.logo_hide_on_startup_check_box.isChecked())
|
||||
settings.setValue('update check', self.check_for_updates_check_box.isChecked())
|
||||
settings.setValue('save prompt', self.save_check_service_check_box.isChecked())
|
||||
settings.setValue('auto unblank', self.auto_unblank_check_box.isChecked())
|
||||
|
@ -346,3 +395,28 @@ class GeneralTab(SettingsTab):
|
|||
Called when the width, height, x position or y position has changed.
|
||||
"""
|
||||
self.display_changed = True
|
||||
|
||||
def on_logo_browse_button_clicked(self):
|
||||
"""
|
||||
Select the logo file
|
||||
"""
|
||||
file_filters = '%s;;%s (*.*)' % (get_images_filter(), UiStrings().AllFiles)
|
||||
filename, filter_used = QtWidgets.QFileDialog.getOpenFileName(self,
|
||||
translate('OpenLP.AdvancedTab', 'Open File'), '',
|
||||
file_filters)
|
||||
if filename:
|
||||
self.logo_file_edit.setText(filename)
|
||||
self.logo_file_edit.setFocus()
|
||||
|
||||
def on_logo_revert_button_clicked(self):
|
||||
"""
|
||||
Revert the logo file back to the default setting.
|
||||
"""
|
||||
self.logo_file_edit.setText(':/graphics/openlp-splash-screen.png')
|
||||
self.logo_file_edit.setFocus()
|
||||
|
||||
def on_logo_background_color_changed(self, color):
|
||||
"""
|
||||
Select the background color for logo.
|
||||
"""
|
||||
self.logo_background_color = color
|
||||
|
|
|
@ -19,3 +19,15 @@
|
|||
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||
###############################################################################
|
||||
|
||||
from .colorbutton import ColorButton
|
||||
from .listwidgetwithdnd import ListWidgetWithDnD
|
||||
from .treewidgetwithdnd import TreeWidgetWithDnD
|
||||
from .toolbar import OpenLPToolbar
|
||||
from .dockwidget import OpenLPDockWidget
|
||||
from .wizard import OpenLPWizard, WizardStrings
|
||||
from .mediadockmanager import MediaDockManager
|
||||
from .listpreviewwidget import ListPreviewWidget
|
||||
|
||||
__all__ = ['ColorButton', 'ListPreviewWidget', 'ListWidgetWithDnD', 'OpenLPToolbar', 'OpenLPDockWidget',
|
||||
'OpenLPWizard', 'WizardStrings', 'MediaDockManager', 'ListPreviewWidget']
|
||||
|
|
|
@ -87,7 +87,7 @@ class ListPreviewWidget(QtWidgets.QTableWidget, RegistryProperties):
|
|||
height = self.viewport().width() // self.screen_ratio
|
||||
max_img_row_height = Settings().value('advanced/slide max height')
|
||||
# Adjust for row height cap if in use.
|
||||
if max_img_row_height > 0 and height > max_img_row_height:
|
||||
if isinstance(max_img_row_height, int) and max_img_row_height > 0 and height > max_img_row_height:
|
||||
height = max_img_row_height
|
||||
# Apply new height to slides
|
||||
for frame_number in range(len(self.service_item.get_frames())):
|
||||
|
@ -98,7 +98,8 @@ class ListPreviewWidget(QtWidgets.QTableWidget, RegistryProperties):
|
|||
Will scale non-image slides.
|
||||
"""
|
||||
# Only for non-text slides when row height cap in use
|
||||
if self.service_item.is_text() or Settings().value('advanced/slide max height') <= 0:
|
||||
max_img_row_height = Settings().value('advanced/slide max height')
|
||||
if self.service_item.is_text() or not isinstance(max_img_row_height, int) or max_img_row_height <= 0:
|
||||
return
|
||||
# Get and validate label widget containing slide & adjust max width
|
||||
try:
|
||||
|
@ -160,9 +161,9 @@ class ListPreviewWidget(QtWidgets.QTableWidget, RegistryProperties):
|
|||
pixmap.setDevicePixelRatio(label.devicePixelRatio())
|
||||
label.setPixmap(pixmap)
|
||||
slide_height = width // self.screen_ratio
|
||||
# Setup row height cap if in use.
|
||||
# Setup and validate row height cap if in use.
|
||||
max_img_row_height = Settings().value('advanced/slide max height')
|
||||
if max_img_row_height > 0:
|
||||
if isinstance(max_img_row_height, int) and max_img_row_height > 0:
|
||||
if slide_height > max_img_row_height:
|
||||
slide_height = max_img_row_height
|
||||
label.setMaximumWidth(max_img_row_height * self.screen_ratio)
|
||||
|
@ -194,11 +195,22 @@ class ListPreviewWidget(QtWidgets.QTableWidget, RegistryProperties):
|
|||
"""
|
||||
Switches to the given row.
|
||||
"""
|
||||
if slide >= self.slide_count():
|
||||
slide = self.slide_count() - 1
|
||||
# Scroll to next item if possible.
|
||||
if slide + 1 < self.slide_count():
|
||||
self.scrollToItem(self.item(slide + 1, 0))
|
||||
# Retrieve setting
|
||||
autoscrolling = Settings().value('advanced/autoscrolling')
|
||||
# Check if auto-scroll disabled (None) and validate value as dict containing 'dist' and 'pos'
|
||||
# 'dist' represents the slide to scroll to relative to the new slide (-1 = previous, 0 = current, 1 = next)
|
||||
# 'pos' represents the vert position of of the slide (0 = in view, 1 = top, 2 = middle, 3 = bottom)
|
||||
if not (isinstance(autoscrolling, dict) and 'dist' in autoscrolling and 'pos' in autoscrolling and
|
||||
isinstance(autoscrolling['dist'], int) and isinstance(autoscrolling['pos'], int)):
|
||||
return
|
||||
# prevent scrolling past list bounds
|
||||
scroll_to_slide = slide + autoscrolling['dist']
|
||||
if scroll_to_slide < 0:
|
||||
scroll_to_slide = 0
|
||||
if scroll_to_slide >= self.slide_count():
|
||||
scroll_to_slide = self.slide_count() - 1
|
||||
# Scroll to item if possible.
|
||||
self.scrollToItem(self.item(scroll_to_slide, 0), autoscrolling['pos'])
|
||||
self.selectRow(slide)
|
||||
|
||||
def current_slide_number(self):
|
|
@ -45,6 +45,7 @@ class WizardStrings(object):
|
|||
OS = 'OpenSong'
|
||||
OSIS = 'OSIS'
|
||||
ZEF = 'Zefania'
|
||||
SWORD = 'Sword'
|
||||
# These strings should need a good reason to be retranslated elsewhere.
|
||||
FinishedImport = translate('OpenLP.Ui', 'Finished import.')
|
||||
FormatLabel = translate('OpenLP.Ui', 'Format:')
|
||||
|
@ -111,8 +112,9 @@ class OpenLPWizard(QtWidgets.QWizard, RegistryProperties):
|
|||
def setupUi(self, image):
|
||||
"""
|
||||
Set up the wizard UI.
|
||||
:param image: path to start up image
|
||||
"""
|
||||
self.setWindowIcon(build_icon(u':/icon/openlp-logo.svg'))
|
||||
self.setWindowIcon(build_icon(':/icon/openlp-logo.svg'))
|
||||
self.setModal(True)
|
||||
self.setOptions(QtWidgets.QWizard.IndependentPages |
|
||||
QtWidgets.QWizard.NoBackButtonOnStartPage | QtWidgets.QWizard.NoBackButtonOnLastPage)
|
||||
|
@ -210,6 +212,7 @@ class OpenLPWizard(QtWidgets.QWizard, RegistryProperties):
|
|||
def on_current_id_changed(self, page_id):
|
||||
"""
|
||||
Perform necessary functions depending on which wizard page is active.
|
||||
:param page_id: current page number
|
||||
"""
|
||||
if self.with_progress_page and self.page(page_id) == self.progress_page:
|
||||
self.pre_wizard()
|
||||
|
@ -221,6 +224,7 @@ class OpenLPWizard(QtWidgets.QWizard, RegistryProperties):
|
|||
def custom_page_changed(self, page_id):
|
||||
"""
|
||||
Called when changing to a page other than the progress page
|
||||
:param page_id: current page number
|
||||
"""
|
||||
pass
|
||||
|
|
@ -254,10 +254,10 @@ class MainDisplay(OpenLPMixin, Display, RegistryProperties):
|
|||
if self.is_live:
|
||||
# Build the initial frame.
|
||||
background_color = QtGui.QColor()
|
||||
background_color.setNamedColor(Settings().value('advanced/default color'))
|
||||
background_color.setNamedColor(Settings().value('core/logo background color'))
|
||||
if not background_color.isValid():
|
||||
background_color = QtCore.Qt.white
|
||||
image_file = Settings().value('advanced/default image')
|
||||
image_file = Settings().value('core/logo file')
|
||||
splash_image = QtGui.QImage(image_file)
|
||||
self.initial_fame = QtGui.QImage(
|
||||
self.screen['size'].width(),
|
||||
|
@ -523,7 +523,9 @@ class MainDisplay(OpenLPMixin, Display, RegistryProperties):
|
|||
if not Settings().value('core/display on monitor'):
|
||||
return
|
||||
self.frame.evaluateJavaScript('show_blank("show");')
|
||||
if self.isHidden():
|
||||
# Check if setting for hiding logo on startup is enabled.
|
||||
# If it is, display should remain hidden, otherwise logo is shown. (from def setup)
|
||||
if self.isHidden() and not Settings().value('core/logo hide on startup'):
|
||||
self.setVisible(True)
|
||||
self.hide_mode = None
|
||||
# Trigger actions when display is active again.
|
||||
|
|
|
@ -38,15 +38,17 @@ from openlp.core.common import Registry, RegistryProperties, AppLocation, Langua
|
|||
check_directory_exists, translate, is_win, is_macosx, add_actions
|
||||
from openlp.core.common.actions import ActionList, CategoryOrder
|
||||
from openlp.core.common.versionchecker import get_application_version
|
||||
from openlp.core.lib import Renderer, OpenLPDockWidget, 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.ui import AboutForm, SettingsForm, ServiceManager, ThemeManager, LiveController, PluginForm, \
|
||||
MediaDockManager, ShortcutListForm, FormattingTagForm, PreviewController
|
||||
ShortcutListForm, FormattingTagForm, PreviewController
|
||||
from openlp.core.ui.firsttimeform import FirstTimeForm
|
||||
from openlp.core.ui.media import MediaController
|
||||
from openlp.core.ui.printserviceform import PrintServiceForm
|
||||
from openlp.core.ui.projector.manager import ProjectorManager
|
||||
from openlp.core.ui.lib.toolbar import OpenLPToolbar
|
||||
from openlp.core.ui.lib.dockwidget import OpenLPDockWidget
|
||||
from openlp.core.ui.lib.mediadockmanager import MediaDockManager
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
@ -638,13 +640,15 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
|
|||
self.open_cmd_line_files(self.arguments)
|
||||
elif Settings().value(self.general_settings_section + '/auto open'):
|
||||
self.service_manager_contents.load_last_file()
|
||||
# This will store currently used layout preset so it remains enabled on next startup.
|
||||
# If any panel is enabled/disabled after preset is set, this setting is not saved.
|
||||
view_mode = Settings().value('%s/view mode' % self.general_settings_section)
|
||||
if view_mode == 'default':
|
||||
if view_mode == 'default' and Settings().value('user interface/is preset layout'):
|
||||
self.mode_default_item.setChecked(True)
|
||||
elif view_mode == 'setup':
|
||||
elif view_mode == 'setup' and Settings().value('user interface/is preset layout'):
|
||||
self.set_view_mode(True, True, False, True, False, True)
|
||||
self.mode_setup_item.setChecked(True)
|
||||
elif view_mode == 'live':
|
||||
elif view_mode == 'live' and Settings().value('user interface/is preset layout'):
|
||||
self.set_view_mode(False, True, False, False, True, True)
|
||||
self.mode_live_item.setChecked(True)
|
||||
|
||||
|
@ -696,6 +700,11 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
|
|||
return
|
||||
self.application.set_busy_cursor()
|
||||
self.first_time()
|
||||
# Check if Projectors panel should be visible or not after wizard.
|
||||
if Settings().value('projector/show after wizard'):
|
||||
self.projector_manager_dock.setVisible(True)
|
||||
else:
|
||||
self.projector_manager_dock.setVisible(False)
|
||||
for plugin in self.plugin_manager.plugins:
|
||||
self.active_plugin = plugin
|
||||
old_status = self.active_plugin.status
|
||||
|
@ -1027,18 +1036,24 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
|
|||
Put OpenLP into "Default" view mode.
|
||||
"""
|
||||
self.set_view_mode(True, True, True, True, True, True, 'default')
|
||||
Settings().setValue('user interface/is preset layout', True)
|
||||
Settings().setValue('projector/show after wizard', True)
|
||||
|
||||
def on_mode_setup_item_clicked(self):
|
||||
"""
|
||||
Put OpenLP into "Setup" view mode.
|
||||
"""
|
||||
self.set_view_mode(True, True, False, True, False, True, 'setup')
|
||||
Settings().setValue('user interface/is preset layout', True)
|
||||
Settings().setValue('projector/show after wizard', True)
|
||||
|
||||
def on_mode_live_item_clicked(self):
|
||||
"""
|
||||
Put OpenLP into "Live" view mode.
|
||||
"""
|
||||
self.set_view_mode(False, True, False, False, True, True, 'live')
|
||||
Settings().setValue('user interface/is preset layout', True)
|
||||
Settings().setValue('projector/show after wizard', True)
|
||||
|
||||
def set_view_mode(self, media=True, service=True, theme=True, preview=True, live=True, projector=True, mode=''):
|
||||
"""
|
||||
|
@ -1176,24 +1191,33 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
|
|||
Toggle the visibility of the media manager
|
||||
"""
|
||||
self.media_manager_dock.setVisible(not self.media_manager_dock.isVisible())
|
||||
Settings().setValue('user interface/is preset layout', False)
|
||||
|
||||
def toggle_projector_manager(self):
|
||||
"""
|
||||
Toggle visibility of the projector manager
|
||||
"""
|
||||
self.projector_manager_dock.setVisible(not self.projector_manager_dock.isVisible())
|
||||
Settings().setValue('user interface/is preset layout', False)
|
||||
# Check/uncheck checkbox on First time wizard based on visibility of this panel.
|
||||
if not Settings().value('projector/show after wizard'):
|
||||
Settings().setValue('projector/show after wizard', True)
|
||||
else:
|
||||
Settings().setValue('projector/show after wizard', False)
|
||||
|
||||
def toggle_service_manager(self):
|
||||
"""
|
||||
Toggle the visibility of the service manager
|
||||
"""
|
||||
self.service_manager_dock.setVisible(not self.service_manager_dock.isVisible())
|
||||
Settings().setValue('user interface/is preset layout', False)
|
||||
|
||||
def toggle_theme_manager(self):
|
||||
"""
|
||||
Toggle the visibility of the theme manager
|
||||
"""
|
||||
self.theme_manager_dock.setVisible(not self.theme_manager_dock.isVisible())
|
||||
Settings().setValue('user interface/is preset layout', False)
|
||||
|
||||
def set_preview_panel_visibility(self, visible):
|
||||
"""
|
||||
|
@ -1207,6 +1231,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
|
|||
self.preview_controller.panel.setVisible(visible)
|
||||
Settings().setValue('user interface/preview panel', visible)
|
||||
self.view_preview_panel.setChecked(visible)
|
||||
Settings().setValue('user interface/is preset layout', False)
|
||||
|
||||
def set_lock_panel(self, lock):
|
||||
"""
|
||||
|
@ -1217,6 +1242,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
|
|||
self.service_manager_dock.setFeatures(QtWidgets.QDockWidget.NoDockWidgetFeatures)
|
||||
self.media_manager_dock.setFeatures(QtWidgets.QDockWidget.NoDockWidgetFeatures)
|
||||
self.projector_manager_dock.setFeatures(QtWidgets.QDockWidget.NoDockWidgetFeatures)
|
||||
self.view_mode_menu.setEnabled(False)
|
||||
self.view_media_manager_item.setEnabled(False)
|
||||
self.view_service_manager_item.setEnabled(False)
|
||||
self.view_theme_manager_item.setEnabled(False)
|
||||
|
@ -1228,6 +1254,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
|
|||
self.service_manager_dock.setFeatures(QtWidgets.QDockWidget.AllDockWidgetFeatures)
|
||||
self.media_manager_dock.setFeatures(QtWidgets.QDockWidget.AllDockWidgetFeatures)
|
||||
self.projector_manager_dock.setFeatures(QtWidgets.QDockWidget.AllDockWidgetFeatures)
|
||||
self.view_mode_menu.setEnabled(True)
|
||||
self.view_media_manager_item.setEnabled(True)
|
||||
self.view_service_manager_item.setEnabled(True)
|
||||
self.view_theme_manager_item.setEnabled(True)
|
||||
|
@ -1248,6 +1275,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
|
|||
self.live_controller.panel.setVisible(visible)
|
||||
Settings().setValue('user interface/live panel', visible)
|
||||
self.view_live_panel.setChecked(visible)
|
||||
Settings().setValue('user interface/is preset layout', False)
|
||||
|
||||
def load_settings(self):
|
||||
"""
|
||||
|
|
|
@ -60,12 +60,14 @@ class MediaInfo(object):
|
|||
"""
|
||||
file_info = None
|
||||
volume = 100
|
||||
is_flash = False
|
||||
is_background = False
|
||||
can_loop_playback = False
|
||||
length = 0
|
||||
start_time = 0
|
||||
end_time = 0
|
||||
title_track = 0
|
||||
is_playing = False
|
||||
timer = 1000
|
||||
audio_track = 0
|
||||
subtitle_track = 0
|
||||
media_type = MediaType()
|
||||
|
@ -104,15 +106,15 @@ def set_media_players(players_list, overridden_player='auto'):
|
|||
Settings().setValue('media/players', players)
|
||||
|
||||
|
||||
def parse_optical_path(input):
|
||||
def parse_optical_path(input_string):
|
||||
"""
|
||||
Split the optical path info.
|
||||
|
||||
:param input: The string to parse
|
||||
:param input_string: The string to parse
|
||||
:return: The elements extracted from the string: filename, title, audio_track, subtitle_track, start, end
|
||||
"""
|
||||
log.debug('parse_optical_path, about to parse: "%s"' % input)
|
||||
clip_info = input.split(sep=':')
|
||||
log.debug('parse_optical_path, about to parse: "%s"' % input_string)
|
||||
clip_info = input_string.split(sep=':')
|
||||
title = int(clip_info[1])
|
||||
audio_track = int(clip_info[2])
|
||||
subtitle_track = int(clip_info[3])
|
||||
|
|
|
@ -29,16 +29,21 @@ import datetime
|
|||
from PyQt5 import QtCore, QtWidgets
|
||||
|
||||
from openlp.core.common import OpenLPMixin, Registry, RegistryMixin, RegistryProperties, Settings, UiStrings, translate
|
||||
from openlp.core.lib import OpenLPToolbar, ItemCapabilities
|
||||
from openlp.core.lib import ItemCapabilities
|
||||
from openlp.core.lib.ui import critical_error_message_box
|
||||
from openlp.core.ui.media import MediaState, MediaInfo, MediaType, get_media_players, set_media_players,\
|
||||
parse_optical_path
|
||||
from openlp.core.ui.media.mediaplayer import MediaPlayer
|
||||
from openlp.core.common import AppLocation
|
||||
from openlp.core.ui import DisplayControllerType
|
||||
from openlp.core.ui.media.vendor.mediainfoWrapper import MediaInfoWrapper
|
||||
from openlp.core.ui.media.mediaplayer import MediaPlayer
|
||||
from openlp.core.ui.media import MediaState, MediaInfo, MediaType, get_media_players, set_media_players,\
|
||||
parse_optical_path
|
||||
from openlp.core.ui.lib.toolbar import OpenLPToolbar
|
||||
from openlp.core.ui.lib.dockwidget import OpenLPDockWidget
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
TICK_TIME = 200
|
||||
|
||||
|
||||
class MediaSlider(QtWidgets.QSlider):
|
||||
"""
|
||||
|
@ -51,10 +56,13 @@ class MediaSlider(QtWidgets.QSlider):
|
|||
super(MediaSlider, self).__init__(direction)
|
||||
self.manager = manager
|
||||
self.controller = controller
|
||||
self.no_matching_player = translate('MediaPlugin.MediaItem', 'File %s not supported using player %s')
|
||||
|
||||
def mouseMoveEvent(self, event):
|
||||
"""
|
||||
Override event to allow hover time to be displayed.
|
||||
|
||||
:param event: The triggering event
|
||||
"""
|
||||
time_value = QtWidgets.QStyle.sliderValueFromPosition(self.minimum(), self.maximum(), event.x(), self.width())
|
||||
self.setToolTip('%s' % datetime.timedelta(seconds=int(time_value / 1000)))
|
||||
|
@ -63,12 +71,16 @@ class MediaSlider(QtWidgets.QSlider):
|
|||
def mousePressEvent(self, event):
|
||||
"""
|
||||
Mouse Press event no new functionality
|
||||
|
||||
:param event: The triggering event
|
||||
"""
|
||||
QtWidgets.QSlider.mousePressEvent(self, event)
|
||||
|
||||
def mouseReleaseEvent(self, event):
|
||||
"""
|
||||
Set the slider position when the mouse is clicked and released on the slider.
|
||||
|
||||
:param event: The triggering event
|
||||
"""
|
||||
self.setValue(QtWidgets.QStyle.sliderValueFromPosition(self.minimum(), self.maximum(), event.x(), self.width()))
|
||||
QtWidgets.QSlider.mouseReleaseEvent(self, event)
|
||||
|
@ -96,13 +108,17 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties):
|
|||
self.display_controllers = {}
|
||||
self.current_media_players = {}
|
||||
# Timer for video state
|
||||
self.timer = QtCore.QTimer()
|
||||
self.timer.setInterval(200)
|
||||
self.live_timer = QtCore.QTimer()
|
||||
self.live_timer.setInterval(TICK_TIME)
|
||||
self.preview_timer = QtCore.QTimer()
|
||||
self.preview_timer.setInterval(TICK_TIME)
|
||||
# Signals
|
||||
self.timer.timeout.connect(self.media_state)
|
||||
self.live_timer.timeout.connect(self.media_state_live)
|
||||
self.preview_timer.timeout.connect(self.media_state_preview)
|
||||
Registry().register_function('playbackPlay', self.media_play_msg)
|
||||
Registry().register_function('playbackPause', self.media_pause_msg)
|
||||
Registry().register_function('playbackStop', self.media_stop_msg)
|
||||
Registry().register_function('playbackLoop', self.media_loop_msg)
|
||||
Registry().register_function('seek_slider', self.media_seek_msg)
|
||||
Registry().register_function('volume_slider', self.media_volume_msg)
|
||||
Registry().register_function('media_hide', self.media_hide)
|
||||
|
@ -172,8 +188,7 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties):
|
|||
log.warning('Failed to import %s on path %s', module_name, path)
|
||||
player_classes = MediaPlayer.__subclasses__()
|
||||
for player_class in player_classes:
|
||||
player = player_class(self)
|
||||
self.register_players(player)
|
||||
self.register_players(player_class(self))
|
||||
if not self.media_players:
|
||||
return False
|
||||
saved_players, overridden_player = get_media_players()
|
||||
|
@ -188,31 +203,39 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties):
|
|||
self._generate_extensions_lists()
|
||||
return True
|
||||
|
||||
def media_state(self):
|
||||
def media_state_live(self):
|
||||
"""
|
||||
Check if there is a running media Player and do updating stuff (e.g. update the UI)
|
||||
Check if there is a running Live media Player and do updating stuff (e.g. update the UI)
|
||||
"""
|
||||
if not list(self.current_media_players.keys()):
|
||||
self.timer.stop()
|
||||
display = self._define_display(self.display_controllers[DisplayControllerType.Live])
|
||||
if DisplayControllerType.Live in self.current_media_players:
|
||||
self.current_media_players[DisplayControllerType.Live].resize(display)
|
||||
self.current_media_players[DisplayControllerType.Live].update_ui(display)
|
||||
self.tick(self.display_controllers[DisplayControllerType.Live])
|
||||
if self.current_media_players[DisplayControllerType.Live].get_live_state() is not MediaState.Playing:
|
||||
self.live_timer.stop()
|
||||
else:
|
||||
any_active = False
|
||||
for source in list(self.current_media_players.keys()):
|
||||
display = self._define_display(self.display_controllers[source])
|
||||
self.current_media_players[source].resize(display)
|
||||
self.current_media_players[source].update_ui(display)
|
||||
if self.current_media_players[source].state == MediaState.Playing:
|
||||
any_active = True
|
||||
# There are still any active players - no need to stop timer.
|
||||
if any_active:
|
||||
return
|
||||
# no players are active anymore
|
||||
for source in list(self.current_media_players.keys()):
|
||||
if self.current_media_players[source].state != MediaState.Paused:
|
||||
display = self._define_display(self.display_controllers[source])
|
||||
display.controller.seek_slider.setSliderPosition(0)
|
||||
display.controller.mediabar.actions['playbackPlay'].setVisible(True)
|
||||
display.controller.mediabar.actions['playbackPause'].setVisible(False)
|
||||
self.timer.stop()
|
||||
self.live_timer.stop()
|
||||
self.media_stop(self.display_controllers[DisplayControllerType.Live])
|
||||
if self.display_controllers[DisplayControllerType.Live].media_info.can_loop_playback:
|
||||
self.media_play(self.display_controllers[DisplayControllerType.Live], True)
|
||||
|
||||
def media_state_preview(self):
|
||||
"""
|
||||
Check if there is a running Preview media Player and do updating stuff (e.g. update the UI)
|
||||
"""
|
||||
display = self._define_display(self.display_controllers[DisplayControllerType.Preview])
|
||||
if DisplayControllerType.Preview in self.current_media_players:
|
||||
self.current_media_players[DisplayControllerType.Preview].resize(display)
|
||||
self.current_media_players[DisplayControllerType.Preview].update_ui(display)
|
||||
self.tick(self.display_controllers[DisplayControllerType.Preview])
|
||||
if self.current_media_players[DisplayControllerType.Preview].get_preview_state() is not MediaState.Playing:
|
||||
self.preview_timer.stop()
|
||||
else:
|
||||
self.preview_timer.stop()
|
||||
self.media_stop(self.display_controllers[DisplayControllerType.Preview])
|
||||
if self.display_controllers[DisplayControllerType.Preview].media_info.can_loop_playback:
|
||||
self.media_play(self.display_controllers[DisplayControllerType.Preview], True)
|
||||
|
||||
def get_media_display_css(self):
|
||||
"""
|
||||
|
@ -274,6 +297,15 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties):
|
|||
icon=':/slides/media_playback_stop.png',
|
||||
tooltip=translate('OpenLP.SlideController', 'Stop playing media.'),
|
||||
triggers=controller.send_to_plugins)
|
||||
controller.mediabar.add_toolbar_action('playbackLoop', text='media_playback_loop',
|
||||
icon=':/media/media_repeat.png', checked=False,
|
||||
tooltip=translate('OpenLP.SlideController', 'Loop playing media.'),
|
||||
triggers=controller.send_to_plugins)
|
||||
controller.position_label = QtWidgets.QLabel()
|
||||
controller.position_label.setText(' 00:00 / 00:00')
|
||||
controller.position_label.setToolTip(translate('OpenLP.SlideController', 'Video timer.'))
|
||||
controller.position_label.setObjectName('position_label')
|
||||
controller.mediabar.add_toolbar_widget(controller.position_label)
|
||||
# Build the seek_slider.
|
||||
controller.seek_slider = MediaSlider(QtCore.Qt.Horizontal, self, controller)
|
||||
controller.seek_slider.setMaximum(1000)
|
||||
|
@ -297,6 +329,8 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties):
|
|||
controller.mediabar.add_toolbar_widget(controller.volume_slider)
|
||||
controller.controller_layout.addWidget(controller.mediabar)
|
||||
controller.mediabar.setVisible(False)
|
||||
if not controller.is_live:
|
||||
controller.volume_slider.setEnabled(False)
|
||||
# Signals
|
||||
controller.seek_slider.valueChanged.connect(controller.send_to_plugins)
|
||||
controller.volume_slider.valueChanged.connect(controller.send_to_plugins)
|
||||
|
@ -335,7 +369,8 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties):
|
|||
if self.current_media_players[controller.controller_type] != self.media_players['webkit']:
|
||||
controller.display.set_transparency(False)
|
||||
|
||||
def resize(self, display, player):
|
||||
@staticmethod
|
||||
def resize(display, player):
|
||||
"""
|
||||
After Mainwindow changes or Splitter moved all related media widgets have to be resized
|
||||
|
||||
|
@ -353,7 +388,6 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties):
|
|||
:param hidden: The player which is doing the playing
|
||||
:param video_behind_text: Is the video to be played behind text.
|
||||
"""
|
||||
log.debug('video')
|
||||
is_valid = False
|
||||
controller = self.display_controllers[source]
|
||||
# stop running videos
|
||||
|
@ -361,6 +395,8 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties):
|
|||
controller.media_info = MediaInfo()
|
||||
controller.media_info.volume = controller.volume_slider.value()
|
||||
controller.media_info.is_background = video_behind_text
|
||||
# background will always loop video.
|
||||
controller.media_info.can_loop_playback = video_behind_text
|
||||
controller.media_info.file_info = QtCore.QFileInfo(service_item.get_frame_path())
|
||||
display = self._define_display(controller)
|
||||
if controller.is_live:
|
||||
|
@ -373,6 +409,7 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties):
|
|||
controller)
|
||||
else:
|
||||
log.debug('video is not optical and live')
|
||||
controller.media_info.length = service_item.media_length
|
||||
is_valid = self._check_file_type(controller, display, service_item)
|
||||
display.override['theme'] = ''
|
||||
display.override['video'] = True
|
||||
|
@ -392,6 +429,7 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties):
|
|||
controller)
|
||||
else:
|
||||
log.debug('video is not optical and preview')
|
||||
controller.media_info.length = service_item.media_length
|
||||
is_valid = self._check_file_type(controller, display, service_item)
|
||||
if not is_valid:
|
||||
# Media could not be loaded correctly
|
||||
|
@ -428,26 +466,22 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties):
|
|||
|
||||
:param service_item: The ServiceItem containing the details to be played.
|
||||
"""
|
||||
controller = self.display_controllers[DisplayControllerType.Plugin]
|
||||
log.debug('media_length')
|
||||
# stop running videos
|
||||
self.media_reset(controller)
|
||||
controller.media_info = MediaInfo()
|
||||
controller.media_info.volume = 0
|
||||
controller.media_info.file_info = QtCore.QFileInfo(service_item.get_frame_path())
|
||||
display = controller.preview_display
|
||||
if not self._check_file_type(controller, display, service_item):
|
||||
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 File'),
|
||||
translate('MediaPlugin.MediaItem', 'Unsupported File'))
|
||||
critical_error_message_box(translate('MediaPlugin.MediaItem', 'Unsupported Media File'),
|
||||
translate('MediaPlugin.MediaItem', 'File %s not supported using player %s') %
|
||||
(service_item.get_frame_path(), used_players[0]))
|
||||
return False
|
||||
if not self.media_play(controller):
|
||||
critical_error_message_box(translate('MediaPlugin.MediaItem', 'Unsupported File'),
|
||||
translate('MediaPlugin.MediaItem', 'Unsupported File'))
|
||||
return False
|
||||
service_item.set_media_length(controller.media_info.length)
|
||||
self.media_stop(controller)
|
||||
log.debug('use %s controller' % self.current_media_players[controller.controller_type])
|
||||
media_data = MediaInfoWrapper.parse(service_item.get_frame_path())
|
||||
# duration returns in milli seconds
|
||||
service_item.set_media_length(media_data.tracks[0].duration)
|
||||
return True
|
||||
|
||||
def media_setup_optical(self, filename, title, audio_track, subtitle_track, start, end, display, controller):
|
||||
|
@ -458,13 +492,12 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties):
|
|||
:param title: The main/title track to play.
|
||||
:param audio_track: The audio track to play.
|
||||
:param subtitle_track: The subtitle track to play.
|
||||
:param start: Start position in miliseconds.
|
||||
:param end: End position in miliseconds.
|
||||
:param start: Start position in milliseconds.
|
||||
:param end: End position in milliseconds.
|
||||
:param display: The display to play the media.
|
||||
:param controller: The media contraoller.
|
||||
:return: True if setup succeded else False.
|
||||
:param controller: The media controller.
|
||||
:return: True if setup succeeded else False.
|
||||
"""
|
||||
log.debug('media_setup_optical')
|
||||
if controller is None:
|
||||
controller = self.display_controllers[DisplayControllerType.Plugin]
|
||||
# stop running videos
|
||||
|
@ -476,9 +509,9 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties):
|
|||
controller.media_info.media_type = MediaType.CD
|
||||
else:
|
||||
controller.media_info.media_type = MediaType.DVD
|
||||
controller.media_info.start_time = start / 1000
|
||||
controller.media_info.end_time = end / 1000
|
||||
controller.media_info.length = (end - start) / 1000
|
||||
controller.media_info.start_time = start // 1000
|
||||
controller.media_info.end_time = end // 1000
|
||||
controller.media_info.length = (end - start) // 1000
|
||||
controller.media_info.title_track = title
|
||||
controller.media_info.audio_track = audio_track
|
||||
controller.media_info.subtitle_track = subtitle_track
|
||||
|
@ -506,13 +539,13 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties):
|
|||
controller.media_info.media_type = MediaType.DVD
|
||||
return True
|
||||
|
||||
def _check_file_type(self, controller, display, service_item):
|
||||
@staticmethod
|
||||
def _get_used_players(service_item):
|
||||
"""
|
||||
Select the correct media Player type from the prioritized Player list
|
||||
Find the player for a given service item
|
||||
|
||||
:param controller: First element is the controller which should be used
|
||||
:param display: Which display to use
|
||||
:param service_item: The ServiceItem containing the details to be played.
|
||||
:param service_item: where the information is about the media and required player
|
||||
:return: player description
|
||||
"""
|
||||
used_players = get_media_players()[0]
|
||||
# If no player, we can't play
|
||||
|
@ -525,6 +558,17 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties):
|
|||
used_players = default_player
|
||||
else:
|
||||
used_players = [service_item.processor.lower()]
|
||||
return used_players
|
||||
|
||||
def _check_file_type(self, controller, display, service_item):
|
||||
"""
|
||||
Select the correct media Player type from the prioritized Player list
|
||||
|
||||
:param controller: First element is the controller which should be used
|
||||
:param display: Which display to use
|
||||
:param service_item: The ServiceItem containing the details to be played.
|
||||
"""
|
||||
used_players = self._get_used_players(service_item)
|
||||
if controller.media_info.file_info.isFile():
|
||||
suffix = '*.%s' % controller.media_info.file_info.suffix().lower()
|
||||
for title in used_players:
|
||||
|
@ -573,17 +617,15 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties):
|
|||
:param msg: First element is the controller which should be used
|
||||
:param status:
|
||||
"""
|
||||
log.debug('media_play_msg')
|
||||
self.media_play(msg[0], status)
|
||||
|
||||
def media_play(self, controller, status=True):
|
||||
def media_play(self, controller, first_time=True):
|
||||
"""
|
||||
Responds to the request to play a loaded video
|
||||
|
||||
:param controller: The controller to be played
|
||||
:param status:
|
||||
:param first_time:
|
||||
"""
|
||||
log.debug('media_play')
|
||||
controller.seek_slider.blockSignals(True)
|
||||
controller.volume_slider.blockSignals(True)
|
||||
display = self._define_display(controller)
|
||||
|
@ -595,35 +637,60 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties):
|
|||
self.media_volume(controller, 0)
|
||||
else:
|
||||
self.media_volume(controller, controller.media_info.volume)
|
||||
if status:
|
||||
if first_time:
|
||||
if not controller.media_info.is_background:
|
||||
display.frame.evaluateJavaScript('show_blank("desktop");')
|
||||
self.current_media_players[controller.controller_type].set_visible(display, True)
|
||||
# Flash needs to be played and will not AutoPlay
|
||||
if controller.media_info.is_flash:
|
||||
controller.mediabar.actions['playbackPlay'].setVisible(True)
|
||||
controller.mediabar.actions['playbackPause'].setVisible(False)
|
||||
else:
|
||||
controller.mediabar.actions['playbackPlay'].setVisible(False)
|
||||
controller.mediabar.actions['playbackPause'].setVisible(True)
|
||||
controller.mediabar.actions['playbackPlay'].setVisible(False)
|
||||
controller.mediabar.actions['playbackPause'].setVisible(True)
|
||||
controller.mediabar.actions['playbackStop'].setDisabled(False)
|
||||
if controller.is_live:
|
||||
if controller.hide_menu.defaultAction().isChecked() and not controller.media_info.is_background:
|
||||
controller.hide_menu.defaultAction().trigger()
|
||||
# Start Timer for ui updates
|
||||
if not self.timer.isActive():
|
||||
self.timer.start()
|
||||
if controller.is_live:
|
||||
if controller.hide_menu.defaultAction().isChecked() and not controller.media_info.is_background:
|
||||
controller.hide_menu.defaultAction().trigger()
|
||||
# Start Timer for ui updates
|
||||
if not self.live_timer.isActive():
|
||||
self.live_timer.start()
|
||||
else:
|
||||
# Start Timer for ui updates
|
||||
if not self.preview_timer.isActive():
|
||||
self.preview_timer.start()
|
||||
controller.seek_slider.blockSignals(False)
|
||||
controller.volume_slider.blockSignals(False)
|
||||
controller.media_info.is_playing = True
|
||||
display = self._define_display(controller)
|
||||
display.setVisible(True)
|
||||
return True
|
||||
|
||||
def tick(self, controller):
|
||||
"""
|
||||
Add a tick while the media is playing but only count if not paused
|
||||
|
||||
:param controller: The Controller to be processed
|
||||
"""
|
||||
start_again = False
|
||||
if controller.media_info.is_playing and controller.media_info.length > 0:
|
||||
if controller.media_info.timer > controller.media_info.length:
|
||||
self.media_stop(controller, True)
|
||||
if controller.media_info.can_loop_playback:
|
||||
start_again = True
|
||||
controller.media_info.timer += TICK_TIME
|
||||
seconds = controller.media_info.timer // 1000
|
||||
minutes = seconds // 60
|
||||
seconds %= 60
|
||||
total_seconds = controller.media_info.length // 1000
|
||||
total_minutes = total_seconds // 60
|
||||
total_seconds %= 60
|
||||
controller.position_label.setText(' %02d:%02d / %02d:%02d' %
|
||||
(minutes, seconds, total_minutes, total_seconds))
|
||||
if start_again:
|
||||
self.media_play(controller, True)
|
||||
|
||||
def media_pause_msg(self, msg):
|
||||
"""
|
||||
Responds to the request to pause a loaded video
|
||||
|
||||
:param msg: First element is the controller which should be used
|
||||
"""
|
||||
log.debug('media_pause_msg')
|
||||
self.media_pause(msg[0])
|
||||
|
||||
def media_pause(self, controller):
|
||||
|
@ -632,12 +699,31 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties):
|
|||
|
||||
:param controller: The Controller to be paused
|
||||
"""
|
||||
log.debug('media_pause')
|
||||
display = self._define_display(controller)
|
||||
self.current_media_players[controller.controller_type].pause(display)
|
||||
controller.mediabar.actions['playbackPlay'].setVisible(True)
|
||||
controller.mediabar.actions['playbackStop'].setDisabled(False)
|
||||
controller.mediabar.actions['playbackPause'].setVisible(False)
|
||||
if controller.controller_type in self.current_media_players:
|
||||
self.current_media_players[controller.controller_type].pause(display)
|
||||
controller.mediabar.actions['playbackPlay'].setVisible(True)
|
||||
controller.mediabar.actions['playbackStop'].setDisabled(False)
|
||||
controller.mediabar.actions['playbackPause'].setVisible(False)
|
||||
controller.media_info.is_playing = False
|
||||
|
||||
def media_loop_msg(self, msg):
|
||||
"""
|
||||
Responds to the request to loop a loaded video
|
||||
|
||||
:param msg: First element is the controller which should be used
|
||||
"""
|
||||
self.media_loop(msg[0])
|
||||
|
||||
@staticmethod
|
||||
def media_loop(controller):
|
||||
"""
|
||||
Responds to the request to loop a loaded video
|
||||
|
||||
:param controller: The controller that needs to be stopped
|
||||
"""
|
||||
controller.media_info.can_loop_playback = not controller.media_info.can_loop_playback
|
||||
controller.mediabar.actions['playbackLoop'].setChecked(controller.media_info.can_loop_playback)
|
||||
|
||||
def media_stop_msg(self, msg):
|
||||
"""
|
||||
|
@ -645,25 +731,28 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties):
|
|||
|
||||
:param msg: First element is the controller which should be used
|
||||
"""
|
||||
log.debug('media_stop_msg')
|
||||
self.media_stop(msg[0])
|
||||
|
||||
def media_stop(self, controller):
|
||||
def media_stop(self, controller, looping_background=False):
|
||||
"""
|
||||
Responds to the request to stop a loaded video
|
||||
|
||||
:param controller: The controller that needs to be stopped
|
||||
:param looping_background: The background is looping so do not blank.
|
||||
"""
|
||||
log.debug('media_stop')
|
||||
display = self._define_display(controller)
|
||||
if controller.controller_type in self.current_media_players:
|
||||
display.frame.evaluateJavaScript('show_blank("black");')
|
||||
if not looping_background:
|
||||
display.frame.evaluateJavaScript('show_blank("black");')
|
||||
self.current_media_players[controller.controller_type].stop(display)
|
||||
self.current_media_players[controller.controller_type].set_visible(display, False)
|
||||
controller.seek_slider.setSliderPosition(0)
|
||||
controller.mediabar.actions['playbackPlay'].setVisible(True)
|
||||
controller.mediabar.actions['playbackStop'].setDisabled(True)
|
||||
controller.mediabar.actions['playbackPause'].setVisible(False)
|
||||
controller.media_info.is_playing = False
|
||||
controller.media_info.timer = 1000
|
||||
controller.media_timer = 0
|
||||
|
||||
def media_volume_msg(self, msg):
|
||||
"""
|
||||
|
@ -694,7 +783,6 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties):
|
|||
:param msg: First element is the controller which should be used
|
||||
Second element is a list with the seek value as first element
|
||||
"""
|
||||
log.debug('media_seek')
|
||||
controller = msg[0]
|
||||
seek_value = msg[1][0]
|
||||
self.media_seek(controller, seek_value)
|
||||
|
@ -706,15 +794,15 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties):
|
|||
:param controller: The controller to use.
|
||||
:param seek_value: The value to set.
|
||||
"""
|
||||
log.debug('media_seek')
|
||||
display = self._define_display(controller)
|
||||
self.current_media_players[controller.controller_type].seek(display, seek_value)
|
||||
controller.media_info.timer = seek_value
|
||||
|
||||
def media_reset(self, controller):
|
||||
"""
|
||||
Responds to the request to reset a loaded video
|
||||
:param controller: The controller to use.
|
||||
"""
|
||||
log.debug('media_reset')
|
||||
self.set_controls_visible(controller, False)
|
||||
display = self._define_display(controller)
|
||||
if controller.controller_type in self.current_media_players:
|
||||
|
@ -735,7 +823,7 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties):
|
|||
return
|
||||
display = self._define_display(self.live_controller)
|
||||
if self.live_controller.controller_type in self.current_media_players and \
|
||||
self.current_media_players[self.live_controller.controller_type].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.current_media_players[self.live_controller.controller_type].set_visible(display, False)
|
||||
|
||||
|
@ -753,7 +841,7 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties):
|
|||
Registry().execute('live_display_hide', hide_mode)
|
||||
display = self._define_display(self.live_controller)
|
||||
if self.live_controller.controller_type in self.current_media_players and \
|
||||
self.current_media_players[self.live_controller.controller_type].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.current_media_players[self.live_controller.controller_type].set_visible(display, False)
|
||||
|
||||
|
@ -770,22 +858,25 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties):
|
|||
return
|
||||
display = self._define_display(self.live_controller)
|
||||
if self.live_controller.controller_type in self.current_media_players and \
|
||||
self.current_media_players[self.live_controller.controller_type].state != MediaState.Playing:
|
||||
self.current_media_players[self.live_controller.controller_type].get_live_state() != \
|
||||
MediaState.Playing:
|
||||
if self.current_media_players[self.live_controller.controller_type].play(display):
|
||||
self.current_media_players[self.live_controller.controller_type].set_visible(display, True)
|
||||
# Start Timer for ui updates
|
||||
if not self.timer.isActive():
|
||||
self.timer.start()
|
||||
if not self.live_timer.isActive():
|
||||
self.live_timer.start()
|
||||
|
||||
def finalise(self):
|
||||
"""
|
||||
Reset all the media controllers when OpenLP shuts down
|
||||
"""
|
||||
self.timer.stop()
|
||||
self.live_timer.stop()
|
||||
self.preview_timer.stop()
|
||||
for controller in self.display_controllers:
|
||||
self.media_reset(self.display_controllers[controller])
|
||||
|
||||
def _define_display(self, controller):
|
||||
@staticmethod
|
||||
def _define_display(controller):
|
||||
"""
|
||||
Extract the correct display for a given controller
|
||||
|
||||
|
|
|
@ -41,7 +41,7 @@ class MediaPlayer(RegistryProperties):
|
|||
self.is_active = False
|
||||
self.can_background = False
|
||||
self.can_folder = False
|
||||
self.state = MediaState.Off
|
||||
self.state = {0: MediaState.Off, 1: MediaState.Off}
|
||||
self.has_own_widget = False
|
||||
self.audio_extensions_list = []
|
||||
self.video_extensions_list = []
|
||||
|
@ -55,12 +55,16 @@ class MediaPlayer(RegistryProperties):
|
|||
def setup(self, display):
|
||||
"""
|
||||
Create the related widgets for the current display
|
||||
|
||||
:param display: The display to be updated.
|
||||
"""
|
||||
pass
|
||||
|
||||
def load(self, display):
|
||||
"""
|
||||
Load a new media file and check if it is valid
|
||||
|
||||
:param display: The display to be updated.
|
||||
"""
|
||||
return True
|
||||
|
||||
|
@ -68,54 +72,75 @@ class MediaPlayer(RegistryProperties):
|
|||
"""
|
||||
If the main display size or position is changed, the media widgets
|
||||
should also resized
|
||||
|
||||
:param display: The display to be updated.
|
||||
"""
|
||||
pass
|
||||
|
||||
def play(self, display):
|
||||
"""
|
||||
Starts playing of current Media File
|
||||
|
||||
:param display: The display to be updated.
|
||||
"""
|
||||
pass
|
||||
|
||||
def pause(self, display):
|
||||
"""
|
||||
Pause of current Media File
|
||||
|
||||
:param display: The display to be updated.
|
||||
"""
|
||||
pass
|
||||
|
||||
def stop(self, display):
|
||||
"""
|
||||
Stop playing of current Media File
|
||||
|
||||
:param display: The display to be updated.
|
||||
"""
|
||||
pass
|
||||
|
||||
def volume(self, display, vol):
|
||||
def volume(self, display, volume):
|
||||
"""
|
||||
Change volume of current Media File
|
||||
|
||||
:param display: The display to be updated.
|
||||
:param volume: The volume to set.
|
||||
"""
|
||||
pass
|
||||
|
||||
def seek(self, display, seek_value):
|
||||
"""
|
||||
Change playing position of current Media File
|
||||
|
||||
:param display: The display to be updated.
|
||||
:param seek_value: The where to seek to.
|
||||
"""
|
||||
pass
|
||||
|
||||
def reset(self, display):
|
||||
"""
|
||||
Remove the current loaded video
|
||||
|
||||
:param display: The display to be updated.
|
||||
"""
|
||||
pass
|
||||
|
||||
def set_visible(self, display, status):
|
||||
"""
|
||||
Show/Hide the media widgets
|
||||
|
||||
:param display: The display to be updated.
|
||||
:param status: The status to be set.
|
||||
"""
|
||||
pass
|
||||
|
||||
def update_ui(self, display):
|
||||
"""
|
||||
Do some ui related stuff (e.g. update the seek slider)
|
||||
|
||||
:param display: The display to be updated.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
@ -142,3 +167,45 @@ class MediaPlayer(RegistryProperties):
|
|||
Returns Information about the player
|
||||
"""
|
||||
return ''
|
||||
|
||||
def get_live_state(self):
|
||||
"""
|
||||
Get the state of the live player
|
||||
:return: Live state
|
||||
"""
|
||||
return self.state[0]
|
||||
|
||||
def set_live_state(self, state):
|
||||
"""
|
||||
Set the State of the Live player
|
||||
:param state: State to be set
|
||||
:return: None
|
||||
"""
|
||||
self.state[0] = state
|
||||
|
||||
def get_preview_state(self):
|
||||
"""
|
||||
Get the state of the preview player
|
||||
:return: Preview State
|
||||
"""
|
||||
return self.state[1]
|
||||
|
||||
def set_preview_state(self, state):
|
||||
"""
|
||||
Set the state of the Preview Player
|
||||
:param state: State to be set
|
||||
:return: None
|
||||
"""
|
||||
self.state[1] = state
|
||||
|
||||
def set_state(self, state, display):
|
||||
"""
|
||||
Set the State based on the display being processed
|
||||
:param state: State to be set
|
||||
:param display: Identify the Display type
|
||||
:return: None
|
||||
"""
|
||||
if display.controller.is_live:
|
||||
self.set_live_state(state)
|
||||
else:
|
||||
self.set_preview_state(state)
|
||||
|
|
|
@ -26,9 +26,10 @@ import platform
|
|||
from PyQt5 import QtCore, QtWidgets
|
||||
|
||||
from openlp.core.common import Registry, Settings, UiStrings, translate
|
||||
from openlp.core.lib import ColorButton, SettingsTab
|
||||
from openlp.core.lib import SettingsTab
|
||||
from openlp.core.lib.ui import create_button
|
||||
from openlp.core.ui.media import get_media_players, set_media_players
|
||||
from openlp.core.ui.lib.colorbutton import ColorButton
|
||||
|
||||
|
||||
class MediaQCheckBox(QtWidgets.QCheckBox):
|
||||
|
@ -133,12 +134,16 @@ class PlayerTab(SettingsTab):
|
|||
def on_background_color_changed(self, color):
|
||||
"""
|
||||
Set the background color
|
||||
|
||||
:param color: The color to be set.
|
||||
"""
|
||||
self.background_color = color
|
||||
|
||||
def on_player_check_box_changed(self, check_state):
|
||||
"""
|
||||
Add or remove players depending on their status
|
||||
|
||||
:param check_state: The requested status.
|
||||
"""
|
||||
player = self.sender().player_name
|
||||
if check_state == QtCore.Qt.Checked:
|
||||
|
|
|
@ -4,14 +4,7 @@
|
|||
###############################################################################
|
||||
# OpenLP - Open Source Lyrics Projection #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Copyright (c) 2008-2014 Raoul Snyman #
|
||||
# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan #
|
||||
# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
|
||||
# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
|
||||
# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
|
||||
# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
|
||||
# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, #
|
||||
# Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
|
||||
# Copyright (c) 2008-2016 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 #
|
||||
|
@ -124,7 +117,8 @@ class SystemPlayer(MediaPlayer):
|
|||
def load(self, display):
|
||||
"""
|
||||
Load a video into the display
|
||||
:param display:
|
||||
|
||||
:param display: The display where the media is
|
||||
"""
|
||||
log.debug('load vid in System Controller')
|
||||
controller = display.controller
|
||||
|
@ -141,93 +135,122 @@ class SystemPlayer(MediaPlayer):
|
|||
def resize(self, display):
|
||||
"""
|
||||
Resize the display
|
||||
:param display:
|
||||
|
||||
:param display: The display where the media is
|
||||
"""
|
||||
display.video_widget.resize(display.size())
|
||||
|
||||
def play(self, display):
|
||||
"""
|
||||
Play the current media item
|
||||
:param display:
|
||||
|
||||
:param display: The display where the media is
|
||||
"""
|
||||
log.info('Play the current item')
|
||||
controller = display.controller
|
||||
start_time = 0
|
||||
if display.media_player.state() != QtMultimedia.QMediaPlayer.PausedState and \
|
||||
controller.media_info.start_time > 0:
|
||||
start_time = controller.media_info.start_time
|
||||
if display.controller.is_live:
|
||||
if self.get_live_state() != QtMultimedia.QMediaPlayer.PausedState and controller.media_info.start_time > 0:
|
||||
start_time = controller.media_info.start_time
|
||||
else:
|
||||
if self.get_preview_state() != QtMultimedia.QMediaPlayer.PausedState and \
|
||||
controller.media_info.start_time > 0:
|
||||
start_time = controller.media_info.start_time
|
||||
display.media_player.play()
|
||||
if start_time > 0:
|
||||
self.seek(display, controller.media_info.start_time * 1000)
|
||||
self.volume(display, controller.media_info.volume)
|
||||
display.media_player.durationChanged.connect(functools.partial(self.set_duration, controller))
|
||||
self.state = MediaState.Playing
|
||||
self.set_state(MediaState.Playing, display)
|
||||
display.video_widget.raise_()
|
||||
return True
|
||||
|
||||
def pause(self, display):
|
||||
"""
|
||||
Pause the current media item
|
||||
|
||||
:param display: The display where the media is
|
||||
"""
|
||||
display.media_player.pause()
|
||||
if display.media_player.state() == QtMultimedia.QMediaPlayer.PausedState:
|
||||
self.state = MediaState.Paused
|
||||
if display.controller.is_live:
|
||||
if self.get_live_state() == QtMultimedia.QMediaPlayer.PausedState:
|
||||
self.set_state(MediaState.Paused, display)
|
||||
else:
|
||||
if self.get_preview_state() == QtMultimedia.QMediaPlayer.PausedState:
|
||||
self.set_state(MediaState.Paused, display)
|
||||
|
||||
def stop(self, display):
|
||||
"""
|
||||
Stop the current media item
|
||||
|
||||
:param display: The display where the media is
|
||||
"""
|
||||
display.media_player.blockSignals(True)
|
||||
display.media_player.durationChanged.disconnect()
|
||||
display.media_player.blockSignals(False)
|
||||
display.media_player.stop()
|
||||
self.set_visible(display, False)
|
||||
self.state = MediaState.Stopped
|
||||
self.set_state(MediaState.Stopped, display)
|
||||
|
||||
def volume(self, display, vol):
|
||||
def volume(self, display, volume):
|
||||
"""
|
||||
Set the volume
|
||||
|
||||
:param display: The display where the media is
|
||||
:param volume: The volume to be set
|
||||
"""
|
||||
if display.has_audio:
|
||||
display.media_player.setVolume(vol)
|
||||
display.media_player.setVolume(volume)
|
||||
|
||||
def seek(self, display, seek_value):
|
||||
"""
|
||||
Go to a particular point in the current media item
|
||||
|
||||
:param display: The display where the media is
|
||||
:param seek_value: The where to seek to
|
||||
"""
|
||||
display.media_player.setPosition(seek_value)
|
||||
|
||||
def reset(self, display):
|
||||
"""
|
||||
Reset the media player
|
||||
|
||||
:param display: The display where the media is
|
||||
"""
|
||||
display.media_player.stop()
|
||||
display.media_player.setMedia(QtMultimedia.QMediaContent())
|
||||
self.set_visible(display, False)
|
||||
display.video_widget.setVisible(False)
|
||||
self.state = MediaState.Off
|
||||
self.set_state(MediaState.Off, display)
|
||||
|
||||
def set_visible(self, display, status):
|
||||
"""
|
||||
Set the visibility of the widget
|
||||
|
||||
:param display: The display where the media is
|
||||
:param status: The visibility status to be set
|
||||
"""
|
||||
if self.has_own_widget:
|
||||
display.video_widget.setVisible(status)
|
||||
|
||||
@staticmethod
|
||||
def set_duration(controller, duration):
|
||||
controller.media_info.length = int(duration / 1000)
|
||||
controller.seek_slider.setMaximum(controller.media_info.length * 1000)
|
||||
"""
|
||||
|
||||
:param controller: the controller displaying the media
|
||||
:param duration: how long is the media
|
||||
:return:
|
||||
"""
|
||||
controller.seek_slider.setMaximum(controller.media_info.length)
|
||||
|
||||
def update_ui(self, display):
|
||||
"""
|
||||
Update the UI
|
||||
|
||||
:param display: The display where the media is
|
||||
"""
|
||||
if display.media_player.state() == QtMultimedia.QMediaPlayer.PausedState and self.state != MediaState.Paused:
|
||||
self.stop(display)
|
||||
controller = display.controller
|
||||
if controller.media_info.end_time > 0:
|
||||
if display.media_player.position() > controller.media_info.end_time * 1000:
|
||||
if display.media_player.position() > controller.media_info.end_time:
|
||||
self.stop(display)
|
||||
self.set_visible(display, False)
|
||||
if not controller.seek_slider.isSliderDown():
|
||||
|
|
|
@ -0,0 +1,140 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||
|
||||
###############################################################################
|
||||
# OpenLP - Open Source Lyrics Projection #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Copyright (c) 2008-2016 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.ui.media.mediainfo` module contains code to run mediainfo on a media file and obtain
|
||||
information related to the rwquested media.
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
from subprocess import Popen
|
||||
from tempfile import mkstemp
|
||||
|
||||
import six
|
||||
from bs4 import BeautifulSoup, NavigableString
|
||||
|
||||
ENV_DICT = os.environ
|
||||
|
||||
|
||||
class Track(object):
|
||||
|
||||
def __getattribute__(self, name):
|
||||
try:
|
||||
return object.__getattribute__(self, name)
|
||||
except:
|
||||
pass
|
||||
return None
|
||||
|
||||
def __init__(self, xml_dom_fragment):
|
||||
self.xml_dom_fragment = xml_dom_fragment
|
||||
self.track_type = xml_dom_fragment.attrs['type']
|
||||
for el in self.xml_dom_fragment.children:
|
||||
if not isinstance(el, NavigableString):
|
||||
node_name = el.name.lower().strip().strip('_')
|
||||
if node_name == 'id':
|
||||
node_name = 'track_id'
|
||||
node_value = el.string
|
||||
other_node_name = "other_%s" % node_name
|
||||
if getattr(self, node_name) is None:
|
||||
setattr(self, node_name, node_value)
|
||||
else:
|
||||
if getattr(self, other_node_name) is None:
|
||||
setattr(self, other_node_name, [node_value, ])
|
||||
else:
|
||||
getattr(self, other_node_name).append(node_value)
|
||||
|
||||
for o in [d for d in self.__dict__.keys() if d.startswith('other_')]:
|
||||
try:
|
||||
primary = o.replace('other_', '')
|
||||
setattr(self, primary, int(getattr(self, primary)))
|
||||
except:
|
||||
for v in getattr(self, o):
|
||||
try:
|
||||
current = getattr(self, primary)
|
||||
setattr(self, primary, int(v))
|
||||
getattr(self, o).append(current)
|
||||
break
|
||||
except:
|
||||
pass
|
||||
|
||||
def __repr__(self):
|
||||
return "<Track track_id='{0}', track_type='{1}'>".format(self.track_id, self.track_type)
|
||||
|
||||
def to_data(self):
|
||||
data = {}
|
||||
for k, v in six.iteritems(self.__dict__):
|
||||
if k != 'xml_dom_fragment':
|
||||
data[k] = v
|
||||
return data
|
||||
|
||||
|
||||
class MediaInfoWrapper(object):
|
||||
|
||||
def __init__(self, xml):
|
||||
self.xml_dom = xml
|
||||
xml_types = (str,) # no unicode type in python3
|
||||
if isinstance(xml, xml_types):
|
||||
self.xml_dom = MediaInfoWrapper.parse_xml_data_into_dom(xml)
|
||||
|
||||
@staticmethod
|
||||
def parse_xml_data_into_dom(xml_data):
|
||||
return BeautifulSoup(xml_data, "xml")
|
||||
|
||||
@staticmethod
|
||||
def parse(filename, environment=ENV_DICT):
|
||||
command = ["mediainfo", "-f", "--Output=XML", filename]
|
||||
fileno_out, fname_out = mkstemp(suffix=".xml", prefix="media-")
|
||||
fileno_err, fname_err = mkstemp(suffix=".err", prefix="media-")
|
||||
fp_out = os.fdopen(fileno_out, 'r+b')
|
||||
fp_err = os.fdopen(fileno_err, 'r+b')
|
||||
p = Popen(command, stdout=fp_out, stderr=fp_err, env=environment)
|
||||
p.wait()
|
||||
fp_out.seek(0)
|
||||
|
||||
xml_dom = MediaInfoWrapper.parse_xml_data_into_dom(fp_out.read())
|
||||
fp_out.close()
|
||||
fp_err.close()
|
||||
os.unlink(fname_out)
|
||||
os.unlink(fname_err)
|
||||
return MediaInfoWrapper(xml_dom)
|
||||
|
||||
def _populate_tracks(self):
|
||||
if self.xml_dom is None:
|
||||
return
|
||||
for xml_track in self.xml_dom.Mediainfo.File.find_all("track"):
|
||||
self._tracks.append(Track(xml_track))
|
||||
|
||||
@property
|
||||
def tracks(self):
|
||||
if not hasattr(self, "_tracks"):
|
||||
self._tracks = []
|
||||
if len(self._tracks) == 0:
|
||||
self._populate_tracks()
|
||||
return self._tracks
|
||||
|
||||
def to_data(self):
|
||||
data = {'tracks': []}
|
||||
for track in self.tracks:
|
||||
data['tracks'].append(track.to_data())
|
||||
return data
|
||||
|
||||
def to_json(self):
|
||||
return json.dumps(self.to_data())
|
|
@ -144,6 +144,9 @@ class VlcPlayer(MediaPlayer):
|
|||
def setup(self, display):
|
||||
"""
|
||||
Set up the media player
|
||||
|
||||
:param display: The display where the media is
|
||||
:return:
|
||||
"""
|
||||
vlc = get_vlc()
|
||||
display.vlc_widget = QtWidgets.QFrame(display)
|
||||
|
@ -186,6 +189,9 @@ class VlcPlayer(MediaPlayer):
|
|||
def load(self, display):
|
||||
"""
|
||||
Load a video into VLC
|
||||
|
||||
:param display: The display where the media is
|
||||
:return:
|
||||
"""
|
||||
vlc = get_vlc()
|
||||
log.debug('load vid in Vlc Controller')
|
||||
|
@ -214,18 +220,16 @@ class VlcPlayer(MediaPlayer):
|
|||
# parse the metadata of the file
|
||||
display.vlc_media.parse()
|
||||
self.volume(display, volume)
|
||||
# We need to set media_info.length during load because we want
|
||||
# to avoid start and stop the video twice. Once for real playback
|
||||
# and once to just get media length.
|
||||
#
|
||||
# Media plugin depends on knowing media length before playback.
|
||||
controller.media_info.length = int(display.vlc_media_player.get_media().get_duration() / 1000)
|
||||
return True
|
||||
|
||||
def media_state_wait(self, display, media_state):
|
||||
"""
|
||||
Wait for the video to change its state
|
||||
Wait no longer than 60 seconds. (loading an iso file needs a long time)
|
||||
|
||||
:param media_state: The state of the playing media
|
||||
:param display: The display where the media is
|
||||
:return:
|
||||
"""
|
||||
vlc = get_vlc()
|
||||
start = datetime.now()
|
||||
|
@ -240,25 +244,40 @@ class VlcPlayer(MediaPlayer):
|
|||
def resize(self, display):
|
||||
"""
|
||||
Resize the player
|
||||
|
||||
:param display: The display where the media is
|
||||
:return:
|
||||
"""
|
||||
display.vlc_widget.resize(display.size())
|
||||
|
||||
def play(self, display):
|
||||
"""
|
||||
Play the current item
|
||||
|
||||
:param display: The display where the media is
|
||||
:return:
|
||||
"""
|
||||
vlc = get_vlc()
|
||||
controller = display.controller
|
||||
start_time = 0
|
||||
log.debug('vlc play')
|
||||
if self.state != MediaState.Paused and controller.media_info.start_time > 0:
|
||||
start_time = controller.media_info.start_time
|
||||
if display.controller.is_live:
|
||||
if self.get_live_state() != MediaState.Paused and controller.media_info.start_time > 0:
|
||||
start_time = controller.media_info.start_time
|
||||
else:
|
||||
if self.get_preview_state() != MediaState.Paused and controller.media_info.start_time > 0:
|
||||
start_time = controller.media_info.start_time
|
||||
threading.Thread(target=display.vlc_media_player.play).start()
|
||||
if not self.media_state_wait(display, vlc.State.Playing):
|
||||
return False
|
||||
if self.state != MediaState.Paused and controller.media_info.start_time > 0:
|
||||
log.debug('vlc play, starttime set')
|
||||
start_time = controller.media_info.start_time
|
||||
if display.controller.is_live:
|
||||
if self.get_live_state() != MediaState.Paused and controller.media_info.start_time > 0:
|
||||
log.debug('vlc play, start time set')
|
||||
start_time = controller.media_info.start_time
|
||||
else:
|
||||
if self.get_preview_state() != MediaState.Paused and controller.media_info.start_time > 0:
|
||||
log.debug('vlc play, start time set')
|
||||
start_time = controller.media_info.start_time
|
||||
log.debug('mediatype: ' + str(controller.media_info.media_type))
|
||||
# Set tracks for the optical device
|
||||
if controller.media_info.media_type == MediaType.DVD:
|
||||
|
@ -279,37 +298,45 @@ class VlcPlayer(MediaPlayer):
|
|||
log.debug('vlc play, starttime set: ' + str(controller.media_info.start_time))
|
||||
start_time = controller.media_info.start_time
|
||||
controller.media_info.length = controller.media_info.end_time - controller.media_info.start_time
|
||||
else:
|
||||
controller.media_info.length = int(display.vlc_media_player.get_media().get_duration() / 1000)
|
||||
self.volume(display, controller.media_info.volume)
|
||||
if start_time > 0 and display.vlc_media_player.is_seekable():
|
||||
display.vlc_media_player.set_time(int(start_time * 1000))
|
||||
controller.seek_slider.setMaximum(controller.media_info.length * 1000)
|
||||
self.state = MediaState.Playing
|
||||
display.vlc_media_player.set_time(int(start_time))
|
||||
controller.seek_slider.setMaximum(controller.media_info.length)
|
||||
self.set_state(MediaState.Playing, display)
|
||||
display.vlc_widget.raise_()
|
||||
return True
|
||||
|
||||
def pause(self, display):
|
||||
"""
|
||||
Pause the current item
|
||||
|
||||
:param display: The display where the media is
|
||||
:return:
|
||||
"""
|
||||
vlc = get_vlc()
|
||||
if display.vlc_media.get_state() != vlc.State.Playing:
|
||||
return
|
||||
display.vlc_media_player.pause()
|
||||
if self.media_state_wait(display, vlc.State.Paused):
|
||||
self.state = MediaState.Paused
|
||||
self.set_state(MediaState.Paused, display)
|
||||
|
||||
def stop(self, display):
|
||||
"""
|
||||
Stop the current item
|
||||
|
||||
:param display: The display where the media is
|
||||
:return:
|
||||
"""
|
||||
threading.Thread(target=display.vlc_media_player.stop).start()
|
||||
self.state = MediaState.Stopped
|
||||
self.set_state(MediaState.Stopped, display)
|
||||
|
||||
def volume(self, display, vol):
|
||||
"""
|
||||
Set the volume
|
||||
|
||||
:param vol: The volume to be sets
|
||||
:param display: The display where the media is
|
||||
:return:
|
||||
"""
|
||||
if display.has_audio:
|
||||
display.vlc_media_player.audio_set_volume(vol)
|
||||
|
@ -317,6 +344,9 @@ class VlcPlayer(MediaPlayer):
|
|||
def seek(self, display, seek_value):
|
||||
"""
|
||||
Go to a particular position
|
||||
|
||||
:param seek_value: The position of where a seek goes to
|
||||
:param display: The display where the media is
|
||||
"""
|
||||
if display.controller.media_info.media_type == MediaType.CD \
|
||||
or display.controller.media_info.media_type == MediaType.DVD:
|
||||
|
@ -327,14 +357,19 @@ class VlcPlayer(MediaPlayer):
|
|||
def reset(self, display):
|
||||
"""
|
||||
Reset the player
|
||||
|
||||
:param display: The display where the media is
|
||||
"""
|
||||
display.vlc_media_player.stop()
|
||||
display.vlc_widget.setVisible(False)
|
||||
self.state = MediaState.Off
|
||||
self.set_state(MediaState.Off, display)
|
||||
|
||||
def set_visible(self, display, status):
|
||||
"""
|
||||
Set the visibility
|
||||
|
||||
:param display: The display where the media is
|
||||
:param status: The visibility status
|
||||
"""
|
||||
if self.has_own_widget:
|
||||
display.vlc_widget.setVisible(status)
|
||||
|
@ -342,6 +377,8 @@ class VlcPlayer(MediaPlayer):
|
|||
def update_ui(self, display):
|
||||
"""
|
||||
Update the UI
|
||||
|
||||
:param display: The display where the media is
|
||||
"""
|
||||
vlc = get_vlc()
|
||||
# Stop video if playback is finished.
|
||||
|
|
|
@ -99,74 +99,6 @@ VIDEO_HTML = """
|
|||
<video id="video" class="size" style="visibility:hidden" autobuffer preload></video>
|
||||
"""
|
||||
|
||||
FLASH_CSS = """
|
||||
#flash {
|
||||
z-index:5;
|
||||
}
|
||||
"""
|
||||
|
||||
FLASH_JS = """
|
||||
function getFlashMovieObject(movieName)
|
||||
{
|
||||
if (window.document[movieName]){
|
||||
return window.document[movieName];
|
||||
}
|
||||
if (document.embeds && document.embeds[movieName]){
|
||||
return document.embeds[movieName];
|
||||
}
|
||||
}
|
||||
|
||||
function show_flash(state, path, volume, variable_value){
|
||||
var text = document.getElementById('flash');
|
||||
var flashMovie = getFlashMovieObject("OpenLPFlashMovie");
|
||||
var src = "src = 'file:///" + path + "'";
|
||||
var view_parm = " wmode='opaque'" + " width='100%%'" + " height='100%%'";
|
||||
var swf_parm = " name='OpenLPFlashMovie'" + " autostart='true' loop='false' play='true'" +
|
||||
" hidden='false' swliveconnect='true' allowscriptaccess='always'" + " volume='" + volume + "'";
|
||||
|
||||
switch(state){
|
||||
case 'load':
|
||||
text.innerHTML = "<embed " + src + view_parm + swf_parm + "/>";
|
||||
flashMovie = getFlashMovieObject("OpenLPFlashMovie");
|
||||
flashMovie.Play();
|
||||
break;
|
||||
case 'play':
|
||||
flashMovie.Play();
|
||||
break;
|
||||
case 'pause':
|
||||
flashMovie.StopPlay();
|
||||
break;
|
||||
case 'stop':
|
||||
flashMovie.StopPlay();
|
||||
tempHtml = text.innerHTML;
|
||||
text.innerHTML = '';
|
||||
text.innerHTML = tempHtml;
|
||||
break;
|
||||
case 'close':
|
||||
flashMovie.StopPlay();
|
||||
text.innerHTML = '';
|
||||
break;
|
||||
case 'length':
|
||||
return flashMovie.TotalFrames();
|
||||
case 'current_time':
|
||||
return flashMovie.CurrentFrame();
|
||||
case 'seek':
|
||||
// flashMovie.GotoFrame(variable_value);
|
||||
break;
|
||||
case 'isEnded':
|
||||
//TODO check flash end
|
||||
return false;
|
||||
case 'setVisible':
|
||||
text.style.visibility = variable_value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
FLASH_HTML = """
|
||||
<div id="flash" class="size" style="visibility:hidden"></div>
|
||||
"""
|
||||
|
||||
VIDEO_EXT = ['*.3gp', '*.3gpp', '*.3g2', '*.3gpp2', '*.aac', '*.flv', '*.f4a', '*.f4b', '*.f4p', '*.f4v', '*.mov',
|
||||
'*.m4a', '*.m4b', '*.m4p', '*.m4v', '*.mkv', '*.mp4', '*.ogv', '*.webm', '*.mpg', '*.wmv', '*.mpeg',
|
||||
'*.avi', '*.swf']
|
||||
|
@ -198,23 +130,25 @@ class WebkitPlayer(MediaPlayer):
|
|||
"""
|
||||
background = QtGui.QColor(Settings().value('players/background color')).name()
|
||||
css = VIDEO_CSS % {'bgcolor': background}
|
||||
return css + FLASH_CSS
|
||||
return css
|
||||
|
||||
def get_media_display_javascript(self):
|
||||
"""
|
||||
Add javascript functions to htmlbuilder
|
||||
"""
|
||||
return VIDEO_JS + FLASH_JS
|
||||
return VIDEO_JS
|
||||
|
||||
def get_media_display_html(self):
|
||||
"""
|
||||
Add html code to htmlbuilder
|
||||
"""
|
||||
return VIDEO_HTML + FLASH_HTML
|
||||
return VIDEO_HTML
|
||||
|
||||
def setup(self, display):
|
||||
"""
|
||||
Set up the player
|
||||
|
||||
:param display: The display to be updated.
|
||||
"""
|
||||
display.web_view.resize(display.size())
|
||||
display.web_view.raise_()
|
||||
|
@ -235,6 +169,8 @@ class WebkitPlayer(MediaPlayer):
|
|||
def load(self, display):
|
||||
"""
|
||||
Load a video
|
||||
|
||||
:param display: The display to be updated.
|
||||
"""
|
||||
log.debug('load vid in Webkit Controller')
|
||||
controller = display.controller
|
||||
|
@ -249,132 +185,120 @@ class WebkitPlayer(MediaPlayer):
|
|||
else:
|
||||
loop = 'false'
|
||||
display.web_view.setVisible(True)
|
||||
if controller.media_info.file_info.suffix() == 'swf':
|
||||
controller.media_info.is_flash = True
|
||||
js = 'show_flash("load","%s");' % (path.replace('\\', '\\\\'))
|
||||
else:
|
||||
js = 'show_video("load", "%s", %s, %s);' % (path.replace('\\', '\\\\'), str(vol), loop)
|
||||
js = 'show_video("load", "%s", %s, %s);' % (path.replace('\\', '\\\\'), str(vol), loop)
|
||||
display.frame.evaluateJavaScript(js)
|
||||
return True
|
||||
|
||||
def resize(self, display):
|
||||
"""
|
||||
Resize the player
|
||||
|
||||
:param display: The display to be updated.
|
||||
"""
|
||||
display.web_view.resize(display.size())
|
||||
|
||||
def play(self, display):
|
||||
"""
|
||||
Play a video
|
||||
|
||||
:param display: The display to be updated.
|
||||
"""
|
||||
controller = display.controller
|
||||
display.web_loaded = True
|
||||
length = 0
|
||||
start_time = 0
|
||||
if self.state != MediaState.Paused and controller.media_info.start_time > 0:
|
||||
start_time = controller.media_info.start_time
|
||||
self.set_visible(display, True)
|
||||
if controller.media_info.is_flash:
|
||||
display.frame.evaluateJavaScript('show_flash("play");')
|
||||
if display.controller.is_live:
|
||||
if self.get_live_state() != MediaState.Paused and controller.media_info.start_time > 0:
|
||||
start_time = controller.media_info.start_time
|
||||
else:
|
||||
display.frame.evaluateJavaScript('show_video("play");')
|
||||
if self.get_preview_state() != MediaState.Paused and controller.media_info.start_time > 0:
|
||||
start_time = controller.media_info.start_time
|
||||
self.set_visible(display, True)
|
||||
display.frame.evaluateJavaScript('show_video("play");')
|
||||
if start_time > 0:
|
||||
self.seek(display, controller.media_info.start_time * 1000)
|
||||
# TODO add playing check and get the correct media length
|
||||
controller.media_info.length = length
|
||||
self.state = MediaState.Playing
|
||||
self.set_state(MediaState.Playing, display)
|
||||
display.web_view.raise_()
|
||||
return True
|
||||
|
||||
def pause(self, display):
|
||||
"""
|
||||
Pause a video
|
||||
|
||||
:param display: The display to be updated.
|
||||
"""
|
||||
controller = display.controller
|
||||
if controller.media_info.is_flash:
|
||||
display.frame.evaluateJavaScript('show_flash("pause");')
|
||||
else:
|
||||
display.frame.evaluateJavaScript('show_video("pause");')
|
||||
self.state = MediaState.Paused
|
||||
display.frame.evaluateJavaScript('show_video("pause");')
|
||||
self.set_state(MediaState.Paused, display)
|
||||
|
||||
def stop(self, display):
|
||||
"""
|
||||
Stop a video
|
||||
|
||||
:param display: The display to be updated.
|
||||
"""
|
||||
controller = display.controller
|
||||
if controller.media_info.is_flash:
|
||||
display.frame.evaluateJavaScript('show_flash("stop");')
|
||||
else:
|
||||
display.frame.evaluateJavaScript('show_video("stop");')
|
||||
self.state = MediaState.Stopped
|
||||
display.frame.evaluateJavaScript('show_video("stop");')
|
||||
self.set_state(MediaState.Stopped, display)
|
||||
|
||||
def volume(self, display, volume):
|
||||
"""
|
||||
Set the volume
|
||||
|
||||
:param display: The display to be updated.
|
||||
:param volume: The volume to be set.
|
||||
"""
|
||||
controller = display.controller
|
||||
# 1.0 is the highest value
|
||||
if display.has_audio:
|
||||
vol = float(volume) / float(100)
|
||||
if not controller.media_info.is_flash:
|
||||
display.frame.evaluateJavaScript('show_video(null, null, %s);' % str(vol))
|
||||
display.frame.evaluateJavaScript('show_video(null, null, %s);' % str(vol))
|
||||
|
||||
def seek(self, display, seek_value):
|
||||
"""
|
||||
Go to a position in the video
|
||||
|
||||
:param display: The display to be updated.
|
||||
:param seek_value: The value to be set.
|
||||
"""
|
||||
controller = display.controller
|
||||
if controller.media_info.is_flash:
|
||||
seek = seek_value
|
||||
display.frame.evaluateJavaScript('show_flash("seek", null, null, "%s");' % seek)
|
||||
else:
|
||||
seek = float(seek_value) / 1000
|
||||
display.frame.evaluateJavaScript('show_video("seek", null, null, null, "%f");' % seek)
|
||||
seek = float(seek_value) / 1000
|
||||
display.frame.evaluateJavaScript('show_video("seek", null, null, null, "%f");' % seek)
|
||||
|
||||
def reset(self, display):
|
||||
"""
|
||||
Reset the player
|
||||
"""
|
||||
controller = display.controller
|
||||
if controller.media_info.is_flash:
|
||||
display.frame.evaluateJavaScript('show_flash("close");')
|
||||
else:
|
||||
display.frame.evaluateJavaScript('show_video("close");')
|
||||
self.state = MediaState.Off
|
||||
|
||||
def set_visible(self, display, status):
|
||||
:param display: The display to be updated.
|
||||
"""
|
||||
display.frame.evaluateJavaScript('show_video("close");')
|
||||
self.set_state(MediaState.Off, display)
|
||||
|
||||
def set_visible(self, display, visibility):
|
||||
"""
|
||||
Set the visibility
|
||||
|
||||
:param display: The display to be updated.
|
||||
:param visibility: The visibility to be set.
|
||||
"""
|
||||
controller = display.controller
|
||||
if status:
|
||||
if visibility:
|
||||
is_visible = "visible"
|
||||
else:
|
||||
is_visible = "hidden"
|
||||
if controller.media_info.is_flash:
|
||||
display.frame.evaluateJavaScript('show_flash("setVisible", null, null, "%s");' % is_visible)
|
||||
else:
|
||||
display.frame.evaluateJavaScript('show_video("setVisible", null, null, null, "%s");' % is_visible)
|
||||
display.frame.evaluateJavaScript('show_video("setVisible", null, null, null, "%s");' % is_visible)
|
||||
|
||||
def update_ui(self, display):
|
||||
"""
|
||||
Update the UI
|
||||
|
||||
:param display: The display to be updated.
|
||||
"""
|
||||
controller = display.controller
|
||||
if controller.media_info.is_flash:
|
||||
current_time = display.frame.evaluateJavaScript('show_flash("current_time");')
|
||||
length = display.frame.evaluateJavaScript('show_flash("length");')
|
||||
else:
|
||||
if display.frame.evaluateJavaScript('show_video("isEnded");'):
|
||||
self.stop(display)
|
||||
current_time = display.frame.evaluateJavaScript('show_video("current_time");')
|
||||
# check if conversion was ok and value is not 'NaN'
|
||||
if current_time and current_time != float('inf'):
|
||||
current_time = int(current_time * 1000)
|
||||
length = display.frame.evaluateJavaScript('show_video("length");')
|
||||
# check if conversion was ok and value is not 'NaN'
|
||||
if length and length != float('inf'):
|
||||
length = int(length * 1000)
|
||||
if display.frame.evaluateJavaScript('show_video("isEnded");'):
|
||||
self.stop(display)
|
||||
current_time = display.frame.evaluateJavaScript('show_video("current_time");')
|
||||
# check if conversion was ok and value is not 'NaN'
|
||||
if current_time and current_time != float('inf'):
|
||||
current_time = int(current_time * 1000)
|
||||
length = display.frame.evaluateJavaScript('show_video("length");')
|
||||
# check if conversion was ok and value is not 'NaN'
|
||||
if length and length != float('inf'):
|
||||
length = int(length * 1000)
|
||||
if current_time and length:
|
||||
controller.media_info.length = length
|
||||
controller.seek_slider.setMaximum(length)
|
||||
|
|
|
@ -182,9 +182,10 @@ class ProjectorEditForm(QDialog, Ui_ProjectorEditForm):
|
|||
QtWidgets.QMessageBox.warning(self,
|
||||
translate('OpenLP.ProjectorEdit', 'Duplicate Name'),
|
||||
translate('OpenLP.ProjectorEdit',
|
||||
'There is already an entry with name "%s" in '
|
||||
'the database as ID "%s". <br />'
|
||||
'Please enter a different name.' % (name, record.id)))
|
||||
'There is already an entry with name "{name}" in '
|
||||
'the database as ID "{record}". <br />'
|
||||
'Please enter a different name.'.format(name=name,
|
||||
record=record.id)))
|
||||
valid = False
|
||||
return
|
||||
adx = self.ip_text.text()
|
||||
|
@ -198,17 +199,17 @@ class ProjectorEditForm(QDialog, Ui_ProjectorEditForm):
|
|||
QtWidgets.QMessageBox.warning(self,
|
||||
translate('OpenLP.ProjectorWizard', 'Duplicate IP Address'),
|
||||
translate('OpenLP.ProjectorWizard',
|
||||
'IP address "%s"<br />is already in the database as ID %s.'
|
||||
'<br /><br />Please Enter a different IP address.' %
|
||||
(adx, ip.id)))
|
||||
'IP address "{ip}"<br />is already in the database '
|
||||
'as ID {data}.<br /><br />Please Enter a different '
|
||||
'IP address.'.format(ip=adx, data=ip.id)))
|
||||
valid = False
|
||||
return
|
||||
else:
|
||||
QtWidgets.QMessageBox.warning(self,
|
||||
translate('OpenLP.ProjectorWizard', 'Invalid IP Address'),
|
||||
translate('OpenLP.ProjectorWizard',
|
||||
'IP address "%s"<br>is not a valid IP address.'
|
||||
'<br /><br />Please enter a valid IP address.' % adx))
|
||||
'IP address "{ip}"<br>is not a valid IP address.'
|
||||
'<br /><br />Please enter a valid IP address.'.format(ip=adx)))
|
||||
valid = False
|
||||
return
|
||||
port = int(self.port_text.text())
|
||||
|
@ -219,8 +220,8 @@ class ProjectorEditForm(QDialog, Ui_ProjectorEditForm):
|
|||
'Port numbers below 1000 are reserved for admin use only, '
|
||||
'<br />and port numbers above 32767 are not currently usable.'
|
||||
'<br /><br />Please enter a valid port number between '
|
||||
' 1000 and 32767.'
|
||||
'<br /><br />Default PJLink port is %s' % PJLINK_PORT))
|
||||
'1000 and 32767.<br /><br />'
|
||||
'Default PJLink port is {port}'.format(port=PJLINK_PORT)))
|
||||
valid = False
|
||||
if valid:
|
||||
self.projector.ip = self.ip_text.text()
|
||||
|
|
|
@ -35,7 +35,7 @@ from PyQt5.QtWidgets import QWidget
|
|||
|
||||
from openlp.core.common import RegistryProperties, Settings, OpenLPMixin, \
|
||||
RegistryMixin, translate
|
||||
from openlp.core.lib import OpenLPToolbar
|
||||
from openlp.core.ui.lib import OpenLPToolbar
|
||||
from openlp.core.lib.ui import create_widget_action
|
||||
from openlp.core.lib.projector import DialogSourceStyle
|
||||
from openlp.core.lib.projector.constants import *
|
||||
|
@ -344,7 +344,7 @@ class ProjectorManager(OpenLPMixin, RegistryMixin, QWidget, Ui_ProjectorManager,
|
|||
real_projector = item.data(QtCore.Qt.UserRole)
|
||||
projector_name = str(item.text())
|
||||
visible = real_projector.link.status_connect >= S_CONNECTED
|
||||
log.debug('(%s) Building menu - visible = %s' % (projector_name, visible))
|
||||
log.debug('({name}) Building menu - visible = {visible}'.format(name=projector_name, visible=visible))
|
||||
self.delete_action.setVisible(True)
|
||||
self.edit_action.setVisible(True)
|
||||
self.connect_action.setVisible(not visible)
|
||||
|
@ -394,7 +394,7 @@ class ProjectorManager(OpenLPMixin, RegistryMixin, QWidget, Ui_ProjectorManager,
|
|||
projectordb=self.projectordb,
|
||||
edit=edit)
|
||||
source = source_select_form.exec(projector.link)
|
||||
log.debug('(%s) source_select_form() returned %s' % (projector.link.ip, source))
|
||||
log.debug('({ip}) source_select_form() returned {data}'.format(ip=projector.link.ip, data=source))
|
||||
if source is not None and source > 0:
|
||||
projector.link.set_input_source(str(source))
|
||||
return
|
||||
|
@ -473,8 +473,9 @@ class ProjectorManager(OpenLPMixin, RegistryMixin, QWidget, Ui_ProjectorManager,
|
|||
return
|
||||
projector = list_item.data(QtCore.Qt.UserRole)
|
||||
msg = QtWidgets.QMessageBox()
|
||||
msg.setText(translate('OpenLP.ProjectorManager', 'Delete projector (%s) %s?') % (projector.link.ip,
|
||||
projector.link.name))
|
||||
msg.setText(translate('OpenLP.ProjectorManager',
|
||||
'Delete projector ({ip}) {name}?'.format(ip=projector.link.ip,
|
||||
name=projector.link.name)))
|
||||
msg.setInformativeText(translate('OpenLP.ProjectorManager', 'Are you sure you want to delete this projector?'))
|
||||
msg.setStandardButtons(msg.Cancel | msg.Ok)
|
||||
msg.setDefaultButton(msg.Cancel)
|
||||
|
@ -522,7 +523,7 @@ class ProjectorManager(OpenLPMixin, RegistryMixin, QWidget, Ui_ProjectorManager,
|
|||
list_item = None
|
||||
deleted = self.projectordb.delete_projector(projector.db_item)
|
||||
for item in self.projector_list:
|
||||
log.debug('New projector list - item: %s %s' % (item.link.ip, item.link.name))
|
||||
log.debug('New projector list - item: {ip} {name}'.format(ip=item.link.ip, name=item.link.name))
|
||||
|
||||
def on_disconnect_projector(self, opt=None):
|
||||
"""
|
||||
|
@ -627,53 +628,58 @@ class ProjectorManager(OpenLPMixin, RegistryMixin, QWidget, Ui_ProjectorManager,
|
|||
"""
|
||||
lwi = self.projector_list_widget.item(self.projector_list_widget.currentRow())
|
||||
projector = lwi.data(QtCore.Qt.UserRole)
|
||||
message = '<b>%s</b>: %s<BR />' % (translate('OpenLP.ProjectorManager', 'Name'),
|
||||
projector.link.name)
|
||||
message = '%s<b>%s</b>: %s<br />' % (message, translate('OpenLP.ProjectorManager', 'IP'),
|
||||
projector.link.ip)
|
||||
message = '%s<b>%s</b>: %s<br />' % (message, translate('OpenLP.ProjectorManager', 'Port'),
|
||||
projector.link.port)
|
||||
message = '%s<b>%s</b>: %s<br />' % (message, translate('OpenLP.ProjectorManager', 'Notes'),
|
||||
projector.link.notes)
|
||||
message = '%s<hr /><br >' % message
|
||||
message = '<b>{title}</b>: {data}<BR />'.format(title=translate('OpenLP.ProjectorManager', 'Name'),
|
||||
data=projector.link.name)
|
||||
message += '<b>{title}</b>: {data}<br />'.format(title=translate('OpenLP.ProjectorManager', 'IP'),
|
||||
data=projector.link.ip)
|
||||
message += '<b>{title}</b>: {data}<br />'.format(title=translate('OpenLP.ProjectorManager', 'Port'),
|
||||
data=projector.link.port)
|
||||
message += '<b>{title}</b>: {data}<br />'.format(title=translate('OpenLP.ProjectorManager', 'Notes'),
|
||||
data=projector.link.notes)
|
||||
message += '<hr /><br >'
|
||||
if projector.link.manufacturer is None:
|
||||
message = '%s%s' % (message, translate('OpenLP.ProjectorManager',
|
||||
'Projector information not available at this time.'))
|
||||
message += translate('OpenLP.ProjectorManager', 'Projector information not available at this time.')
|
||||
else:
|
||||
message = '%s<b>%s</b>: %s<BR />' % (message, translate('OpenLP.ProjectorManager', 'Projector Name'),
|
||||
projector.link.pjlink_name)
|
||||
message = '%s<b>%s</b>: %s<br />' % (message, translate('OpenLP.ProjectorManager', 'Manufacturer'),
|
||||
projector.link.manufacturer)
|
||||
message = '%s<b>%s</b>: %s<br />' % (message, translate('OpenLP.ProjectorManager', 'Model'),
|
||||
projector.link.model)
|
||||
message = '%s<b>%s</b>: %s<br /><br />' % (message, translate('OpenLP.ProjectorManager', 'Other info'),
|
||||
projector.link.other_info)
|
||||
message = '%s<b>%s</b>: %s<br />' % (message, translate('OpenLP.ProjectorManager', 'Power status'),
|
||||
ERROR_MSG[projector.link.power])
|
||||
message = '%s<b>%s</b>: %s<br />' % (message, translate('OpenLP.ProjectorManager', 'Shutter is'),
|
||||
translate('OpenLP.ProjectorManager', 'Closed')
|
||||
if projector.link.shutter else translate('OpenLP', 'Open'))
|
||||
message += '<b>{title}</b>: {data}<br />'.format(title=translate('OpenLP.ProjectorManager',
|
||||
'Projector Name'),
|
||||
data=projector.link.pjlink_name)
|
||||
message += '<b>{title}</b>: {data}<br />'.format(title=translate('OpenLP.ProjectorManager', 'Manufacturer'),
|
||||
data=projector.link.manufacturer)
|
||||
message += '<b>{title}</b>: {data}<br />'.format(title=translate('OpenLP.ProjectorManager', 'Model'),
|
||||
data=projector.link.model)
|
||||
message += '<b>{title}</b>: {data}<br /><br />'.format(title=translate('OpenLP.ProjectorManager',
|
||||
'Other info'),
|
||||
data=projector.link.other_info)
|
||||
message += '<b>{title}</b>: {data}<br />'.format(title=translate('OpenLP.ProjectorManager', 'Power status'),
|
||||
data=ERROR_MSG[projector.link.power])
|
||||
message += '<b>{title}</b>: {data}<br />'.format(title=translate('OpenLP.ProjectorManager', 'Shutter is'),
|
||||
data=translate('OpenLP.ProjectorManager', 'Closed')
|
||||
if projector.link.shutter
|
||||
else translate('OpenLP', 'Open'))
|
||||
message = '%s<b>%s</b>: %s<br />' % (message,
|
||||
translate('OpenLP.ProjectorManager', 'Current source input is'),
|
||||
projector.link.source)
|
||||
count = 1
|
||||
for item in projector.link.lamp:
|
||||
message = '%s <b>%s %s</b> (%s) %s: %s<br />' % (message,
|
||||
translate('OpenLP.ProjectorManager', 'Lamp'),
|
||||
count,
|
||||
translate('OpenLP.ProjectorManager', 'On')
|
||||
if item['On']
|
||||
else translate('OpenLP.ProjectorManager', 'Off'),
|
||||
translate('OpenLP.ProjectorManager', 'Hours'),
|
||||
item['Hours'])
|
||||
count = count + 1
|
||||
message = '%s<hr /><br />' % message
|
||||
message += '<b>{title} {count}</b> {status} '.format(title=translate('OpenLP.ProjectorManager',
|
||||
'Lamp'),
|
||||
count=count,
|
||||
status=translate('OpenLP.ProjectorManager',
|
||||
' is on')
|
||||
if item['On']
|
||||
else translate('OpenLP.ProjectorManager',
|
||||
'is off'))
|
||||
|
||||
message += '<b>{title}</b>: {hours}<br />'.format(title=translate('OpenLP.ProjectorManager', 'Hours'),
|
||||
hours=item['Hours'])
|
||||
count += 1
|
||||
message += '<hr /><br />'
|
||||
if projector.link.projector_errors is None:
|
||||
message = '%s%s' % (message, translate('OpenLP.ProjectorManager', 'No current errors or warnings'))
|
||||
message += translate('OpenLP.ProjectorManager', 'No current errors or warnings')
|
||||
else:
|
||||
message = '%s<b>%s</b>' % (message, translate('OpenLP.ProjectorManager', 'Current errors/warnings'))
|
||||
message += '<b>{data}</b>'.format(data=translate('OpenLP.ProjectorManager', 'Current errors/warnings'))
|
||||
for (key, val) in projector.link.projector_errors.items():
|
||||
message = '%s<b>%s</b>: %s<br />' % (message, key, ERROR_MSG[val])
|
||||
message += '<b>{key}</b>: {data}<br />'.format(key=key, data=ERROR_MSG[val])
|
||||
QtWidgets.QMessageBox.information(self, translate('OpenLP.ProjectorManager', 'Projector Information'), message)
|
||||
|
||||
def _add_projector(self, projector):
|
||||
|
@ -743,7 +749,7 @@ class ProjectorManager(OpenLPMixin, RegistryMixin, QWidget, Ui_ProjectorManager,
|
|||
if start:
|
||||
item.link.connect_to_host()
|
||||
for item in self.projector_list:
|
||||
log.debug('New projector list - item: (%s) %s' % (item.link.ip, item.link.name))
|
||||
log.debug('New projector list - item: ({ip}) {name}'.format(ip=item.link.ip, name=item.link.name))
|
||||
|
||||
@pyqtSlot(str)
|
||||
def add_projector_from_wizard(self, ip, opts=None):
|
||||
|
@ -753,7 +759,7 @@ class ProjectorManager(OpenLPMixin, RegistryMixin, QWidget, Ui_ProjectorManager,
|
|||
:param ip: IP address of new record item to find
|
||||
:param opts: Needed by PyQt5
|
||||
"""
|
||||
log.debug('add_projector_from_wizard(ip=%s)' % ip)
|
||||
log.debug('add_projector_from_wizard(ip={ip})'.format(ip=ip))
|
||||
item = self.projectordb.get_projector_by_ip(ip)
|
||||
self.add_projector(item)
|
||||
|
||||
|
@ -764,7 +770,7 @@ class ProjectorManager(OpenLPMixin, RegistryMixin, QWidget, Ui_ProjectorManager,
|
|||
|
||||
:param projector: Projector() instance of projector with updated information
|
||||
"""
|
||||
log.debug('edit_projector_from_wizard(ip=%s)' % projector.ip)
|
||||
log.debug('edit_projector_from_wizard(ip={ip})'.format(ip=projector.ip))
|
||||
self.old_projector.link.name = projector.name
|
||||
self.old_projector.link.ip = projector.ip
|
||||
self.old_projector.link.pin = None if projector.pin == '' else projector.pin
|
||||
|
@ -816,7 +822,9 @@ class ProjectorManager(OpenLPMixin, RegistryMixin, QWidget, Ui_ProjectorManager,
|
|||
else:
|
||||
status_code = status
|
||||
message = ERROR_MSG[status] if msg is None else msg
|
||||
log.debug('(%s) updateStatus(status=%s) message: "%s"' % (item.link.name, status_code, message))
|
||||
log.debug('({name}) updateStatus(status={status}) message: "{message}"'.format(name=item.link.name,
|
||||
status=status_code,
|
||||
message=message))
|
||||
if status in STATUS_ICONS:
|
||||
if item.status == status:
|
||||
return
|
||||
|
@ -826,14 +834,14 @@ class ProjectorManager(OpenLPMixin, RegistryMixin, QWidget, Ui_ProjectorManager,
|
|||
status_code = ERROR_STRING[status]
|
||||
elif status in STATUS_STRING:
|
||||
status_code = STATUS_STRING[status]
|
||||
log.debug('(%s) Updating icon with %s' % (item.link.name, status_code))
|
||||
log.debug('({name}) Updating icon with {code}'.format(name=item.link.name, code=status_code))
|
||||
item.widget.setIcon(item.icon)
|
||||
self.update_icons()
|
||||
|
||||
def get_toolbar_item(self, name, enabled=False, hidden=False):
|
||||
item = self.one_toolbar.findChild(QtWidgets.QAction, name)
|
||||
if item == 0:
|
||||
log.debug('No item found with name "%s"' % name)
|
||||
log.debug('No item found with name "{name}"'.format(name=name))
|
||||
return
|
||||
item.setVisible(False if hidden else True)
|
||||
item.setEnabled(True if enabled else False)
|
||||
|
@ -918,11 +926,12 @@ class ProjectorManager(OpenLPMixin, RegistryMixin, QWidget, Ui_ProjectorManager,
|
|||
|
||||
:param name: Name from QListWidgetItem
|
||||
"""
|
||||
QtWidgets.QMessageBox.warning(self, translate('OpenLP.ProjectorManager',
|
||||
'"%s" Authentication Error' % name),
|
||||
title = '"{name} {message}" '.format(name=name,
|
||||
message=translate('OpenLP.ProjectorManager', 'Authentication Error'))
|
||||
QtWidgets.QMessageBox.warning(self, title,
|
||||
'<br />There was an authentication error while trying to connect.'
|
||||
'<br /><br />Please verify your PIN setting '
|
||||
'for projector item "%s"' % name)
|
||||
'for projector item "{name}"'.format(name=name))
|
||||
|
||||
@pyqtSlot(str)
|
||||
def no_authentication_error(self, name):
|
||||
|
@ -932,11 +941,12 @@ class ProjectorManager(OpenLPMixin, RegistryMixin, QWidget, Ui_ProjectorManager,
|
|||
|
||||
:param name: Name from QListWidgetItem
|
||||
"""
|
||||
QtWidgets.QMessageBox.warning(self, translate('OpenLP.ProjectorManager',
|
||||
'"%s" No Authentication Error' % name),
|
||||
title = '"{name} {message}" '.format(name=name,
|
||||
message=translate('OpenLP.ProjectorManager', 'No Authentication Error'))
|
||||
QtWidgets.QMessageBox.warning(self, title,
|
||||
'<br />PIN is set and projector does not require authentication.'
|
||||
'<br /><br />Please verify your PIN setting '
|
||||
'for projector item "%s"' % name)
|
||||
'for projector item "{name}"'.format(name=name))
|
||||
|
||||
|
||||
class ProjectorItem(QObject):
|
||||
|
@ -972,5 +982,5 @@ def not_implemented(function):
|
|||
QtWidgets.QMessageBox.information(None,
|
||||
translate('OpenLP.ProjectorManager', 'Not Implemented Yet'),
|
||||
translate('OpenLP.ProjectorManager',
|
||||
'Function "%s"<br />has not been implemented yet.'
|
||||
'<br />Please check back again later.' % function))
|
||||
'Function "{function}"<br />has not been implemented yet.'
|
||||
'<br />Please check back again later.'.format(function=function)))
|
||||
|
|
|
@ -115,7 +115,7 @@ def Build_Tab(group, source_key, default, projector, projectordb, edit=False):
|
|||
if edit:
|
||||
for key in sourcelist:
|
||||
item = QLineEdit()
|
||||
item.setObjectName('source_key_%s' % key)
|
||||
item.setObjectName('source_key_{key}'.format(key=key))
|
||||
source_item = projectordb.get_source_by_code(code=key, projector_id=projector.db_item.id)
|
||||
if source_item is None:
|
||||
item.setText(PJLINK_DEFAULT_CODES[key])
|
||||
|
@ -161,7 +161,7 @@ def set_button_tooltip(bar):
|
|||
button.setToolTip(translate('OpenLP.SourceSelectForm',
|
||||
'Save changes and return to OpenLP'))
|
||||
else:
|
||||
log.debug('No tooltip for button {}'.format(button.text()))
|
||||
log.debug('No tooltip for button {text}'.format(text=button.text()))
|
||||
|
||||
|
||||
class FingerTabBarWidget(QTabBar):
|
||||
|
@ -359,16 +359,20 @@ class SourceSelectTabs(QDialog):
|
|||
continue
|
||||
item = self.projectordb.get_source_by_code(code=code, projector_id=projector.id)
|
||||
if item is None:
|
||||
log.debug("(%s) Adding new source text %s: %s" % (projector.ip, code, text))
|
||||
log.debug("({ip}) Adding new source text {code}: {text}".format(ip=projector.ip,
|
||||
code=code,
|
||||
text=text))
|
||||
item = ProjectorSource(projector_id=projector.id, code=code, text=text)
|
||||
else:
|
||||
item.text = text
|
||||
log.debug('(%s) Updating source code %s with text="%s"' % (projector.ip, item.code, item.text))
|
||||
log.debug('({ip}) Updating source code {code} with text="{text}"'.format(ip=projector.ip,
|
||||
code=item.code,
|
||||
text=item.text))
|
||||
self.projectordb.add_source(item)
|
||||
selected = 0
|
||||
else:
|
||||
selected = self.button_group.checkedId()
|
||||
log.debug('SourceSelectTabs().accepted() Setting source to %s' % selected)
|
||||
log.debug('SourceSelectTabs().accepted() Setting source to {selected}'.format(selected=selected))
|
||||
self.done(selected)
|
||||
|
||||
|
||||
|
@ -417,7 +421,7 @@ class SourceSelectSingle(QDialog):
|
|||
if self.edit:
|
||||
for key in keys:
|
||||
item = QLineEdit()
|
||||
item.setObjectName('source_key_%s' % key)
|
||||
item.setObjectName('source_key_{key}'.format(key=key))
|
||||
source_item = self.projectordb.get_source_by_code(code=key, projector_id=self.projector.db_item.id)
|
||||
if source_item is None:
|
||||
item.setText(PJLINK_DEFAULT_CODES[key])
|
||||
|
@ -498,14 +502,18 @@ class SourceSelectSingle(QDialog):
|
|||
continue
|
||||
item = self.projectordb.get_source_by_code(code=code, projector_id=projector.id)
|
||||
if item is None:
|
||||
log.debug("(%s) Adding new source text %s: %s" % (projector.ip, code, text))
|
||||
log.debug("({ip}) Adding new source text {code}: {text}".format(ip=projector.ip,
|
||||
code=code,
|
||||
text=text))
|
||||
item = ProjectorSource(projector_id=projector.id, code=code, text=text)
|
||||
else:
|
||||
item.text = text
|
||||
log.debug('(%s) Updating source code %s with text="%s"' % (projector.ip, item.code, item.text))
|
||||
log.debug('({ip}) Updating source code {code} with text="{text}"'.format(ip=projector.ip,
|
||||
code=item.code,
|
||||
text=item.text))
|
||||
self.projectordb.add_source(item)
|
||||
selected = 0
|
||||
else:
|
||||
selected = self.button_group.checkedId()
|
||||
log.debug('SourceSelectDialog().accepted() Setting source to %s' % selected)
|
||||
log.debug('SourceSelectDialog().accepted() Setting source to {selected}'.format(selected=selected))
|
||||
self.done(selected)
|
||||
|
|
|
@ -35,9 +35,10 @@ from PyQt5 import QtCore, QtGui, QtWidgets
|
|||
from openlp.core.common import Registry, RegistryProperties, AppLocation, Settings, ThemeLevel, OpenLPMixin, \
|
||||
RegistryMixin, check_directory_exists, UiStrings, translate, split_filename, delete_file
|
||||
from openlp.core.common.actions import ActionList, CategoryOrder
|
||||
from openlp.core.lib import OpenLPToolbar, 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.ui import ServiceNoteForm, ServiceItemEditForm, StartTimeForm
|
||||
from openlp.core.ui.lib import OpenLPToolbar
|
||||
from openlp.core.common.languagemanager import format_time
|
||||
|
||||
|
||||
|
|
|
@ -33,11 +33,14 @@ from PyQt5 import QtCore, QtGui, QtWidgets
|
|||
from openlp.core.common import Registry, RegistryProperties, Settings, SlideLimits, UiStrings, translate, \
|
||||
RegistryMixin, OpenLPMixin
|
||||
from openlp.core.common.actions import ActionList, CategoryOrder
|
||||
from openlp.core.lib import OpenLPToolbar, ItemCapabilities, ServiceItem, ImageSource, ServiceItemAction, \
|
||||
ScreenList, build_icon, build_html
|
||||
from openlp.core.lib import ItemCapabilities, ServiceItem, ImageSource, ServiceItemAction, ScreenList, build_icon, \
|
||||
build_html
|
||||
from openlp.core.lib.ui import create_action
|
||||
from openlp.core.ui.lib.toolbar import OpenLPToolbar
|
||||
from openlp.core.ui.lib.dockwidget import OpenLPDockWidget
|
||||
from openlp.core.ui.lib.listpreviewwidget import ListPreviewWidget
|
||||
from openlp.core.ui import HideMode, MainDisplay, Display, DisplayControllerType
|
||||
from openlp.core.ui.listpreviewwidget import ListPreviewWidget
|
||||
|
||||
|
||||
# Threshold which has to be trespassed to toggle.
|
||||
HIDE_MENU_THRESHOLD = 27
|
||||
|
@ -84,7 +87,7 @@ class DisplayController(QtWidgets.QWidget):
|
|||
super(DisplayController, self).__init__(parent)
|
||||
self.is_live = False
|
||||
self.display = None
|
||||
self.controller_type = DisplayControllerType.Plugin
|
||||
self.controller_type = None
|
||||
|
||||
def send_to_plugins(self, *args):
|
||||
"""
|
||||
|
|
|
@ -31,6 +31,7 @@ from openlp.core.common import Registry, RegistryProperties, UiStrings, translat
|
|||
from openlp.core.lib.theme import BackgroundType, BackgroundGradientType
|
||||
from openlp.core.lib.ui import critical_error_message_box
|
||||
from openlp.core.ui import ThemeLayoutForm
|
||||
from openlp.core.ui.lib.colorbutton import ColorButton
|
||||
from .themewizard import Ui_ThemeWizard
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
@ -147,6 +148,7 @@ class ThemeForm(QtWidgets.QWizard, Ui_ThemeWizard, RegistryProperties):
|
|||
def update_lines_text(self, lines):
|
||||
"""
|
||||
Updates the lines on a page on the wizard
|
||||
:param lines: then number of lines to be displayed
|
||||
"""
|
||||
self.main_line_count_label.setText(
|
||||
translate('OpenLP.ThemeForm', '(approximately %d lines per slide)') % int(lines))
|
||||
|
@ -186,6 +188,7 @@ class ThemeForm(QtWidgets.QWizard, Ui_ThemeWizard, RegistryProperties):
|
|||
def on_current_id_changed(self, page_id):
|
||||
"""
|
||||
Detects Page changes and updates as appropriate.
|
||||
:param page_id: current page number
|
||||
"""
|
||||
enabled = self.page(page_id) == self.area_position_page
|
||||
self.setOption(QtWidgets.QWizard.HaveCustomButton1, enabled)
|
||||
|
|
|
@ -31,11 +31,12 @@ from PyQt5 import QtCore, QtGui, QtWidgets
|
|||
|
||||
from openlp.core.common import Registry, RegistryProperties, AppLocation, Settings, OpenLPMixin, RegistryMixin, \
|
||||
check_directory_exists, UiStrings, translate, is_win, get_filesystem_encoding, delete_file
|
||||
from openlp.core.lib import FileDialog, ImageSource, OpenLPToolbar, ValidationError, get_text_file_string, build_icon, \
|
||||
from openlp.core.lib import FileDialog, ImageSource, ValidationError, get_text_file_string, build_icon, \
|
||||
check_item_selected, create_thumb, validate_thumb
|
||||
from openlp.core.lib.theme import ThemeXML, BackgroundType
|
||||
from openlp.core.lib.ui import critical_error_message_box, create_widget_action
|
||||
from openlp.core.ui import FileRenameForm, ThemeForm
|
||||
from openlp.core.ui.lib import OpenLPToolbar
|
||||
from openlp.core.common.languagemanager import get_locale_key
|
||||
|
||||
|
||||
|
@ -760,8 +761,8 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ThemeManage
|
|||
used_count = plugin.uses_theme(theme)
|
||||
if used_count:
|
||||
plugin_usage = "%s%s" % (plugin_usage, (translate('OpenLP.ThemeManager',
|
||||
'%s time(s) by %s') %
|
||||
(used_count, plugin.name)))
|
||||
'%(count)s time(s) by %(plugin)s') %
|
||||
{'count': used_count, 'plugin': plugin.name}))
|
||||
plugin_usage = "%s\n" % plugin_usage
|
||||
if plugin_usage:
|
||||
critical_error_message_box(translate('OpenLP.ThemeManager', 'Unable to delete theme'),
|
||||
|
|
|
@ -25,9 +25,10 @@ The Create/Edit theme wizard
|
|||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
from openlp.core.common import UiStrings, translate, is_macosx
|
||||
from openlp.core.lib import build_icon, ColorButton
|
||||
from openlp.core.lib import build_icon
|
||||
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.ui.lib.colorbutton import ColorButton
|
||||
|
||||
|
||||
class Ui_ThemeWizard(object):
|
||||
|
|
|
@ -23,8 +23,9 @@
|
|||
from PyQt5 import QtGui, QtWidgets
|
||||
|
||||
from openlp.core.common import Settings, UiStrings, translate
|
||||
from openlp.core.lib import ColorButton, SettingsTab
|
||||
from openlp.core.lib import SettingsTab
|
||||
from openlp.core.lib.ui import create_valign_selection_widgets
|
||||
from openlp.core.ui.lib.colorbutton import ColorButton
|
||||
|
||||
|
||||
class AlertsTab(SettingsTab):
|
||||
|
|
|
@ -27,14 +27,19 @@ import os
|
|||
import urllib.error
|
||||
|
||||
from PyQt5 import QtWidgets
|
||||
try:
|
||||
from pysword import modules
|
||||
PYSWORD_AVAILABLE = True
|
||||
except:
|
||||
PYSWORD_AVAILABLE = False
|
||||
|
||||
from openlp.core.common import AppLocation, Settings, UiStrings, translate, clean_filename
|
||||
from openlp.core.lib.db import delete_database
|
||||
from openlp.core.lib.ui import critical_error_message_box
|
||||
from openlp.core.ui.wizard import OpenLPWizard, WizardStrings
|
||||
from openlp.core.ui.lib.wizard import OpenLPWizard, WizardStrings
|
||||
from openlp.core.common.languagemanager import get_locale_key
|
||||
from openlp.plugins.bibles.lib.manager import BibleFormat
|
||||
from openlp.plugins.bibles.lib.db import BiblesResourcesDB, clean_filename
|
||||
from openlp.plugins.bibles.lib.db import clean_filename
|
||||
from openlp.plugins.bibles.lib.http import CWExtract, BGExtract, BSExtract
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
@ -94,6 +99,19 @@ class BibleImportForm(OpenLPWizard):
|
|||
self.manager.set_process_dialog(self)
|
||||
self.restart()
|
||||
self.select_stack.setCurrentIndex(0)
|
||||
if PYSWORD_AVAILABLE:
|
||||
self.pysword_folder_modules = modules.SwordModules()
|
||||
try:
|
||||
self.pysword_folder_modules_json = self.pysword_folder_modules.parse_modules()
|
||||
except FileNotFoundError:
|
||||
log.debug('No installed SWORD modules found in the default location')
|
||||
self.sword_bible_combo_box.clear()
|
||||
return
|
||||
bible_keys = self.pysword_folder_modules_json.keys()
|
||||
for key in bible_keys:
|
||||
self.sword_bible_combo_box.addItem(self.pysword_folder_modules_json[key]['description'], key)
|
||||
else:
|
||||
self.sword_tab_widget.setDisabled(True)
|
||||
|
||||
def custom_signals(self):
|
||||
"""
|
||||
|
@ -106,6 +124,8 @@ class BibleImportForm(OpenLPWizard):
|
|||
self.open_song_browse_button.clicked.connect(self.on_open_song_browse_button_clicked)
|
||||
self.zefania_browse_button.clicked.connect(self.on_zefania_browse_button_clicked)
|
||||
self.web_update_button.clicked.connect(self.on_web_update_button_clicked)
|
||||
self.sword_browse_button.clicked.connect(self.on_sword_browse_button_clicked)
|
||||
self.sword_zipbrowse_button.clicked.connect(self.on_sword_zipbrowse_button_clicked)
|
||||
|
||||
def add_custom_pages(self):
|
||||
"""
|
||||
|
@ -121,7 +141,7 @@ class BibleImportForm(OpenLPWizard):
|
|||
self.format_label = QtWidgets.QLabel(self.select_page)
|
||||
self.format_label.setObjectName('FormatLabel')
|
||||
self.format_combo_box = QtWidgets.QComboBox(self.select_page)
|
||||
self.format_combo_box.addItems(['', '', '', '', ''])
|
||||
self.format_combo_box.addItems(['', '', '', '', '', ''])
|
||||
self.format_combo_box.setObjectName('FormatComboBox')
|
||||
self.format_layout.addRow(self.format_label, self.format_combo_box)
|
||||
self.spacer = QtWidgets.QSpacerItem(10, 0, QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Minimum)
|
||||
|
@ -275,6 +295,64 @@ class BibleImportForm(OpenLPWizard):
|
|||
self.zefania_layout.addRow(self.zefania_file_label, self.zefania_file_layout)
|
||||
self.zefania_layout.setItem(5, QtWidgets.QFormLayout.LabelRole, self.spacer)
|
||||
self.select_stack.addWidget(self.zefania_widget)
|
||||
self.sword_widget = QtWidgets.QWidget(self.select_page)
|
||||
self.sword_widget.setObjectName('SwordWidget')
|
||||
self.sword_layout = QtWidgets.QVBoxLayout(self.sword_widget)
|
||||
self.sword_layout.setObjectName('SwordLayout')
|
||||
self.sword_tab_widget = QtWidgets.QTabWidget(self.sword_widget)
|
||||
self.sword_tab_widget.setObjectName('SwordTabWidget')
|
||||
self.sword_folder_tab = QtWidgets.QWidget(self.sword_tab_widget)
|
||||
self.sword_folder_tab.setObjectName('SwordFolderTab')
|
||||
self.sword_folder_tab_layout = QtWidgets.QGridLayout(self.sword_folder_tab)
|
||||
self.sword_folder_tab_layout.setObjectName('SwordTabFolderLayout')
|
||||
self.sword_folder_label = QtWidgets.QLabel(self.sword_folder_tab)
|
||||
self.sword_folder_label.setObjectName('SwordSourceLabel')
|
||||
self.sword_folder_tab_layout.addWidget(self.sword_folder_label, 0, 0)
|
||||
self.sword_folder_label.setObjectName('SwordFolderLabel')
|
||||
self.sword_folder_edit = QtWidgets.QLineEdit(self.sword_folder_tab)
|
||||
self.sword_folder_edit.setObjectName('SwordFolderEdit')
|
||||
self.sword_browse_button = QtWidgets.QToolButton(self.sword_folder_tab)
|
||||
self.sword_browse_button.setIcon(self.open_icon)
|
||||
self.sword_browse_button.setObjectName('SwordBrowseButton')
|
||||
self.sword_folder_tab_layout.addWidget(self.sword_folder_edit, 0, 1)
|
||||
self.sword_folder_tab_layout.addWidget(self.sword_browse_button, 0, 2)
|
||||
self.sword_bible_label = QtWidgets.QLabel(self.sword_folder_tab)
|
||||
self.sword_bible_label.setObjectName('SwordBibleLabel')
|
||||
self.sword_folder_tab_layout.addWidget(self.sword_bible_label, 1, 0)
|
||||
self.sword_bible_combo_box = QtWidgets.QComboBox(self.sword_folder_tab)
|
||||
self.sword_bible_combo_box.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToContents)
|
||||
self.sword_bible_combo_box.setInsertPolicy(QtWidgets.QComboBox.InsertAlphabetically)
|
||||
self.sword_bible_combo_box.setObjectName('SwordBibleComboBox')
|
||||
self.sword_folder_tab_layout.addWidget(self.sword_bible_combo_box, 1, 1)
|
||||
self.sword_tab_widget.addTab(self.sword_folder_tab, '')
|
||||
self.sword_zip_tab = QtWidgets.QWidget(self.sword_tab_widget)
|
||||
self.sword_zip_tab.setObjectName('SwordZipTab')
|
||||
self.sword_zip_layout = QtWidgets.QGridLayout(self.sword_zip_tab)
|
||||
self.sword_zip_layout.setObjectName('SwordZipLayout')
|
||||
self.sword_zipfile_label = QtWidgets.QLabel(self.sword_zip_tab)
|
||||
self.sword_zipfile_label.setObjectName('SwordZipFileLabel')
|
||||
self.sword_zipfile_edit = QtWidgets.QLineEdit(self.sword_zip_tab)
|
||||
self.sword_zipfile_edit.setObjectName('SwordZipFileEdit')
|
||||
self.sword_zipbrowse_button = QtWidgets.QToolButton(self.sword_zip_tab)
|
||||
self.sword_zipbrowse_button.setIcon(self.open_icon)
|
||||
self.sword_zipbrowse_button.setObjectName('SwordZipBrowseButton')
|
||||
self.sword_zipbible_label = QtWidgets.QLabel(self.sword_folder_tab)
|
||||
self.sword_zipbible_label.setObjectName('SwordZipBibleLabel')
|
||||
self.sword_zipbible_combo_box = QtWidgets.QComboBox(self.sword_zip_tab)
|
||||
self.sword_zipbible_combo_box.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToContents)
|
||||
self.sword_zipbible_combo_box.setInsertPolicy(QtWidgets.QComboBox.InsertAlphabetically)
|
||||
self.sword_zipbible_combo_box.setObjectName('SwordZipBibleComboBox')
|
||||
self.sword_zip_layout.addWidget(self.sword_zipfile_label, 0, 0)
|
||||
self.sword_zip_layout.addWidget(self.sword_zipfile_edit, 0, 1)
|
||||
self.sword_zip_layout.addWidget(self.sword_zipbrowse_button, 0, 2)
|
||||
self.sword_zip_layout.addWidget(self.sword_zipbible_label, 1, 0)
|
||||
self.sword_zip_layout.addWidget(self.sword_zipbible_combo_box, 1, 1)
|
||||
self.sword_tab_widget.addTab(self.sword_zip_tab, '')
|
||||
self.sword_layout.addWidget(self.sword_tab_widget)
|
||||
self.sword_disabled_label = QtWidgets.QLabel(self.sword_widget)
|
||||
self.sword_disabled_label.setObjectName('SwordDisabledLabel')
|
||||
self.sword_layout.addWidget(self.sword_disabled_label)
|
||||
self.select_stack.addWidget(self.sword_widget)
|
||||
self.select_page_layout.addLayout(self.select_stack)
|
||||
self.addPage(self.select_page)
|
||||
# License Page
|
||||
|
@ -323,6 +401,7 @@ class BibleImportForm(OpenLPWizard):
|
|||
self.format_combo_box.setItemText(BibleFormat.WebDownload, translate('BiblesPlugin.ImportWizardForm',
|
||||
'Web Download'))
|
||||
self.format_combo_box.setItemText(BibleFormat.Zefania, WizardStrings.ZEF)
|
||||
self.format_combo_box.setItemText(BibleFormat.SWORD, WizardStrings.SWORD)
|
||||
self.osis_file_label.setText(translate('BiblesPlugin.ImportWizardForm', 'Bible file:'))
|
||||
self.csv_books_label.setText(translate('BiblesPlugin.ImportWizardForm', 'Books file:'))
|
||||
self.csv_verses_label.setText(translate('BiblesPlugin.ImportWizardForm', 'Verses file:'))
|
||||
|
@ -346,6 +425,22 @@ class BibleImportForm(OpenLPWizard):
|
|||
self.web_tab_widget.setTabText(
|
||||
self.web_tab_widget.indexOf(self.web_proxy_tab), translate('BiblesPlugin.ImportWizardForm',
|
||||
'Proxy Server (Optional)'))
|
||||
self.sword_bible_label.setText(translate('BiblesPlugin.ImportWizardForm', 'Bibles:'))
|
||||
self.sword_folder_label.setText(translate('BiblesPlugin.ImportWizardForm', 'SWORD data folder:'))
|
||||
self.sword_zipfile_label.setText(translate('BiblesPlugin.ImportWizardForm', 'SWORD zip-file:'))
|
||||
self.sword_folder_edit.setPlaceholderText(translate('BiblesPlugin.ImportWizardForm',
|
||||
'Defaults to the standard SWORD data folder'))
|
||||
self.sword_zipbible_label.setText(translate('BiblesPlugin.ImportWizardForm', 'Bibles:'))
|
||||
self.sword_tab_widget.setTabText(self.sword_tab_widget.indexOf(self.sword_folder_tab),
|
||||
translate('BiblesPlugin.ImportWizardForm', 'Import from folder'))
|
||||
self.sword_tab_widget.setTabText(self.sword_tab_widget.indexOf(self.sword_zip_tab),
|
||||
translate('BiblesPlugin.ImportWizardForm', 'Import from Zip-file'))
|
||||
if PYSWORD_AVAILABLE:
|
||||
self.sword_disabled_label.setText('')
|
||||
else:
|
||||
self.sword_disabled_label.setText(translate('BiblesPlugin.ImportWizardForm',
|
||||
'To import SWORD bibles the pysword python module must be '
|
||||
'installed. Please read the manual for instructions.'))
|
||||
self.license_details_page.setTitle(
|
||||
translate('BiblesPlugin.ImportWizardForm', 'License Details'))
|
||||
self.license_details_page.setSubTitle(translate('BiblesPlugin.ImportWizardForm',
|
||||
|
@ -374,6 +469,9 @@ class BibleImportForm(OpenLPWizard):
|
|||
if self.currentPage() == self.welcome_page:
|
||||
return True
|
||||
elif self.currentPage() == self.select_page:
|
||||
self.version_name_edit.clear()
|
||||
self.permissions_edit.clear()
|
||||
self.copyright_edit.clear()
|
||||
if self.field('source_format') == BibleFormat.OSIS:
|
||||
if not self.field('osis_location'):
|
||||
critical_error_message_box(UiStrings().NFSs, WizardStrings.YouSpecifyFile % WizardStrings.OSIS)
|
||||
|
@ -410,6 +508,31 @@ class BibleImportForm(OpenLPWizard):
|
|||
return False
|
||||
else:
|
||||
self.version_name_edit.setText(self.web_translation_combo_box.currentText())
|
||||
elif self.field('source_format') == BibleFormat.SWORD:
|
||||
# Test the SWORD tab that is currently active
|
||||
if self.sword_tab_widget.currentIndex() == self.sword_tab_widget.indexOf(self.sword_folder_tab):
|
||||
if not self.field('sword_folder_path') and self.sword_bible_combo_box.count() == 0:
|
||||
critical_error_message_box(UiStrings().NFSs,
|
||||
WizardStrings.YouSpecifyFolder % WizardStrings.SWORD)
|
||||
self.sword_folder_edit.setFocus()
|
||||
return False
|
||||
key = self.sword_bible_combo_box.itemData(self.sword_bible_combo_box.currentIndex())
|
||||
if 'description' in self.pysword_folder_modules_json[key]:
|
||||
self.version_name_edit.setText(self.pysword_folder_modules_json[key]['description'])
|
||||
if 'distributionlicense' in self.pysword_folder_modules_json[key]:
|
||||
self.permissions_edit.setText(self.pysword_folder_modules_json[key]['distributionlicense'])
|
||||
if 'copyright' in self.pysword_folder_modules_json[key]:
|
||||
self.copyright_edit.setText(self.pysword_folder_modules_json[key]['copyright'])
|
||||
elif self.sword_tab_widget.currentIndex() == self.sword_tab_widget.indexOf(self.sword_zip_tab):
|
||||
if not self.field('sword_zip_path'):
|
||||
critical_error_message_box(UiStrings().NFSs, WizardStrings.YouSpecifyFile % WizardStrings.SWORD)
|
||||
self.sword_zipfile_edit.setFocus()
|
||||
return False
|
||||
key = self.sword_zipbible_combo_box.itemData(self.sword_zipbible_combo_box.currentIndex())
|
||||
if 'description' in self.pysword_zip_modules_json[key]:
|
||||
self.version_name_edit.setText(self.pysword_zip_modules_json[key]['description'])
|
||||
if 'distributionlicense' in self.pysword_zip_modules_json[key]:
|
||||
self.permissions_edit.setText(self.pysword_zip_modules_json[key]['distributionlicense'])
|
||||
return True
|
||||
elif self.currentPage() == self.license_details_page:
|
||||
license_version = self.field('license_version')
|
||||
|
@ -531,6 +654,40 @@ class BibleImportForm(OpenLPWizard):
|
|||
self.web_update_button.setEnabled(True)
|
||||
self.web_progress_bar.setVisible(False)
|
||||
|
||||
def on_sword_browse_button_clicked(self):
|
||||
"""
|
||||
Show the file open dialog for the SWORD folder.
|
||||
"""
|
||||
self.get_folder(WizardStrings.OpenTypeFolder % WizardStrings.SWORD, self.sword_folder_edit,
|
||||
'last directory import')
|
||||
if self.sword_folder_edit.text():
|
||||
try:
|
||||
self.pysword_folder_modules = modules.SwordModules(self.sword_folder_edit.text())
|
||||
self.pysword_folder_modules_json = self.pysword_folder_modules.parse_modules()
|
||||
bible_keys = self.pysword_folder_modules_json.keys()
|
||||
self.sword_bible_combo_box.clear()
|
||||
for key in bible_keys:
|
||||
self.sword_bible_combo_box.addItem(self.pysword_folder_modules_json[key]['description'], key)
|
||||
except:
|
||||
self.sword_bible_combo_box.clear()
|
||||
|
||||
def on_sword_zipbrowse_button_clicked(self):
|
||||
"""
|
||||
Show the file open dialog for a SWORD zip-file.
|
||||
"""
|
||||
self.get_file_name(WizardStrings.OpenTypeFile % WizardStrings.SWORD, self.sword_zipfile_edit,
|
||||
'last directory import')
|
||||
if self.sword_zipfile_edit.text():
|
||||
try:
|
||||
self.pysword_zip_modules = modules.SwordModules(self.sword_zipfile_edit.text())
|
||||
self.pysword_zip_modules_json = self.pysword_zip_modules.parse_modules()
|
||||
bible_keys = self.pysword_zip_modules_json.keys()
|
||||
self.sword_zipbible_combo_box.clear()
|
||||
for key in bible_keys:
|
||||
self.sword_zipbible_combo_box.addItem(self.pysword_zip_modules_json[key]['description'], key)
|
||||
except:
|
||||
self.sword_zipbible_combo_box.clear()
|
||||
|
||||
def register_fields(self):
|
||||
"""
|
||||
Register the bible import wizard fields.
|
||||
|
@ -543,6 +700,8 @@ class BibleImportForm(OpenLPWizard):
|
|||
self.select_page.registerField('zefania_file', self.zefania_file_edit)
|
||||
self.select_page.registerField('web_location', self.web_source_combo_box)
|
||||
self.select_page.registerField('web_biblename', self.web_translation_combo_box)
|
||||
self.select_page.registerField('sword_folder_path', self.sword_folder_edit)
|
||||
self.select_page.registerField('sword_zip_path', self.sword_zipfile_edit)
|
||||
self.select_page.registerField('proxy_server', self.web_server_edit)
|
||||
self.select_page.registerField('proxy_username', self.web_user_edit)
|
||||
self.select_page.registerField('proxy_password', self.web_password_edit)
|
||||
|
@ -565,6 +724,8 @@ class BibleImportForm(OpenLPWizard):
|
|||
self.setField('csv_versefile', '')
|
||||
self.setField('opensong_file', '')
|
||||
self.setField('zefania_file', '')
|
||||
self.setField('sword_folder_path', '')
|
||||
self.setField('sword_zip_path', '')
|
||||
self.setField('web_location', WebDownload.Crosswalk)
|
||||
self.setField('web_biblename', self.web_translation_combo_box.currentIndex())
|
||||
self.setField('proxy_server', settings.value('proxy address'))
|
||||
|
@ -626,9 +787,21 @@ class BibleImportForm(OpenLPWizard):
|
|||
language_id=language_id
|
||||
)
|
||||
elif bible_type == BibleFormat.Zefania:
|
||||
# Import an Zefania bible.
|
||||
# Import a Zefania bible.
|
||||
importer = self.manager.import_bible(BibleFormat.Zefania, name=license_version,
|
||||
filename=self.field('zefania_file'))
|
||||
elif bible_type == BibleFormat.SWORD:
|
||||
# Import a SWORD bible.
|
||||
if self.sword_tab_widget.currentIndex() == self.sword_tab_widget.indexOf(self.sword_folder_tab):
|
||||
importer = self.manager.import_bible(BibleFormat.SWORD, name=license_version,
|
||||
sword_path=self.field('sword_folder_path'),
|
||||
sword_key=self.sword_bible_combo_box.itemData(
|
||||
self.sword_bible_combo_box.currentIndex()))
|
||||
else:
|
||||
importer = self.manager.import_bible(BibleFormat.SWORD, name=license_version,
|
||||
sword_path=self.field('sword_zip_path'),
|
||||
sword_key=self.sword_zipbible_combo_box.itemData(
|
||||
self.sword_zipbible_combo_box.currentIndex()))
|
||||
if importer.do_import(license_version):
|
||||
self.manager.save_meta_data(license_version, license_version, license_copyright, license_permissions)
|
||||
self.manager.reload_bibles()
|
||||
|
|
|
@ -32,7 +32,7 @@ from PyQt5 import QtCore, QtWidgets
|
|||
from openlp.core.common import Registry, AppLocation, UiStrings, Settings, check_directory_exists, translate, \
|
||||
delete_file
|
||||
from openlp.core.lib.ui import critical_error_message_box
|
||||
from openlp.core.ui.wizard import OpenLPWizard, WizardStrings
|
||||
from openlp.core.ui.lib.wizard import OpenLPWizard, WizardStrings
|
||||
from openlp.plugins.bibles.lib.db import BibleDB, BibleMeta, OldBibleDB, BiblesResourcesDB
|
||||
from openlp.plugins.bibles.lib.http import BSExtract, BGExtract, CWExtract
|
||||
|
||||
|
|
|
@ -504,7 +504,7 @@ class CWExtract(RegistryProperties):
|
|||
soup = get_soup_for_bible_ref(chapter_url)
|
||||
if not soup:
|
||||
return None
|
||||
content = soup.find_all(('h4', {'class': 'small-header'}))
|
||||
content = soup.find_all('h4', {'class': 'small-header'})
|
||||
if not content:
|
||||
log.error('No books found in the Crosswalk response.')
|
||||
send_error_message('parse')
|
||||
|
|
|
@ -31,7 +31,10 @@ from .http import HTTPBible
|
|||
from .opensong import OpenSongBible
|
||||
from .osis import OSISBible
|
||||
from .zefania import ZefaniaBible
|
||||
|
||||
try:
|
||||
from .sword import SwordBible
|
||||
except:
|
||||
pass
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
@ -46,6 +49,7 @@ class BibleFormat(object):
|
|||
OpenSong = 2
|
||||
WebDownload = 3
|
||||
Zefania = 4
|
||||
SWORD = 5
|
||||
|
||||
@staticmethod
|
||||
def get_class(bible_format):
|
||||
|
@ -64,6 +68,8 @@ class BibleFormat(object):
|
|||
return HTTPBible
|
||||
elif bible_format == BibleFormat.Zefania:
|
||||
return ZefaniaBible
|
||||
elif bible_format == BibleFormat.SWORD:
|
||||
return SwordBible
|
||||
else:
|
||||
return None
|
||||
|
||||
|
@ -78,6 +84,7 @@ class BibleFormat(object):
|
|||
BibleFormat.OpenSong,
|
||||
BibleFormat.WebDownload,
|
||||
BibleFormat.Zefania,
|
||||
BibleFormat.SWORD
|
||||
]
|
||||
|
||||
|
||||
|
|
|
@ -881,6 +881,9 @@ class BibleMediaItem(MediaManagerItem):
|
|||
except IndexError:
|
||||
log.exception('The second_search_results does not have as many verses as the search_results.')
|
||||
break
|
||||
except TypeError:
|
||||
log.exception('The second_search_results does not have this book.')
|
||||
break
|
||||
bible_text = '%s %d%s%d (%s, %s)' % (book, verse.chapter, verse_separator, verse.verse, version,
|
||||
second_version)
|
||||
else:
|
||||
|
|
|
@ -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-2016 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 pysword import modules
|
||||
|
||||
from openlp.core.common import translate
|
||||
from openlp.core.lib.ui import critical_error_message_box
|
||||
from openlp.plugins.bibles.lib.db import BibleDB, BiblesResourcesDB
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SwordBible(BibleDB):
|
||||
"""
|
||||
SWORD Bible format importer class.
|
||||
"""
|
||||
def __init__(self, parent, **kwargs):
|
||||
"""
|
||||
Constructor to create and set up an instance of the SwordBible class. This class is used to import Bibles
|
||||
from SWORD bible modules.
|
||||
"""
|
||||
log.debug(self.__class__.__name__)
|
||||
BibleDB.__init__(self, parent, **kwargs)
|
||||
self.sword_key = kwargs['sword_key']
|
||||
self.sword_path = kwargs['sword_path']
|
||||
if self.sword_path == '':
|
||||
self.sword_path = None
|
||||
|
||||
def do_import(self, bible_name=None):
|
||||
"""
|
||||
Loads a Bible from SWORD module.
|
||||
"""
|
||||
log.debug('Starting SWORD import from "%s"' % self.sword_key)
|
||||
success = True
|
||||
try:
|
||||
pysword_modules = modules.SwordModules(self.sword_path)
|
||||
pysword_module_json = pysword_modules.parse_modules()[self.sword_key]
|
||||
bible = pysword_modules.get_bible_from_module(self.sword_key)
|
||||
language = pysword_module_json['lang']
|
||||
language = language[language.find('.') + 1:]
|
||||
language_id = BiblesResourcesDB.get_language(language)['id']
|
||||
self.save_meta('language_id', language_id)
|
||||
books = bible.get_structure().get_books()
|
||||
# Count number of books
|
||||
num_books = 0
|
||||
if 'ot' in books:
|
||||
num_books += len(books['ot'])
|
||||
if 'nt' in books:
|
||||
num_books += len(books['nt'])
|
||||
self.wizard.progress_bar.setMaximum(num_books)
|
||||
# Import the bible
|
||||
for testament in books.keys():
|
||||
for book in books[testament]:
|
||||
book_ref_id = self.get_book_ref_id_by_name(book.name, num_books, language_id)
|
||||
book_details = BiblesResourcesDB.get_book_by_id(book_ref_id)
|
||||
db_book = self.create_book(book_details['name'], book_ref_id, book_details['testament_id'])
|
||||
for chapter_number in range(1, book.num_chapters + 1):
|
||||
if self.stop_import_flag:
|
||||
break
|
||||
verses = bible.get_iter(book.name, chapter_number)
|
||||
verse_number = 0
|
||||
for verse in verses:
|
||||
verse_number += 1
|
||||
self.create_verse(db_book.id, chapter_number, verse_number, verse)
|
||||
self.wizard.increment_progress_bar(
|
||||
translate('BiblesPlugin.Sword', 'Importing %s...') % db_book.name)
|
||||
self.session.commit()
|
||||
self.application.process_events()
|
||||
except Exception as e:
|
||||
critical_error_message_box(
|
||||
message=translate('BiblesPlugin.SwordImport', 'An unexpected error happened while importing the SWORD '
|
||||
'bible, please report this to the OpenLP developers.\n'
|
||||
'%s' % e))
|
||||
log.exception(str(e))
|
||||
success = False
|
||||
if self.stop_import_flag:
|
||||
return False
|
||||
else:
|
||||
return success
|
|
@ -70,7 +70,8 @@ class ZefaniaBible(BibleDB):
|
|||
log.error('Importing books from "%s" failed' % self.filename)
|
||||
return False
|
||||
self.save_meta('language_id', language_id)
|
||||
num_books = int(zefania_bible_tree.xpath("count(//BIBLEBOOK)"))
|
||||
num_books = int(zefania_bible_tree.xpath('count(//BIBLEBOOK)'))
|
||||
self.wizard.progress_bar.setMaximum(int(zefania_bible_tree.xpath('count(//CHAPTER)')))
|
||||
# Strip tags we don't use - keep content
|
||||
etree.strip_tags(zefania_bible_tree, ('STYLE', 'GRAM', 'NOTE', 'SUP', 'XREF'))
|
||||
# Strip tags we don't use - remove content
|
||||
|
|
|
@ -23,7 +23,8 @@
|
|||
from PyQt5 import QtWidgets
|
||||
|
||||
from openlp.core.common import Settings, UiStrings, translate
|
||||
from openlp.core.lib import ColorButton, SettingsTab
|
||||
from openlp.core.lib import SettingsTab
|
||||
from openlp.core.ui.lib.colorbutton import ColorButton
|
||||
|
||||
|
||||
class ImageTab(SettingsTab):
|
||||
|
|
|
@ -27,9 +27,10 @@ from PyQt5 import QtCore, QtGui, QtWidgets
|
|||
|
||||
from openlp.core.common import Registry, AppLocation, Settings, UiStrings, check_directory_exists, translate, \
|
||||
delete_file, get_images_filter
|
||||
from openlp.core.lib import ItemCapabilities, MediaManagerItem, ServiceItemContext, StringContent, TreeWidgetWithDnD,\
|
||||
build_icon, check_item_selected, create_thumb, validate_thumb
|
||||
from openlp.core.lib import ItemCapabilities, MediaManagerItem, ServiceItemContext, StringContent, build_icon, \
|
||||
check_item_selected, create_thumb, validate_thumb
|
||||
from openlp.core.lib.ui import create_widget_action, critical_error_message_box
|
||||
from openlp.core.ui.lib.treewidgetwithdnd import TreeWidgetWithDnD
|
||||
from openlp.core.common.languagemanager import get_locale_key
|
||||
from openlp.plugins.images.forms import AddGroupForm, ChooseGroupForm
|
||||
from openlp.plugins.images.lib.db import ImageFilenames, ImageGroups
|
||||
|
@ -195,7 +196,7 @@ class ImageMediaItem(MediaManagerItem):
|
|||
Add custom buttons to the end of the toolbar
|
||||
"""
|
||||
self.replace_action = self.toolbar.add_toolbar_action('replace_action',
|
||||
icon=':/slides/slide_blank.png',
|
||||
icon=':/slides/slide_theme.png',
|
||||
triggers=self.on_replace_click)
|
||||
self.reset_action = self.toolbar.add_toolbar_action('reset_action',
|
||||
icon=':/system/system_close.png',
|
||||
|
|
|
@ -29,8 +29,8 @@ from openlp.core.common import Registry, RegistryProperties, AppLocation, Settin
|
|||
translate
|
||||
from openlp.core.lib import ItemCapabilities, MediaManagerItem, MediaType, ServiceItem, ServiceItemContext, \
|
||||
build_icon, check_item_selected
|
||||
from openlp.core.lib.ui import critical_error_message_box, create_horizontal_adjusting_combo_box
|
||||
from openlp.core.ui import DisplayController, Display, DisplayControllerType
|
||||
from openlp.core.lib.ui import create_widget_action, critical_error_message_box, create_horizontal_adjusting_combo_box
|
||||
from openlp.core.ui import DisplayControllerType
|
||||
from openlp.core.ui.media import get_media_players, set_media_players, parse_optical_path, format_milliseconds
|
||||
from openlp.core.common.languagemanager import get_locale_key
|
||||
from openlp.core.ui.media.vlcplayer import get_vlc
|
||||
|
@ -78,19 +78,9 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties):
|
|||
self.single_service_item = False
|
||||
self.has_search = True
|
||||
self.media_object = None
|
||||
self.display_controller = DisplayController(self.parent())
|
||||
self.display_controller.controller_layout = QtWidgets.QVBoxLayout()
|
||||
self.media_controller.register_controller(self.display_controller)
|
||||
self.media_controller.set_controls_visible(self.display_controller, False)
|
||||
self.display_controller.preview_display = Display(self.display_controller)
|
||||
self.display_controller.preview_display.hide()
|
||||
self.display_controller.preview_display.setGeometry(QtCore.QRect(0, 0, 300, 300))
|
||||
self.display_controller.preview_display.screen = {'size': self.display_controller.preview_display.geometry()}
|
||||
self.display_controller.preview_display.setup()
|
||||
self.media_controller.setup_display(self.display_controller.preview_display, False)
|
||||
# self.display_controller = DisplayController(self.parent())
|
||||
Registry().register_function('video_background_replaced', self.video_background_replaced)
|
||||
Registry().register_function('mediaitem_media_rebuild', self.rebuild_players)
|
||||
Registry().register_function('config_screen_changed', self.display_setup)
|
||||
# Allow DnD from the desktop
|
||||
self.list_view.activateDnD()
|
||||
|
||||
|
@ -101,12 +91,17 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties):
|
|||
"""
|
||||
self.on_new_prompt = translate('MediaPlugin.MediaItem', 'Select Media')
|
||||
self.replace_action.setText(UiStrings().ReplaceBG)
|
||||
self.replace_action_context.setText(UiStrings().ReplaceBG)
|
||||
if 'webkit' in get_media_players()[0]:
|
||||
self.replace_action.setToolTip(UiStrings().ReplaceLiveBG)
|
||||
self.replace_action_context.setToolTip(UiStrings().ReplaceLiveBG)
|
||||
else:
|
||||
self.replace_action.setToolTip(UiStrings().ReplaceLiveBGDisabled)
|
||||
self.replace_action_context.setToolTip(UiStrings().ReplaceLiveBGDisabled)
|
||||
self.reset_action.setText(UiStrings().ResetBG)
|
||||
self.reset_action.setToolTip(UiStrings().ResetLiveBG)
|
||||
self.reset_action_context.setText(UiStrings().ResetBG)
|
||||
self.reset_action_context.setToolTip(UiStrings().ResetLiveBG)
|
||||
self.automatic = UiStrings().Automatic
|
||||
self.display_type_label.setText(translate('MediaPlugin.MediaItem', 'Use Player:'))
|
||||
|
||||
|
@ -151,10 +146,11 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties):
|
|||
Adds buttons to the end of the header bar.
|
||||
"""
|
||||
# Replace backgrounds do not work at present so remove functionality.
|
||||
self.replace_action = self.toolbar.add_toolbar_action('replace_action', icon=':/slides/slide_blank.png',
|
||||
self.replace_action = self.toolbar.add_toolbar_action('replace_action', icon=':/slides/slide_theme.png',
|
||||
triggers=self.on_replace_click)
|
||||
if 'webkit' not in get_media_players()[0]:
|
||||
self.replace_action.setDisabled(True)
|
||||
self.replace_action_context.setDisabled(True)
|
||||
self.reset_action = self.toolbar.add_toolbar_action('reset_action', icon=':/system/system_close.png',
|
||||
visible=False, triggers=self.on_reset_click)
|
||||
self.media_widget = QtWidgets.QWidget(self)
|
||||
|
@ -173,7 +169,17 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties):
|
|||
self.page_layout.addWidget(self.media_widget)
|
||||
self.display_type_combo_box.currentIndexChanged.connect(self.override_player_changed)
|
||||
|
||||
def override_player_changed(self, index):
|
||||
def add_custom_context_actions(self):
|
||||
create_widget_action(self.list_view, separator=True)
|
||||
self.replace_action_context = create_widget_action(
|
||||
self.list_view, text=UiStrings().ReplaceBG, icon=':/slides/slide_blank.png',
|
||||
triggers=self.on_replace_click)
|
||||
self.reset_action_context = create_widget_action(
|
||||
self.list_view, text=UiStrings().ReplaceLiveBG, icon=':/system/system_close.png',
|
||||
visible=False, triggers=self.on_reset_click)
|
||||
|
||||
@staticmethod
|
||||
def override_player_changed(index):
|
||||
"""
|
||||
The Player has been overridden
|
||||
|
||||
|
@ -191,12 +197,14 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties):
|
|||
"""
|
||||
self.media_controller.media_reset(self.live_controller)
|
||||
self.reset_action.setVisible(False)
|
||||
self.reset_action_context.setVisible(False)
|
||||
|
||||
def video_background_replaced(self):
|
||||
"""
|
||||
Triggered by main display on change of serviceitem.
|
||||
"""
|
||||
self.reset_action.setVisible(False)
|
||||
self.reset_action_context.setVisible(False)
|
||||
|
||||
def on_replace_click(self):
|
||||
"""
|
||||
|
@ -215,6 +223,7 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties):
|
|||
service_item.add_from_command(path, name, CLAPPERBOARD)
|
||||
if self.media_controller.video(DisplayControllerType.Live, service_item, video_behind_text=True):
|
||||
self.reset_action.setVisible(True)
|
||||
self.reset_action_context.setVisible(True)
|
||||
else:
|
||||
critical_error_message_box(UiStrings().LiveBGError,
|
||||
translate('MediaPlugin.MediaItem',
|
||||
|
@ -273,16 +282,14 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties):
|
|||
service_item.processor = self.display_type_combo_box.currentText()
|
||||
service_item.add_from_command(path, name, CLAPPERBOARD)
|
||||
# Only get start and end times if going to a service
|
||||
if context == ServiceItemContext.Service:
|
||||
# Start media and obtain the length
|
||||
if not self.media_controller.media_length(service_item):
|
||||
return False
|
||||
if not self.media_controller.media_length(service_item):
|
||||
return False
|
||||
service_item.add_capability(ItemCapabilities.CanAutoStartForLive)
|
||||
service_item.add_capability(ItemCapabilities.CanEditTitle)
|
||||
service_item.add_capability(ItemCapabilities.RequiresMedia)
|
||||
if Settings().value(self.settings_section + '/media auto start') == QtCore.Qt.Checked:
|
||||
service_item.will_auto_start = True
|
||||
# force a non-existent theme
|
||||
# force a non-existent theme
|
||||
service_item.theme = -1
|
||||
return True
|
||||
|
||||
|
@ -305,12 +312,6 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties):
|
|||
' '.join(self.media_controller.video_extensions_list),
|
||||
' '.join(self.media_controller.audio_extensions_list), UiStrings().AllFiles)
|
||||
|
||||
def display_setup(self):
|
||||
"""
|
||||
Setup media controller display.
|
||||
"""
|
||||
self.media_controller.setup_display(self.display_controller.preview_display, False)
|
||||
|
||||
def populate_display_types(self):
|
||||
"""
|
||||
Load the combobox with the enabled media players, allowing user to select a specific player if settings allow.
|
||||
|
@ -385,16 +386,16 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties):
|
|||
if item_name:
|
||||
self.list_view.addItem(item_name)
|
||||
|
||||
def get_list(self, type=MediaType.Audio):
|
||||
def get_list(self, media_type=MediaType.Audio):
|
||||
"""
|
||||
Get the list of media, optional select media type.
|
||||
|
||||
:param type: Type to get, defaults to audio.
|
||||
:param media_type: Type to get, defaults to audio.
|
||||
:return: The media list
|
||||
"""
|
||||
media = Settings().value(self.settings_section + '/media files')
|
||||
media.sort(key=lambda filename: get_locale_key(os.path.split(str(filename))[1]))
|
||||
if type == MediaType.Audio:
|
||||
if media_type == MediaType.Audio:
|
||||
extension = self.media_controller.audio_extensions_list
|
||||
else:
|
||||
extension = self.media_controller.video_extensions_list
|
||||
|
|
|
@ -24,10 +24,13 @@ The Media plugin
|
|||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
from shutil import which
|
||||
|
||||
from PyQt5 import QtCore
|
||||
|
||||
from openlp.core.common import Settings, translate
|
||||
from openlp.core.common import AppLocation, Settings, translate, check_binary_exists, is_win
|
||||
from openlp.core.lib import Plugin, StringContent, build_icon
|
||||
from openlp.plugins.media.lib import MediaMediaItem, MediaTab
|
||||
|
||||
|
@ -60,15 +63,17 @@ class MediaPlugin(Plugin):
|
|||
"""
|
||||
Override the inherited initialise() method in order to upgrade the media before trying to load it
|
||||
"""
|
||||
# FIXME: Remove after 2.2 release.
|
||||
# This is needed to load the list of media from the config saved before the settings rewrite.
|
||||
if self.media_item_class is not None:
|
||||
loaded_list = Settings().get_files_from_config(self)
|
||||
# Now save the list to the config using our Settings class.
|
||||
if loaded_list:
|
||||
Settings().setValue('%s/%s files' % (self.settings_section, self.name), loaded_list)
|
||||
super().initialise()
|
||||
|
||||
def check_pre_conditions(self):
|
||||
"""
|
||||
Check it we have a valid environment.
|
||||
:return: true or false
|
||||
"""
|
||||
log.debug('check_installed Mediainfo')
|
||||
# Use the user defined program if given
|
||||
return process_check_binary('mediainfo')
|
||||
|
||||
def app_startup(self):
|
||||
"""
|
||||
Override app_startup() in order to do nothing
|
||||
|
@ -144,3 +149,21 @@ class MediaPlugin(Plugin):
|
|||
Add html code to htmlbuilder.
|
||||
"""
|
||||
return self.media_controller.get_media_display_html()
|
||||
|
||||
|
||||
def process_check_binary(program_path):
|
||||
"""
|
||||
Function that checks whether a binary MediaInfo is present
|
||||
|
||||
:param program_path:The full path to the binary to check.
|
||||
:return: If exists or not
|
||||
"""
|
||||
program_type = None
|
||||
runlog = check_binary_exists(program_path)
|
||||
print(runlog, type(runlog))
|
||||
# Analyse the output to see it the program is mediainfo
|
||||
for line in runlog.splitlines():
|
||||
decoded_line = line.decode()
|
||||
if re.search('MediaInfo Command line', decoded_line, re.IGNORECASE):
|
||||
return True
|
||||
return False
|
||||
|
|
|
@ -22,13 +22,12 @@
|
|||
|
||||
import os
|
||||
import logging
|
||||
from tempfile import NamedTemporaryFile
|
||||
import re
|
||||
from shutil import which
|
||||
from subprocess import check_output, CalledProcessError, STDOUT
|
||||
from subprocess import check_output, CalledProcessError
|
||||
|
||||
from openlp.core.common import AppLocation
|
||||
from openlp.core.common import Settings, is_win, trace_error_handler
|
||||
from openlp.core.common import AppLocation, check_binary_exists
|
||||
from openlp.core.common import Settings, is_win
|
||||
from openlp.core.lib import ScreenList
|
||||
from .presentationcontroller import PresentationController, PresentationDocument
|
||||
|
||||
|
@ -61,7 +60,7 @@ class PdfController(PresentationController):
|
|||
self.check_installed()
|
||||
|
||||
@staticmethod
|
||||
def check_binary(program_path):
|
||||
def process_check_binary(program_path):
|
||||
"""
|
||||
Function that checks whether a binary is either ghostscript or mudraw or neither.
|
||||
Is also used from presentationtab.py
|
||||
|
@ -70,22 +69,7 @@ class PdfController(PresentationController):
|
|||
:return: Type of the binary, 'gs' if ghostscript, 'mudraw' if mudraw, None if invalid.
|
||||
"""
|
||||
program_type = None
|
||||
runlog = ''
|
||||
log.debug('testing program_path: %s', program_path)
|
||||
try:
|
||||
# Setup startupinfo options for check_output to avoid console popping up on windows
|
||||
if is_win():
|
||||
startupinfo = STARTUPINFO()
|
||||
startupinfo.dwFlags |= STARTF_USESHOWWINDOW
|
||||
else:
|
||||
startupinfo = None
|
||||
runlog = check_output([program_path, '--help'], stderr=STDOUT, startupinfo=startupinfo)
|
||||
except CalledProcessError as e:
|
||||
runlog = e.output
|
||||
except Exception:
|
||||
trace_error_handler(log)
|
||||
runlog = ''
|
||||
log.debug('check_output returned: %s' % runlog)
|
||||
runlog = check_binary_exists(program_path)
|
||||
# Analyse the output to see it the program is mudraw, ghostscript or neither
|
||||
for line in runlog.splitlines():
|
||||
decoded_line = line.decode()
|
||||
|
@ -122,7 +106,7 @@ class PdfController(PresentationController):
|
|||
# Use the user defined program if given
|
||||
if Settings().value('presentations/enable_pdf_program'):
|
||||
pdf_program = Settings().value('presentations/pdf_program')
|
||||
program_type = self.check_binary(pdf_program)
|
||||
program_type = self.process_check_binary(pdf_program)
|
||||
if program_type == 'gs':
|
||||
self.gsbin = pdf_program
|
||||
elif program_type == 'mudraw':
|
||||
|
|
|
@ -144,18 +144,33 @@ class RemoteTab(SettingsTab):
|
|||
self.android_app_group_box = QtWidgets.QGroupBox(self.right_column)
|
||||
self.android_app_group_box.setObjectName('android_app_group_box')
|
||||
self.right_layout.addWidget(self.android_app_group_box)
|
||||
self.qr_layout = QtWidgets.QVBoxLayout(self.android_app_group_box)
|
||||
self.qr_layout.setObjectName('qr_layout')
|
||||
self.qr_code_label = QtWidgets.QLabel(self.android_app_group_box)
|
||||
self.qr_code_label.setPixmap(QtGui.QPixmap(':/remotes/android_app_qr.png'))
|
||||
self.qr_code_label.setAlignment(QtCore.Qt.AlignCenter)
|
||||
self.qr_code_label.setObjectName('qr_code_label')
|
||||
self.qr_layout.addWidget(self.qr_code_label)
|
||||
self.qr_description_label = QtWidgets.QLabel(self.android_app_group_box)
|
||||
self.qr_description_label.setObjectName('qr_description_label')
|
||||
self.qr_description_label.setOpenExternalLinks(True)
|
||||
self.qr_description_label.setWordWrap(True)
|
||||
self.qr_layout.addWidget(self.qr_description_label)
|
||||
self.android_qr_layout = QtWidgets.QVBoxLayout(self.android_app_group_box)
|
||||
self.android_qr_layout.setObjectName('android_qr_layout')
|
||||
self.android_qr_code_label = QtWidgets.QLabel(self.android_app_group_box)
|
||||
self.android_qr_code_label.setPixmap(QtGui.QPixmap(':/remotes/android_app_qr.png'))
|
||||
self.android_qr_code_label.setAlignment(QtCore.Qt.AlignCenter)
|
||||
self.android_qr_code_label.setObjectName('android_qr_code_label')
|
||||
self.android_qr_layout.addWidget(self.android_qr_code_label)
|
||||
self.android_qr_description_label = QtWidgets.QLabel(self.android_app_group_box)
|
||||
self.android_qr_description_label.setObjectName('android_qr_description_label')
|
||||
self.android_qr_description_label.setOpenExternalLinks(True)
|
||||
self.android_qr_description_label.setWordWrap(True)
|
||||
self.android_qr_layout.addWidget(self.android_qr_description_label)
|
||||
self.ios_app_group_box = QtWidgets.QGroupBox(self.right_column)
|
||||
self.ios_app_group_box.setObjectName('ios_app_group_box')
|
||||
self.right_layout.addWidget(self.ios_app_group_box)
|
||||
self.ios_qr_layout = QtWidgets.QVBoxLayout(self.ios_app_group_box)
|
||||
self.ios_qr_layout.setObjectName('ios_qr_layout')
|
||||
self.ios_qr_code_label = QtWidgets.QLabel(self.ios_app_group_box)
|
||||
self.ios_qr_code_label.setPixmap(QtGui.QPixmap(':/remotes/ios_app_qr.png'))
|
||||
self.ios_qr_code_label.setAlignment(QtCore.Qt.AlignCenter)
|
||||
self.ios_qr_code_label.setObjectName('ios_qr_code_label')
|
||||
self.ios_qr_layout.addWidget(self.ios_qr_code_label)
|
||||
self.ios_qr_description_label = QtWidgets.QLabel(self.ios_app_group_box)
|
||||
self.ios_qr_description_label.setObjectName('ios_qr_description_label')
|
||||
self.ios_qr_description_label.setOpenExternalLinks(True)
|
||||
self.ios_qr_description_label.setWordWrap(True)
|
||||
self.ios_qr_layout.addWidget(self.ios_qr_description_label)
|
||||
self.left_layout.addStretch()
|
||||
self.right_layout.addStretch()
|
||||
self.twelve_hour_check_box.stateChanged.connect(self.on_twelve_hour_check_box_changed)
|
||||
|
@ -176,10 +191,15 @@ class RemoteTab(SettingsTab):
|
|||
self.thumbnails_check_box.setText(translate('RemotePlugin.RemoteTab',
|
||||
'Show thumbnails of non-text slides in remote and stage view.'))
|
||||
self.android_app_group_box.setTitle(translate('RemotePlugin.RemoteTab', 'Android App'))
|
||||
self.qr_description_label.setText(
|
||||
self.android_qr_description_label.setText(
|
||||
translate('RemotePlugin.RemoteTab', 'Scan the QR code or click <a href="%s">download</a> to install the '
|
||||
'Android app from Google Play.') %
|
||||
'https://play.google.com/store/apps/details?id=org.openlp.android2')
|
||||
self.ios_app_group_box.setTitle(translate('RemotePlugin.RemoteTab', 'iOS App'))
|
||||
self.ios_qr_description_label.setText(
|
||||
translate('RemotePlugin.RemoteTab', 'Scan the QR code or click <a href="%s">download</a> to install the '
|
||||
'iOS app from the App Store.') %
|
||||
'https://itunes.apple.com/app/id1096218725')
|
||||
self.https_settings_group_box.setTitle(translate('RemotePlugin.RemoteTab', 'HTTPS Server'))
|
||||
self.https_error_label.setText(
|
||||
translate('RemotePlugin.RemoteTab', 'Could not find an SSL certificate. The HTTPS server will not be '
|
||||
|
|
|
@ -30,7 +30,7 @@ import os
|
|||
from PyQt5 import QtCore, QtWidgets
|
||||
|
||||
from openlp.core.common import Registry, RegistryProperties, translate
|
||||
from openlp.core.ui.wizard import OpenLPWizard, WizardStrings
|
||||
from openlp.core.ui.lib.wizard import OpenLPWizard, WizardStrings
|
||||
from openlp.plugins.songs.lib import delete_song
|
||||
from openlp.plugins.songs.lib.db import Song, MediaFile
|
||||
from openlp.plugins.songs.forms.songreviewwidget import SongReviewWidget
|
||||
|
|
|
@ -34,6 +34,7 @@ from PyQt5 import QtCore, QtWidgets
|
|||
from openlp.core.common import Registry, RegistryProperties, AppLocation, UiStrings, check_directory_exists, translate
|
||||
from openlp.core.lib import FileDialog, PluginStatus, MediaType, create_separated_list
|
||||
from openlp.core.lib.ui import set_case_insensitive_completer, critical_error_message_box, find_and_set_in_combo_box
|
||||
from openlp.core.common.languagemanager import get_natural_key
|
||||
from openlp.plugins.songs.lib import VerseType, clean_song
|
||||
from openlp.plugins.songs.lib.db import Book, Song, Author, AuthorType, Topic, MediaFile, SongBookEntry
|
||||
from openlp.plugins.songs.lib.ui import SongStrings
|
||||
|
@ -110,7 +111,12 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
|
|||
"""
|
||||
Generically load a set of objects into a cache and a combobox.
|
||||
"""
|
||||
objects = self.manager.get_all_objects(cls, order_by_ref=cls.name)
|
||||
def get_key(obj):
|
||||
"""Get the key to sort by"""
|
||||
return get_natural_key(obj.name)
|
||||
|
||||
objects = self.manager.get_all_objects(cls)
|
||||
objects.sort(key=get_key)
|
||||
combo.clear()
|
||||
combo.addItem('')
|
||||
for obj in objects:
|
||||
|
@ -343,7 +349,12 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
|
|||
"""
|
||||
Load the authors from the database into the combobox.
|
||||
"""
|
||||
authors = self.manager.get_all_objects(Author, order_by_ref=Author.display_name)
|
||||
def get_author_key(author):
|
||||
"""Get the key to sort by"""
|
||||
return get_natural_key(author.display_name)
|
||||
|
||||
authors = self.manager.get_all_objects(Author)
|
||||
authors.sort(key=get_author_key)
|
||||
self.authors_combo_box.clear()
|
||||
self.authors_combo_box.addItem('')
|
||||
self.authors = []
|
||||
|
@ -378,9 +389,14 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
|
|||
"""
|
||||
Load the themes into a combobox.
|
||||
"""
|
||||
def get_theme_key(theme):
|
||||
"""Get the key to sort by"""
|
||||
return get_natural_key(theme)
|
||||
|
||||
self.theme_combo_box.clear()
|
||||
self.theme_combo_box.addItem('')
|
||||
self.themes = theme_list
|
||||
self.themes.sort(key=get_theme_key)
|
||||
self.theme_combo_box.addItems(theme_list)
|
||||
set_case_insensitive_completer(self.themes, self.theme_combo_box)
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@ from PyQt5 import QtCore, QtWidgets
|
|||
from openlp.core.common import Registry, UiStrings, translate
|
||||
from openlp.core.lib import create_separated_list, build_icon
|
||||
from openlp.core.lib.ui import critical_error_message_box
|
||||
from openlp.core.ui.wizard import OpenLPWizard, WizardStrings
|
||||
from openlp.core.ui.lib.wizard import OpenLPWizard, WizardStrings
|
||||
from openlp.plugins.songs.lib.db import Song
|
||||
from openlp.plugins.songs.lib.openlyricsexport import OpenLyricsExport
|
||||
|
||||
|
@ -203,6 +203,10 @@ class SongExportForm(OpenLPWizard):
|
|||
"""
|
||||
Set default form values for the song export wizard.
|
||||
"""
|
||||
def get_song_key(song):
|
||||
"""Get the key to sort by"""
|
||||
return song.sort_key
|
||||
|
||||
self.restart()
|
||||
self.finish_button.setVisible(False)
|
||||
self.cancel_button.setVisible(True)
|
||||
|
@ -213,7 +217,7 @@ class SongExportForm(OpenLPWizard):
|
|||
# Load the list of songs.
|
||||
self.application.set_busy_cursor()
|
||||
songs = self.plugin.manager.get_all_objects(Song)
|
||||
songs.sort(key=lambda song: song.sort_key)
|
||||
songs.sort(key=get_song_key)
|
||||
for song in songs:
|
||||
# No need to export temporary songs.
|
||||
if song.temporary:
|
||||
|
|
|
@ -31,7 +31,7 @@ from PyQt5 import QtCore, QtWidgets
|
|||
from openlp.core.common import RegistryProperties, Settings, UiStrings, translate
|
||||
from openlp.core.lib import FileDialog
|
||||
from openlp.core.lib.ui import critical_error_message_box
|
||||
from openlp.core.ui.wizard import OpenLPWizard, WizardStrings
|
||||
from openlp.core.ui.lib.wizard import OpenLPWizard, WizardStrings
|
||||
from openlp.plugins.songs.lib.importer import SongFormat, SongFormatSelect
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
|
|
@ -27,6 +27,7 @@ from sqlalchemy.sql import and_
|
|||
|
||||
from openlp.core.common import Registry, RegistryProperties, UiStrings, translate
|
||||
from openlp.core.lib.ui import critical_error_message_box
|
||||
from openlp.core.common.languagemanager import get_natural_key
|
||||
from openlp.plugins.songs.forms.authorsform import AuthorsForm
|
||||
from openlp.plugins.songs.forms.topicsform import TopicsForm
|
||||
from openlp.plugins.songs.forms.songbookform import SongBookForm
|
||||
|
@ -120,8 +121,13 @@ class SongMaintenanceForm(QtWidgets.QDialog, Ui_SongMaintenanceDialog, RegistryP
|
|||
"""
|
||||
Reloads the Authors list.
|
||||
"""
|
||||
def get_author_key(author):
|
||||
"""Get the key to sort by"""
|
||||
return get_natural_key(author.display_name)
|
||||
|
||||
self.authors_list_widget.clear()
|
||||
authors = self.manager.get_all_objects(Author, order_by_ref=Author.display_name)
|
||||
authors = self.manager.get_all_objects(Author)
|
||||
authors.sort(key=get_author_key)
|
||||
for author in authors:
|
||||
if author.display_name:
|
||||
author_name = QtWidgets.QListWidgetItem(author.display_name)
|
||||
|
@ -134,8 +140,13 @@ class SongMaintenanceForm(QtWidgets.QDialog, Ui_SongMaintenanceDialog, RegistryP
|
|||
"""
|
||||
Reloads the Topics list.
|
||||
"""
|
||||
def get_topic_key(topic):
|
||||
"""Get the key to sort by"""
|
||||
return get_natural_key(topic.name)
|
||||
|
||||
self.topics_list_widget.clear()
|
||||
topics = self.manager.get_all_objects(Topic, order_by_ref=Topic.name)
|
||||
topics = self.manager.get_all_objects(Topic)
|
||||
topics.sort(key=get_topic_key)
|
||||
for topic in topics:
|
||||
topic_name = QtWidgets.QListWidgetItem(topic.name)
|
||||
topic_name.setData(QtCore.Qt.UserRole, topic.id)
|
||||
|
@ -145,8 +156,13 @@ class SongMaintenanceForm(QtWidgets.QDialog, Ui_SongMaintenanceDialog, RegistryP
|
|||
"""
|
||||
Reloads the Books list.
|
||||
"""
|
||||
def get_book_key(book):
|
||||
"""Get the key to sort by"""
|
||||
return get_natural_key(book.name)
|
||||
|
||||
self.song_books_list_widget.clear()
|
||||
books = self.manager.get_all_objects(Book, order_by_ref=Book.name)
|
||||
books = self.manager.get_all_objects(Book)
|
||||
books.sort(key=get_book_key)
|
||||
for book in books:
|
||||
book_name = QtWidgets.QListWidgetItem('%s (%s)' % (book.name, book.publisher))
|
||||
book_name.setData(QtCore.Qt.UserRole, book.id)
|
||||
|
|
|
@ -299,6 +299,7 @@ class SongSelectForm(QtWidgets.QDialog, Ui_SongSelectDialog):
|
|||
# Set up UI components
|
||||
self.view_button.setEnabled(False)
|
||||
self.search_button.setEnabled(False)
|
||||
self.search_combobox.setEnabled(False)
|
||||
self.search_progress_bar.setMinimum(0)
|
||||
self.search_progress_bar.setMaximum(0)
|
||||
self.search_progress_bar.setValue(0)
|
||||
|
@ -354,6 +355,7 @@ class SongSelectForm(QtWidgets.QDialog, Ui_SongSelectDialog):
|
|||
self.application.process_events()
|
||||
self.set_progress_visible(False)
|
||||
self.search_button.setEnabled(True)
|
||||
self.search_combobox.setEnabled(True)
|
||||
self.application.process_events()
|
||||
|
||||
def on_search_results_widget_selection_changed(self):
|
||||
|
|
|
@ -255,6 +255,7 @@ class VerseType(object):
|
|||
for num, translation in enumerate(VerseType.translated_names):
|
||||
if verse_name == translation.lower():
|
||||
return num
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def from_loose_input(verse_name, default=Other):
|
||||
|
@ -270,7 +271,7 @@ class VerseType(object):
|
|||
if verse_index is None:
|
||||
verse_index = VerseType.from_string(verse_name, default)
|
||||
elif len(verse_name) == 1:
|
||||
verse_index = VerseType.from_translated_tag(verse_name, default)
|
||||
verse_index = VerseType.from_translated_tag(verse_name, None)
|
||||
if verse_index is None:
|
||||
verse_index = VerseType.from_tag(verse_name, default)
|
||||
else:
|
||||
|
|
|
@ -383,7 +383,7 @@ def init_schema(url):
|
|||
# Use lazy='joined' to always load authors when the song is fetched from the database (bug 1366198)
|
||||
'authors': relation(Author, secondary=authors_songs_table, viewonly=True, lazy='joined'),
|
||||
'media_files': relation(MediaFile, backref='songs', order_by=media_files_table.c.weight),
|
||||
'songbook_entries': relation(SongBookEntry, backref='song', cascade="all, delete-orphan"),
|
||||
'songbook_entries': relation(SongBookEntry, backref='song', cascade='all, delete-orphan'),
|
||||
'topics': relation(Topic, backref='songs', secondary=songs_topics_table)
|
||||
})
|
||||
mapper(Topic, topics_table)
|
||||
|
|
|
@ -26,7 +26,7 @@ import os
|
|||
import logging
|
||||
|
||||
from openlp.core.common import translate, UiStrings, is_win
|
||||
from openlp.core.ui.wizard import WizardStrings
|
||||
from openlp.core.ui.lib.wizard import WizardStrings
|
||||
from .importers.opensong import OpenSongImport
|
||||
from .importers.easyslides import EasySlidesImport
|
||||
from .importers.openlp import OpenLPSongImport
|
||||
|
|
|
@ -90,7 +90,7 @@ import os
|
|||
from lxml import etree, objectify
|
||||
|
||||
from openlp.core.lib import translate
|
||||
from openlp.core.ui.wizard import WizardStrings
|
||||
from openlp.core.ui.lib.wizard import WizardStrings
|
||||
from openlp.plugins.songs.lib import clean_song, VerseType
|
||||
from openlp.plugins.songs.lib.importers.songimport import SongImport
|
||||
from openlp.plugins.songs.lib.db import Author, Book, Song, Topic
|
||||
|
|
|
@ -31,7 +31,7 @@ from sqlalchemy.orm.exc import UnmappedClassError
|
|||
|
||||
from openlp.core.common import translate
|
||||
from openlp.core.lib.db import BaseModel
|
||||
from openlp.core.ui.wizard import WizardStrings
|
||||
from openlp.core.ui.lib.wizard import WizardStrings
|
||||
from openlp.plugins.songs.lib import clean_song
|
||||
from openlp.plugins.songs.lib.db import Author, Book, Song, Topic, MediaFile
|
||||
from .songimport import SongImport
|
||||
|
@ -51,7 +51,7 @@ class OpenLPSongImport(SongImport):
|
|||
:param manager: The song manager for the running OpenLP installation.
|
||||
:param kwargs: The database providing the data to import.
|
||||
"""
|
||||
SongImport.__init__(self, manager, **kwargs)
|
||||
super(OpenLPSongImport, self).__init__(manager, **kwargs)
|
||||
self.source_session = None
|
||||
|
||||
def do_import(self, progress_dialog=None):
|
||||
|
@ -63,49 +63,61 @@ class OpenLPSongImport(SongImport):
|
|||
|
||||
class OldAuthor(BaseModel):
|
||||
"""
|
||||
Author model
|
||||
Maps to the authors table
|
||||
"""
|
||||
pass
|
||||
|
||||
class OldBook(BaseModel):
|
||||
"""
|
||||
Book model
|
||||
Maps to the songbooks table
|
||||
"""
|
||||
pass
|
||||
|
||||
class OldMediaFile(BaseModel):
|
||||
"""
|
||||
MediaFile model
|
||||
Maps to the media_files table
|
||||
"""
|
||||
pass
|
||||
|
||||
class OldSong(BaseModel):
|
||||
"""
|
||||
Song model
|
||||
Maps to the songs table
|
||||
"""
|
||||
pass
|
||||
|
||||
class OldTopic(BaseModel):
|
||||
"""
|
||||
Topic model
|
||||
Maps to the topics table
|
||||
"""
|
||||
pass
|
||||
|
||||
class OldSongBookEntry(BaseModel):
|
||||
"""
|
||||
Maps to the songs_songbooks table
|
||||
"""
|
||||
pass
|
||||
|
||||
# Check the file type
|
||||
if not self.import_source.endswith('.sqlite'):
|
||||
if not isinstance(self.import_source, str) or not self.import_source.endswith('.sqlite'):
|
||||
self.log_error(self.import_source, translate('SongsPlugin.OpenLPSongImport',
|
||||
'Not a valid OpenLP 2 song database.'))
|
||||
return
|
||||
self.import_source = 'sqlite:///%s' % self.import_source
|
||||
# Load the db file
|
||||
# Load the db file and reflect it
|
||||
engine = create_engine(self.import_source)
|
||||
source_meta = MetaData()
|
||||
source_meta.reflect(engine)
|
||||
self.source_session = scoped_session(sessionmaker(bind=engine))
|
||||
# Run some checks to see which version of the database we have
|
||||
if 'media_files' in list(source_meta.tables.keys()):
|
||||
has_media_files = True
|
||||
else:
|
||||
has_media_files = False
|
||||
if 'songs_songbooks' in list(source_meta.tables.keys()):
|
||||
has_songs_books = True
|
||||
else:
|
||||
has_songs_books = False
|
||||
# Load up the tabls and map them out
|
||||
source_authors_table = source_meta.tables['authors']
|
||||
source_song_books_table = source_meta.tables['song_books']
|
||||
source_songs_table = source_meta.tables['songs']
|
||||
|
@ -113,6 +125,7 @@ class OpenLPSongImport(SongImport):
|
|||
source_authors_songs_table = source_meta.tables['authors_songs']
|
||||
source_songs_topics_table = source_meta.tables['songs_topics']
|
||||
source_media_files_songs_table = None
|
||||
# Set up media_files relations
|
||||
if has_media_files:
|
||||
source_media_files_table = source_meta.tables['media_files']
|
||||
source_media_files_songs_table = source_meta.tables.get('media_files_songs')
|
||||
|
@ -120,9 +133,15 @@ class OpenLPSongImport(SongImport):
|
|||
class_mapper(OldMediaFile)
|
||||
except UnmappedClassError:
|
||||
mapper(OldMediaFile, source_media_files_table)
|
||||
if has_songs_books:
|
||||
source_songs_songbooks_table = source_meta.tables['songs_songbooks']
|
||||
try:
|
||||
class_mapper(OldSongBookEntry)
|
||||
except UnmappedClassError:
|
||||
mapper(OldSongBookEntry, source_songs_songbooks_table, properties={'songbook': relation(OldBook)})
|
||||
# Set up the songs relationships
|
||||
song_props = {
|
||||
'authors': relation(OldAuthor, backref='songs', secondary=source_authors_songs_table),
|
||||
'book': relation(OldBook, backref='songs'),
|
||||
'topics': relation(OldTopic, backref='songs', secondary=source_songs_topics_table)
|
||||
}
|
||||
if has_media_files:
|
||||
|
@ -134,6 +153,11 @@ class OpenLPSongImport(SongImport):
|
|||
relation(OldMediaFile, backref='songs',
|
||||
foreign_keys=[source_media_files_table.c.song_id],
|
||||
primaryjoin=source_songs_table.c.id == source_media_files_table.c.song_id)
|
||||
if has_songs_books:
|
||||
song_props['songbook_entries'] = relation(OldSongBookEntry, backref='song', cascade='all, delete-orphan')
|
||||
else:
|
||||
song_props['book'] = relation(OldBook, backref='songs')
|
||||
# Map the rest of the tables
|
||||
try:
|
||||
class_mapper(OldAuthor)
|
||||
except UnmappedClassError:
|
||||
|
@ -163,44 +187,54 @@ class OpenLPSongImport(SongImport):
|
|||
old_titles = song.search_title.split('@')
|
||||
if len(old_titles) > 1:
|
||||
new_song.alternate_title = old_titles[1]
|
||||
# Values will be set when cleaning the song.
|
||||
# Transfer the values to the new song object
|
||||
new_song.search_title = ''
|
||||
new_song.search_lyrics = ''
|
||||
new_song.song_number = song.song_number
|
||||
new_song.lyrics = song.lyrics
|
||||
new_song.verse_order = song.verse_order
|
||||
new_song.copyright = song.copyright
|
||||
new_song.comments = song.comments
|
||||
new_song.theme_name = song.theme_name
|
||||
new_song.ccli_number = song.ccli_number
|
||||
if hasattr(song, 'song_number') and song.song_number:
|
||||
new_song.song_number = song.song_number
|
||||
# Find or create all the authors and add them to the new song object
|
||||
for author in song.authors:
|
||||
existing_author = self.manager.get_object_filtered(Author, Author.display_name == author.display_name)
|
||||
if existing_author is None:
|
||||
if not existing_author:
|
||||
existing_author = Author.populate(
|
||||
first_name=author.first_name,
|
||||
last_name=author.last_name,
|
||||
display_name=author.display_name)
|
||||
new_song.add_author(existing_author)
|
||||
if song.book:
|
||||
existing_song_book = self.manager.get_object_filtered(Book, Book.name == song.book.name)
|
||||
if existing_song_book is None:
|
||||
existing_song_book = Book.populate(name=song.book.name, publisher=song.book.publisher)
|
||||
new_song.book = existing_song_book
|
||||
# Find or create all the topics and add them to the new song object
|
||||
if song.topics:
|
||||
for topic in song.topics:
|
||||
existing_topic = self.manager.get_object_filtered(Topic, Topic.name == topic.name)
|
||||
if existing_topic is None:
|
||||
if not existing_topic:
|
||||
existing_topic = Topic.populate(name=topic.name)
|
||||
new_song.topics.append(existing_topic)
|
||||
if has_media_files:
|
||||
if song.media_files:
|
||||
for media_file in song.media_files:
|
||||
existing_media_file = self.manager.get_object_filtered(
|
||||
MediaFile, MediaFile.file_name == media_file.file_name)
|
||||
if existing_media_file:
|
||||
new_song.media_files.append(existing_media_file)
|
||||
else:
|
||||
new_song.media_files.append(MediaFile.populate(file_name=media_file.file_name))
|
||||
# Find or create all the songbooks and add them to the new song object
|
||||
if has_songs_books and song.songbook_entries:
|
||||
for entry in song.songbook_entries:
|
||||
existing_book = self.manager.get_object_filtered(Book, Book.name == entry.songbook.name)
|
||||
if not existing_book:
|
||||
existing_book = Book.populate(name=entry.songbook.name, publisher=entry.songbook.publisher)
|
||||
new_song.add_songbook_entry(existing_book, entry.entry)
|
||||
elif song.book:
|
||||
existing_book = self.manager.get_object_filtered(Book, Book.name == song.book.name)
|
||||
if not existing_book:
|
||||
existing_book = Book.populate(name=song.book.name, publisher=song.book.publisher)
|
||||
new_song.add_songbook_entry(existing_book, '')
|
||||
# Find or create all the media files and add them to the new song object
|
||||
if has_media_files and song.media_files:
|
||||
for media_file in song.media_files:
|
||||
existing_media_file = self.manager.get_object_filtered(
|
||||
MediaFile, MediaFile.file_name == media_file.file_name)
|
||||
if existing_media_file:
|
||||
new_song.media_files.append(existing_media_file)
|
||||
else:
|
||||
new_song.media_files.append(MediaFile.populate(file_name=media_file.file_name))
|
||||
clean_song(self.manager, new_song)
|
||||
self.manager.save_object(new_song)
|
||||
if progress_dialog:
|
||||
|
|
|
@ -29,7 +29,7 @@ import os
|
|||
|
||||
from lxml import etree
|
||||
|
||||
from openlp.core.ui.wizard import WizardStrings
|
||||
from openlp.core.ui.lib.wizard import WizardStrings
|
||||
from openlp.plugins.songs.lib.importers.songimport import SongImport
|
||||
from openlp.plugins.songs.lib.ui import SongStrings
|
||||
from openlp.plugins.songs.lib.openlyricsxml import OpenLyrics, OpenLyricsError
|
||||
|
|
|
@ -27,7 +27,7 @@ Powerpraise song files into the current database.
|
|||
import os
|
||||
from lxml import objectify
|
||||
|
||||
from openlp.core.ui.wizard import WizardStrings
|
||||
from openlp.core.ui.lib.wizard import WizardStrings
|
||||
from .songimport import SongImport
|
||||
|
||||
|
||||
|
|
|
@ -26,10 +26,11 @@ Presentationmanager song files into the current database.
|
|||
|
||||
import os
|
||||
import re
|
||||
|
||||
import chardet
|
||||
from lxml import objectify, etree
|
||||
|
||||
from openlp.core.ui.wizard import WizardStrings
|
||||
from openlp.core.ui.lib.wizard import WizardStrings
|
||||
from .songimport import SongImport
|
||||
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ import base64
|
|||
import logging
|
||||
from lxml import objectify
|
||||
|
||||
from openlp.core.ui.wizard import WizardStrings
|
||||
from openlp.core.ui.lib.wizard import WizardStrings
|
||||
from openlp.plugins.songs.lib import strip_rtf
|
||||
from .songimport import SongImport
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ import os
|
|||
from PyQt5 import QtCore
|
||||
|
||||
from openlp.core.common import Registry, AppLocation, check_directory_exists, translate
|
||||
from openlp.core.ui.wizard import WizardStrings
|
||||
from openlp.core.ui.lib.wizard import WizardStrings
|
||||
from openlp.plugins.songs.lib import clean_song, VerseType
|
||||
from openlp.plugins.songs.lib.db import Song, Author, Topic, Book, MediaFile
|
||||
from openlp.plugins.songs.lib.ui import SongStrings
|
||||
|
|
|
@ -29,7 +29,7 @@ import logging
|
|||
import re
|
||||
import struct
|
||||
|
||||
from openlp.core.ui.wizard import WizardStrings
|
||||
from openlp.core.ui.lib.wizard import WizardStrings
|
||||
from openlp.plugins.songs.lib import VerseType, retrieve_windows_encoding
|
||||
from openlp.plugins.songs.lib.importers.songimport import SongImport
|
||||
|
||||
|
|
|
@ -107,9 +107,9 @@ class WordsOfWorshipImport(SongImport):
|
|||
song_data = open(source, 'rb')
|
||||
if song_data.read(19).decode() != 'WoW File\nSong Words':
|
||||
self.log_error(source,
|
||||
str(translate('SongsPlugin.WordsofWorshipSongImport',
|
||||
'Invalid Words of Worship song file. Missing "%s" header.'
|
||||
% 'WoW File\\nSong Words')))
|
||||
translate('SongsPlugin.WordsofWorshipSongImport',
|
||||
'Invalid Words of Worship song file. Missing "%s" header.')
|
||||
% 'WoW File\\nSong Words')
|
||||
continue
|
||||
# Seek to byte which stores number of blocks in the song
|
||||
song_data.seek(56)
|
||||
|
@ -117,9 +117,9 @@ class WordsOfWorshipImport(SongImport):
|
|||
song_data.seek(66)
|
||||
if song_data.read(16).decode() != 'CSongDoc::CBlock':
|
||||
self.log_error(source,
|
||||
str(translate('SongsPlugin.WordsofWorshipSongImport',
|
||||
'Invalid Words of Worship song file. Missing "%s" '
|
||||
'string.' % 'CSongDoc::CBlock')))
|
||||
translate('SongsPlugin.WordsofWorshipSongImport',
|
||||
'Invalid Words of Worship song file. Missing "%s" string.')
|
||||
% 'CSongDoc::CBlock')
|
||||
continue
|
||||
# Seek to the beginning of the first block
|
||||
song_data.seek(82)
|
||||
|
|
|
@ -194,13 +194,13 @@ class SongMediaItem(MediaManagerItem):
|
|||
log.debug('Authors Search')
|
||||
search_string = '%' + search_keywords + '%'
|
||||
search_results = self.plugin.manager.get_all_objects(
|
||||
Author, Author.display_name.like(search_string), Author.display_name.asc())
|
||||
Author, Author.display_name.like(search_string))
|
||||
self.display_results_author(search_results)
|
||||
elif search_type == SongSearch.Topics:
|
||||
log.debug('Topics Search')
|
||||
search_string = '%' + search_keywords + '%'
|
||||
search_results = self.plugin.manager.get_all_objects(
|
||||
Topic, Topic.name.like(search_string), Topic.name.asc())
|
||||
Topic, Topic.name.like(search_string))
|
||||
self.display_results_topic(search_results)
|
||||
elif search_type == SongSearch.Books:
|
||||
log.debug('Songbook Search')
|
||||
|
@ -215,7 +215,7 @@ class SongMediaItem(MediaManagerItem):
|
|||
log.debug('Theme Search')
|
||||
search_string = '%' + search_keywords + '%'
|
||||
search_results = self.plugin.manager.get_all_objects(
|
||||
Song, Song.theme_name.like(search_string), Song.theme_name.asc())
|
||||
Song, Song.theme_name.like(search_string))
|
||||
self.display_results_themes(search_results)
|
||||
elif search_type == SongSearch.Copyright:
|
||||
log.debug('Copyright Search')
|
||||
|
@ -258,10 +258,14 @@ class SongMediaItem(MediaManagerItem):
|
|||
:param search_results: A list of db Song objects
|
||||
:return: None
|
||||
"""
|
||||
def get_song_key(song):
|
||||
"""Get the key to sort by"""
|
||||
return song.sort_key
|
||||
|
||||
log.debug('display results Song')
|
||||
self.save_auto_select_id()
|
||||
self.list_view.clear()
|
||||
search_results.sort(key=lambda song: song.sort_key)
|
||||
search_results.sort(key=get_song_key)
|
||||
for song in search_results:
|
||||
# Do not display temporary songs
|
||||
if song.temporary:
|
||||
|
@ -283,12 +287,20 @@ class SongMediaItem(MediaManagerItem):
|
|||
:param search_results: A list of db Author objects
|
||||
:return: None
|
||||
"""
|
||||
def get_author_key(author):
|
||||
"""Get the key to sort by"""
|
||||
return get_natural_key(author.display_name)
|
||||
|
||||
def get_song_key(song):
|
||||
"""Get the key to sort by"""
|
||||
return song.sort_key
|
||||
|
||||
log.debug('display results Author')
|
||||
self.list_view.clear()
|
||||
search_results = sorted(search_results, key=lambda author: get_natural_key(author.display_name))
|
||||
search_results.sort(key=get_author_key)
|
||||
for author in search_results:
|
||||
songs = sorted(author.songs, key=lambda song: song.sort_key)
|
||||
for song in songs:
|
||||
author.songs.sort(key=get_song_key)
|
||||
for song in author.songs:
|
||||
# Do not display temporary songs
|
||||
if song.temporary:
|
||||
continue
|
||||
|
@ -304,11 +316,15 @@ class SongMediaItem(MediaManagerItem):
|
|||
:param search_results: A list of db SongBookEntry objects
|
||||
:return: None
|
||||
"""
|
||||
def get_songbook_key(songbook_entry):
|
||||
"""Get the key to sort by"""
|
||||
return (get_natural_key(songbook_entry.songbook.name), get_natural_key(songbook_entry.entry))
|
||||
|
||||
log.debug('display results Book')
|
||||
self.list_view.clear()
|
||||
search_results = sorted(search_results, key=lambda songbook_entry:
|
||||
(get_natural_key(songbook_entry.songbook.name), get_natural_key(songbook_entry.entry)))
|
||||
search_results.sort(key=get_songbook_key)
|
||||
for songbook_entry in search_results:
|
||||
# Do not display temporary songs
|
||||
if songbook_entry.song.temporary:
|
||||
continue
|
||||
song_detail = '%s #%s: %s' % (songbook_entry.songbook.name, songbook_entry.entry, songbook_entry.song.title)
|
||||
|
@ -323,12 +339,20 @@ class SongMediaItem(MediaManagerItem):
|
|||
:param search_results: A list of db Topic objects
|
||||
:return: None
|
||||
"""
|
||||
def get_topic_key(topic):
|
||||
"""Get the key to sort by"""
|
||||
return get_natural_key(topic.name)
|
||||
|
||||
def get_song_key(song):
|
||||
"""Get the key to sort by"""
|
||||
return song.sort_key
|
||||
|
||||
log.debug('display results Topic')
|
||||
self.list_view.clear()
|
||||
search_results = sorted(search_results, key=lambda topic: get_natural_key(topic.name))
|
||||
search_results.sort(key=get_topic_key)
|
||||
for topic in search_results:
|
||||
songs = sorted(topic.songs, key=lambda song: song.sort_key)
|
||||
for song in songs:
|
||||
topic.songs.sort(key=get_song_key)
|
||||
for song in topic.songs:
|
||||
# Do not display temporary songs
|
||||
if song.temporary:
|
||||
continue
|
||||
|
@ -344,10 +368,13 @@ class SongMediaItem(MediaManagerItem):
|
|||
:param search_results: A list of db Song objects
|
||||
:return: None
|
||||
"""
|
||||
def get_theme_key(song):
|
||||
"""Get the key to sort by"""
|
||||
return (get_natural_key(song.theme_name), song.sort_key)
|
||||
|
||||
log.debug('display results Themes')
|
||||
self.list_view.clear()
|
||||
search_results = sorted(search_results, key=lambda song: (get_natural_key(song.theme_name),
|
||||
song.sort_key))
|
||||
search_results.sort(key=get_theme_key)
|
||||
for song in search_results:
|
||||
# Do not display temporary songs
|
||||
if song.temporary:
|
||||
|
@ -364,11 +391,14 @@ class SongMediaItem(MediaManagerItem):
|
|||
:param search_results: A list of db Song objects
|
||||
:return: None
|
||||
"""
|
||||
def get_cclinumber_key(song):
|
||||
"""Get the key to sort by"""
|
||||
return (get_natural_key(song.ccli_number), song.sort_key)
|
||||
|
||||
log.debug('display results CCLI number')
|
||||
self.list_view.clear()
|
||||
songs = sorted(search_results, key=lambda song: (get_natural_key(song.ccli_number),
|
||||
song.sort_key))
|
||||
for song in songs:
|
||||
search_results.sort(key=get_cclinumber_key)
|
||||
for song in search_results:
|
||||
# Do not display temporary songs
|
||||
if song.temporary:
|
||||
continue
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 815 B After Width: | Height: | Size: 1.0 KiB |
Binary file not shown.
After Width: | Height: | Size: 758 B |
Binary file not shown.
After Width: | Height: | Size: 884 B |
|
@ -148,6 +148,7 @@
|
|||
<file>media_audio.png</file>
|
||||
<file>media_video.png</file>
|
||||
<file>media_optical.png</file>
|
||||
<file>media_repeat.png</file>
|
||||
<file>slidecontroller_multimedia.png</file>
|
||||
<file>auto-start_active.png</file>
|
||||
<file>auto-start_inactive.png</file>
|
||||
|
@ -206,5 +207,6 @@
|
|||
</qresource>
|
||||
<qresource prefix="remotes">
|
||||
<file>android_app_qr.png</file>
|
||||
<file>ios_app_qr.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
|
|
@ -102,6 +102,7 @@ OPTIONAL_MODULES = [
|
|||
('nose', '(testing framework)', True),
|
||||
('mock', '(testing module)', sys.version_info[1] < 3),
|
||||
('jenkins', '(access jenkins api - package name: jenkins-webapi)', True),
|
||||
('pysword', '(import SWORD bibles)', True),
|
||||
]
|
||||
|
||||
w = sys.stdout.write
|
||||
|
|
|
@ -26,12 +26,12 @@ import sys
|
|||
from PyQt5 import QtWidgets
|
||||
|
||||
if sys.version_info[1] >= 3:
|
||||
from unittest.mock import ANY, MagicMock, patch, mock_open, call
|
||||
from unittest.mock import ANY, MagicMock, patch, mock_open, call, PropertyMock
|
||||
else:
|
||||
from mock import ANY, MagicMock, patch, mock_open, call
|
||||
from mock import ANY, MagicMock, patch, mock_open, call, PropertyMock
|
||||
|
||||
# Only one QApplication can be created. Use QtWidgets.QApplication.instance() when you need to "create" a QApplication.
|
||||
application = QtWidgets.QApplication([])
|
||||
application.setApplicationName('OpenLP')
|
||||
|
||||
__all__ = ['ANY', 'MagicMock', 'patch', 'mock_open', 'call', 'application']
|
||||
__all__ = ['ANY', 'MagicMock', 'patch', 'mock_open', 'call', 'application', 'PropertyMock']
|
||||
|
|
|
@ -23,13 +23,12 @@
|
|||
Package to test the openlp.core.ui.projector.networkutils package.
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from unittest import TestCase
|
||||
|
||||
from openlp.core.common import verify_ip_address, md5_hash, qmd5_hash
|
||||
|
||||
from tests.resources.projector.data import TEST_PIN, TEST_SALT, TEST_HASH
|
||||
|
||||
salt = TEST_SALT
|
||||
pin = TEST_PIN
|
||||
test_hash = TEST_HASH
|
||||
|
|
|
@ -197,6 +197,7 @@ FOOTER_CSS_BASE = """
|
|||
"""
|
||||
FOOTER_CSS = FOOTER_CSS_BASE % ('nowrap')
|
||||
FOOTER_CSS_WRAP = FOOTER_CSS_BASE % ('normal')
|
||||
FOOTER_CSS_INVALID = ''
|
||||
|
||||
|
||||
class Htmbuilder(TestCase, TestMixin):
|
||||
|
@ -359,6 +360,27 @@ class Htmbuilder(TestCase, TestMixin):
|
|||
# THEN: Footer should wrap
|
||||
self.assertEqual(FOOTER_CSS_WRAP, css, 'The footer strings should be equal.')
|
||||
|
||||
def build_footer_invalid_test(self):
|
||||
"""
|
||||
Test the build_footer_css() function
|
||||
"""
|
||||
# GIVEN: Create a theme.
|
||||
css = []
|
||||
item = MagicMock()
|
||||
item.theme_data = None
|
||||
item.footer = 'FAIL'
|
||||
height = 1024
|
||||
|
||||
# WHEN: Settings say that footer should wrap
|
||||
css.append(build_footer_css(item, height))
|
||||
item.theme_data = 'TEST'
|
||||
item.footer = None
|
||||
css.append(build_footer_css(item, height))
|
||||
|
||||
# THEN: Footer should wrap
|
||||
self.assertEqual(FOOTER_CSS_INVALID, css[0], 'The footer strings should be blank.')
|
||||
self.assertEqual(FOOTER_CSS_INVALID, css[1], 'The footer strings should be blank.')
|
||||
|
||||
def webkit_version_test(self):
|
||||
"""
|
||||
Test the webkit_version() function
|
||||
|
|
|
@ -107,3 +107,47 @@ class TestPJLink(TestCase):
|
|||
# THEN: process_inpt method should have been called with 31
|
||||
mock_process_inpt.called_with('31',
|
||||
"process_inpt should have been called with 31")
|
||||
|
||||
@patch.object(pjlink_test, 'projectorReceivedData')
|
||||
def projector_process_lamp_test(self, mock_projectorReceivedData):
|
||||
"""
|
||||
Test setting lamp on/off and hours
|
||||
"""
|
||||
# GIVEN: Test object
|
||||
pjlink = pjlink_test
|
||||
|
||||
# WHEN: Call process_command with lamp data
|
||||
pjlink.process_command('LAMP', '22222 1')
|
||||
|
||||
# THEN: Lamp should have been set with status=ON and hours=22222
|
||||
self.assertEquals(pjlink.lamp[0]['On'], True,
|
||||
'Lamp power status should have been set to TRUE')
|
||||
self.assertEquals(pjlink.lamp[0]['Hours'], 22222,
|
||||
'Lamp hours should have been set to 22222')
|
||||
|
||||
@patch.object(pjlink_test, 'projectorReceivedData')
|
||||
def projector_process_multiple_lamp_test(self, mock_projectorReceivedData):
|
||||
"""
|
||||
Test setting multiple lamp on/off and hours
|
||||
"""
|
||||
# GIVEN: Test object
|
||||
pjlink = pjlink_test
|
||||
|
||||
# WHEN: Call process_command with lamp data
|
||||
pjlink.process_command('LAMP', '11111 1 22222 0 33333 1')
|
||||
|
||||
# THEN: Lamp should have been set with proper lamp status
|
||||
self.assertEquals(len(pjlink.lamp), 3,
|
||||
'Projector should have 3 lamps specified')
|
||||
self.assertEquals(pjlink.lamp[0]['On'], True,
|
||||
'Lamp 1 power status should have been set to TRUE')
|
||||
self.assertEquals(pjlink.lamp[0]['Hours'], 11111,
|
||||
'Lamp 1 hours should have been set to 11111')
|
||||
self.assertEquals(pjlink.lamp[1]['On'], False,
|
||||
'Lamp 2 power status should have been set to FALSE')
|
||||
self.assertEquals(pjlink.lamp[1]['Hours'], 22222,
|
||||
'Lamp 2 hours should have been set to 22222')
|
||||
self.assertEquals(pjlink.lamp[2]['On'], True,
|
||||
'Lamp 3 power status should have been set to TRUE')
|
||||
self.assertEquals(pjlink.lamp[2]['Hours'], 33333,
|
||||
'Lamp 3 hours should have been set to 33333')
|
||||
|
|
|
@ -26,7 +26,7 @@ from unittest import TestCase, skipUnless
|
|||
|
||||
from PyQt5 import QtCore
|
||||
|
||||
from openlp.core.common import Registry, is_macosx
|
||||
from openlp.core.common import Registry, is_macosx, Settings
|
||||
from openlp.core.lib import ScreenList
|
||||
from openlp.core.ui import MainDisplay
|
||||
from openlp.core.ui.maindisplay import TRANSPARENT_STYLESHEET, OPAQUE_STYLESHEET
|
||||
|
@ -183,3 +183,43 @@ class TestMainDisplay(TestCase, TestMixin):
|
|||
'Window level should be NSMainMenuWindowLevel + 2')
|
||||
self.assertEqual(pyobjc_nsview.window().collectionBehavior(), NSWindowCollectionBehaviorManaged,
|
||||
'Window collection behavior should be NSWindowCollectionBehaviorManaged')
|
||||
|
||||
@patch(u'openlp.core.ui.maindisplay.Settings')
|
||||
def show_display_startup_logo_test(self, MockedSettings):
|
||||
# GIVEN: Mocked show_display, setting for logo visibility
|
||||
display = MagicMock()
|
||||
main_display = MainDisplay(display)
|
||||
main_display.frame = MagicMock()
|
||||
main_display.isHidden = MagicMock()
|
||||
main_display.isHidden.return_value = True
|
||||
main_display.setVisible = MagicMock()
|
||||
mocked_settings = MagicMock()
|
||||
mocked_settings.value.return_value = False
|
||||
MockedSettings.return_value = mocked_settings
|
||||
main_display.shake_web_view = MagicMock()
|
||||
|
||||
# WHEN: show_display is called.
|
||||
main_display.show_display()
|
||||
|
||||
# THEN: setVisible should had been called with "True"
|
||||
main_display.setVisible.assert_called_once_with(True)
|
||||
|
||||
@patch(u'openlp.core.ui.maindisplay.Settings')
|
||||
def show_display_hide_startup_logo_test(self, MockedSettings):
|
||||
# GIVEN: Mocked show_display, setting for logo visibility
|
||||
display = MagicMock()
|
||||
main_display = MainDisplay(display)
|
||||
main_display.frame = MagicMock()
|
||||
main_display.isHidden = MagicMock()
|
||||
main_display.isHidden.return_value = False
|
||||
main_display.setVisible = MagicMock()
|
||||
mocked_settings = MagicMock()
|
||||
mocked_settings.value.return_value = False
|
||||
MockedSettings.return_value = mocked_settings
|
||||
main_display.shake_web_view = MagicMock()
|
||||
|
||||
# WHEN: show_display is called.
|
||||
main_display.show_display()
|
||||
|
||||
# THEN: setVisible should had not been called
|
||||
main_display.setVisible.assert_not_called()
|
||||
|
|
|
@ -26,6 +26,8 @@ import os
|
|||
|
||||
from unittest import TestCase
|
||||
|
||||
from PyQt5 import QtWidgets
|
||||
|
||||
from openlp.core.ui.mainwindow import MainWindow
|
||||
from openlp.core.lib.ui import UiStrings
|
||||
from openlp.core.common.registry import Registry
|
||||
|
@ -148,7 +150,7 @@ class TestMainWindow(TestCase, TestMixin):
|
|||
|
||||
# THEN: the following registry functions should have been registered
|
||||
self.assertEqual(len(self.registry.service_list), 6, 'The registry should have 6 services.')
|
||||
self.assertEqual(len(self.registry.functions_list), 16, 'The registry should have 16 functions')
|
||||
self.assertEqual(len(self.registry.functions_list), 17, 'The registry should have 17 functions')
|
||||
self.assertTrue('application' in self.registry.service_list, 'The application should have been registered.')
|
||||
self.assertTrue('main_window' in self.registry.service_list, 'The main_window should have been registered.')
|
||||
self.assertTrue('media_controller' in self.registry.service_list, 'The media_controller should have been '
|
||||
|
@ -189,3 +191,57 @@ class TestMainWindow(TestCase, TestMixin):
|
|||
# THEN: The media manager dock is made visible
|
||||
self.assertEqual(0, mocked_media_manager_dock.setVisible.call_count)
|
||||
mocked_widget.on_focus.assert_called_with()
|
||||
|
||||
@patch('openlp.core.ui.mainwindow.MainWindow.plugin_manager')
|
||||
@patch('openlp.core.ui.mainwindow.MainWindow.first_time')
|
||||
@patch('openlp.core.ui.mainwindow.MainWindow.application')
|
||||
@patch('openlp.core.ui.mainwindow.FirstTimeForm')
|
||||
@patch('openlp.core.ui.mainwindow.QtWidgets.QMessageBox.warning')
|
||||
@patch('openlp.core.ui.mainwindow.Settings')
|
||||
def on_first_time_wizard_clicked_show_projectors_after_test(self, mocked_Settings, mocked_warning,
|
||||
mocked_FirstTimeForm, mocked_application,
|
||||
mocked_first_time,
|
||||
mocked_plugin_manager):
|
||||
# GIVEN: Main_window, patched things, patched "Yes" as confirmation to re-run wizard, settings to True.
|
||||
mocked_Settings_obj = MagicMock()
|
||||
mocked_Settings_obj.value.return_value = True
|
||||
mocked_Settings.return_value = mocked_Settings_obj
|
||||
mocked_warning.return_value = QtWidgets.QMessageBox.Yes
|
||||
mocked_FirstTimeForm_obj = MagicMock()
|
||||
mocked_FirstTimeForm_obj.was_cancelled = False
|
||||
mocked_FirstTimeForm.return_value = mocked_FirstTimeForm_obj
|
||||
mocked_plugin_manager.plugins = []
|
||||
self.main_window.projector_manager_dock = MagicMock()
|
||||
|
||||
# WHEN: on_first_time_wizard_clicked is called
|
||||
self.main_window.on_first_time_wizard_clicked()
|
||||
|
||||
# THEN: projector_manager_dock.setVisible should had been called once
|
||||
self.main_window.projector_manager_dock.setVisible.assert_called_once_with(True)
|
||||
|
||||
@patch('openlp.core.ui.mainwindow.MainWindow.plugin_manager')
|
||||
@patch('openlp.core.ui.mainwindow.MainWindow.first_time')
|
||||
@patch('openlp.core.ui.mainwindow.MainWindow.application')
|
||||
@patch('openlp.core.ui.mainwindow.FirstTimeForm')
|
||||
@patch('openlp.core.ui.mainwindow.QtWidgets.QMessageBox.warning')
|
||||
@patch('openlp.core.ui.mainwindow.Settings')
|
||||
def on_first_time_wizard_clicked_hide_projectors_after_test(self, mocked_Settings, mocked_warning,
|
||||
mocked_FirstTimeForm, mocked_application,
|
||||
mocked_first_time,
|
||||
mocked_plugin_manager):
|
||||
# GIVEN: Main_window, patched things, patched "Yes" as confirmation to re-run wizard, settings to False.
|
||||
mocked_Settings_obj = MagicMock()
|
||||
mocked_Settings_obj.value.return_value = False
|
||||
mocked_Settings.return_value = mocked_Settings_obj
|
||||
mocked_warning.return_value = QtWidgets.QMessageBox.Yes
|
||||
mocked_FirstTimeForm_obj = MagicMock()
|
||||
mocked_FirstTimeForm_obj.was_cancelled = False
|
||||
mocked_FirstTimeForm.return_value = mocked_FirstTimeForm_obj
|
||||
mocked_plugin_manager.plugins = []
|
||||
self.main_window.projector_manager_dock = MagicMock()
|
||||
|
||||
# WHEN: on_first_time_wizard_clicked is called
|
||||
self.main_window.on_first_time_wizard_clicked()
|
||||
|
||||
# THEN: projector_manager_dock.setVisible should had been called once
|
||||
self.main_window.projector_manager_dock.setVisible.assert_called_once_with(False)
|
||||
|
|
|
@ -24,7 +24,7 @@ This module contains tests for the openlp.core.lib.filedialog module
|
|||
"""
|
||||
from unittest import TestCase
|
||||
|
||||
from openlp.core.lib.colorbutton import ColorButton
|
||||
from openlp.core.ui.lib.colorbutton import ColorButton
|
||||
from tests.functional import MagicMock, call, patch
|
||||
|
||||
|
||||
|
@ -33,11 +33,11 @@ class TestColorDialog(TestCase):
|
|||
Test the :class:`~openlp.core.lib.colorbutton.ColorButton` class
|
||||
"""
|
||||
def setUp(self):
|
||||
self.change_color_patcher = patch('openlp.core.lib.colorbutton.ColorButton.change_color')
|
||||
self.clicked_patcher = patch('openlp.core.lib.colorbutton.ColorButton.clicked')
|
||||
self.color_changed_patcher = patch('openlp.core.lib.colorbutton.ColorButton.colorChanged')
|
||||
self.qt_gui_patcher = patch('openlp.core.lib.colorbutton.QtWidgets')
|
||||
self.translate_patcher = patch('openlp.core.lib.colorbutton.translate', **{'return_value': 'Tool Tip Text'})
|
||||
self.change_color_patcher = patch('openlp.core.ui.lib.colorbutton.ColorButton.change_color')
|
||||
self.clicked_patcher = patch('openlp.core.ui.lib.colorbutton.ColorButton.clicked')
|
||||
self.color_changed_patcher = patch('openlp.core.ui.lib.colorbutton.ColorButton.colorChanged')
|
||||
self.qt_gui_patcher = patch('openlp.core.ui.lib.colorbutton.QtWidgets')
|
||||
self.translate_patcher = patch('openlp.core.ui.lib.colorbutton.translate', **{'return_value': 'Tool Tip Text'})
|
||||
self.addCleanup(self.change_color_patcher.stop)
|
||||
self.addCleanup(self.clicked_patcher.stop)
|
||||
self.addCleanup(self.color_changed_patcher.stop)
|
||||
|
@ -55,7 +55,7 @@ class TestColorDialog(TestCase):
|
|||
"""
|
||||
|
||||
# GIVEN: The ColorButton class, a mocked change_color, setToolTip methods and clicked signal
|
||||
with patch('openlp.core.lib.colorbutton.ColorButton.setToolTip') as mocked_set_tool_tip:
|
||||
with patch('openlp.core.ui.lib.colorbutton.ColorButton.setToolTip') as mocked_set_tool_tip:
|
||||
|
||||
# WHEN: The ColorButton object is instantiated
|
||||
widget = ColorButton()
|
||||
|
@ -74,7 +74,7 @@ class TestColorDialog(TestCase):
|
|||
self.change_color_patcher.stop()
|
||||
|
||||
# GIVEN: An instance of the ColorButton object, and a mocked out setStyleSheet
|
||||
with patch('openlp.core.lib.colorbutton.ColorButton.setStyleSheet') as mocked_set_style_sheet:
|
||||
with patch('openlp.core.ui.lib.colorbutton.ColorButton.setStyleSheet') as mocked_set_style_sheet:
|
||||
widget = ColorButton()
|
||||
|
||||
# WHEN: Changing the color
|
||||
|
@ -123,7 +123,7 @@ class TestColorDialog(TestCase):
|
|||
"""
|
||||
|
||||
# GIVEN: An instance of ColorButton, with a mocked __init__
|
||||
with patch('openlp.core.lib.colorbutton.ColorButton.__init__', **{'return_value': None}):
|
||||
with patch('openlp.core.ui.lib.colorbutton.ColorButton.__init__', **{'return_value': None}):
|
||||
widget = ColorButton()
|
||||
|
||||
# WHEN: Setting the color property
|
|
@ -20,12 +20,12 @@
|
|||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||
###############################################################################
|
||||
"""
|
||||
Package to test the openlp.core.ui.listpreviewwidget package.
|
||||
Package to test the openlp.core.ui.lib.listpreviewwidget package.
|
||||
"""
|
||||
from unittest import TestCase
|
||||
|
||||
from openlp.core.common import Settings
|
||||
from openlp.core.ui.listpreviewwidget import ListPreviewWidget
|
||||
from openlp.core.ui.lib.listpreviewwidget import ListPreviewWidget
|
||||
from openlp.core.lib import ServiceItem
|
||||
|
||||
from tests.functional import MagicMock, patch, call
|
||||
|
@ -38,13 +38,13 @@ class TestListPreviewWidget(TestCase):
|
|||
Mock out stuff for all the tests
|
||||
"""
|
||||
# Mock self.parent().width()
|
||||
self.parent_patcher = patch('openlp.core.ui.listpreviewwidget.ListPreviewWidget.parent')
|
||||
self.parent_patcher = patch('openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.parent')
|
||||
self.mocked_parent = self.parent_patcher.start()
|
||||
self.mocked_parent.width.return_value = 100
|
||||
self.addCleanup(self.parent_patcher.stop)
|
||||
|
||||
# Mock Settings().value()
|
||||
self.Settings_patcher = patch('openlp.core.ui.listpreviewwidget.Settings')
|
||||
self.Settings_patcher = patch('openlp.core.ui.lib.listpreviewwidget.Settings')
|
||||
self.mocked_Settings = self.Settings_patcher.start()
|
||||
self.mocked_Settings_obj = MagicMock()
|
||||
self.mocked_Settings_obj.value.return_value = None
|
||||
|
@ -52,7 +52,7 @@ class TestListPreviewWidget(TestCase):
|
|||
self.addCleanup(self.Settings_patcher.stop)
|
||||
|
||||
# Mock self.viewport().width()
|
||||
self.viewport_patcher = patch('openlp.core.ui.listpreviewwidget.ListPreviewWidget.viewport')
|
||||
self.viewport_patcher = patch('openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.viewport')
|
||||
self.mocked_viewport = self.viewport_patcher.start()
|
||||
self.mocked_viewport_obj = MagicMock()
|
||||
self.mocked_viewport_obj.width.return_value = 200
|
||||
|
@ -72,8 +72,8 @@ class TestListPreviewWidget(TestCase):
|
|||
self.assertIsNotNone(list_preview_widget, 'The ListPreviewWidget object should not be None')
|
||||
self.assertEquals(list_preview_widget.screen_ratio, 1, 'Should not be called')
|
||||
|
||||
@patch(u'openlp.core.ui.listpreviewwidget.ListPreviewWidget.resizeRowsToContents')
|
||||
@patch(u'openlp.core.ui.listpreviewwidget.ListPreviewWidget.setRowHeight')
|
||||
@patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.resizeRowsToContents')
|
||||
@patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.setRowHeight')
|
||||
def replace_recalculate_layout_test_text(self, mocked_setRowHeight, mocked_resizeRowsToContents):
|
||||
"""
|
||||
Test if "Max height for non-text slides..." enabled, txt slides unchanged in replace_service_item & __recalc...
|
||||
|
@ -104,8 +104,8 @@ class TestListPreviewWidget(TestCase):
|
|||
self.assertEquals(mocked_resizeRowsToContents.call_count, 2, 'Should be called')
|
||||
self.assertEquals(mocked_setRowHeight.call_count, 0, 'Should not be called')
|
||||
|
||||
@patch(u'openlp.core.ui.listpreviewwidget.ListPreviewWidget.resizeRowsToContents')
|
||||
@patch(u'openlp.core.ui.listpreviewwidget.ListPreviewWidget.setRowHeight')
|
||||
@patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.resizeRowsToContents')
|
||||
@patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.setRowHeight')
|
||||
def replace_recalculate_layout_test_img(self, mocked_setRowHeight, mocked_resizeRowsToContents):
|
||||
"""
|
||||
Test if "Max height for non-text slides..." disabled, img slides unchanged in replace_service_item & __recalc...
|
||||
|
@ -130,16 +130,18 @@ class TestListPreviewWidget(TestCase):
|
|||
|
||||
# WHEN: __recalculate_layout() is called (via resizeEvent)
|
||||
list_preview_widget.resizeEvent(None)
|
||||
self.mocked_Settings_obj.value.return_value = None
|
||||
list_preview_widget.resizeEvent(None)
|
||||
|
||||
# THEN: resizeRowsToContents() should not be called, while setRowHeight() should be called
|
||||
# twice for each slide.
|
||||
self.assertEquals(mocked_resizeRowsToContents.call_count, 0, 'Should not be called')
|
||||
self.assertEquals(mocked_setRowHeight.call_count, 4, 'Should be called twice for each slide')
|
||||
calls = [call(0, 200), call(1, 200), call(0, 400), call(1, 400)]
|
||||
self.assertEquals(mocked_setRowHeight.call_count, 6, 'Should be called 3 times for each slide')
|
||||
calls = [call(0, 200), call(1, 200), call(0, 400), call(1, 400), call(0, 400), call(1, 400)]
|
||||
mocked_setRowHeight.assert_has_calls(calls)
|
||||
|
||||
@patch(u'openlp.core.ui.listpreviewwidget.ListPreviewWidget.resizeRowsToContents')
|
||||
@patch(u'openlp.core.ui.listpreviewwidget.ListPreviewWidget.setRowHeight')
|
||||
@patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.resizeRowsToContents')
|
||||
@patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.setRowHeight')
|
||||
def replace_recalculate_layout_test_img_max(self, mocked_setRowHeight, mocked_resizeRowsToContents):
|
||||
"""
|
||||
Test if "Max height for non-text slides..." enabled, img slides resized in replace_service_item & __recalc...
|
||||
|
@ -172,9 +174,9 @@ class TestListPreviewWidget(TestCase):
|
|||
calls = [call(0, 100), call(1, 100), call(0, 100), call(1, 100)]
|
||||
mocked_setRowHeight.assert_has_calls(calls)
|
||||
|
||||
@patch(u'openlp.core.ui.listpreviewwidget.ListPreviewWidget.resizeRowsToContents')
|
||||
@patch(u'openlp.core.ui.listpreviewwidget.ListPreviewWidget.setRowHeight')
|
||||
@patch(u'openlp.core.ui.listpreviewwidget.ListPreviewWidget.cellWidget')
|
||||
@patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.resizeRowsToContents')
|
||||
@patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.setRowHeight')
|
||||
@patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.cellWidget')
|
||||
def row_resized_test_text(self, mocked_cellWidget, mocked_setRowHeight, mocked_resizeRowsToContents):
|
||||
"""
|
||||
Test if "Max height for non-text slides..." enabled, text-based slides not affected in row_resized.
|
||||
|
@ -206,9 +208,9 @@ class TestListPreviewWidget(TestCase):
|
|||
# THEN: self.cellWidget(row, 0).children()[1].setMaximumWidth() should not be called
|
||||
self.assertEquals(mocked_cellWidget_child.setMaximumWidth.call_count, 0, 'Should not be called')
|
||||
|
||||
@patch(u'openlp.core.ui.listpreviewwidget.ListPreviewWidget.resizeRowsToContents')
|
||||
@patch(u'openlp.core.ui.listpreviewwidget.ListPreviewWidget.setRowHeight')
|
||||
@patch(u'openlp.core.ui.listpreviewwidget.ListPreviewWidget.cellWidget')
|
||||
@patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.resizeRowsToContents')
|
||||
@patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.setRowHeight')
|
||||
@patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.cellWidget')
|
||||
def row_resized_test_img(self, mocked_cellWidget, mocked_setRowHeight, mocked_resizeRowsToContents):
|
||||
"""
|
||||
Test if "Max height for non-text slides..." disabled, image-based slides not affected in row_resized.
|
||||
|
@ -236,13 +238,15 @@ class TestListPreviewWidget(TestCase):
|
|||
|
||||
# WHEN: row_resized() is called
|
||||
list_preview_widget.row_resized(0, 100, 150)
|
||||
self.mocked_Settings_obj.value.return_value = None
|
||||
list_preview_widget.row_resized(0, 100, 150)
|
||||
|
||||
# THEN: self.cellWidget(row, 0).children()[1].setMaximumWidth() should not be called
|
||||
self.assertEquals(mocked_cellWidget_child.setMaximumWidth.call_count, 0, 'Should not be called')
|
||||
|
||||
@patch(u'openlp.core.ui.listpreviewwidget.ListPreviewWidget.resizeRowsToContents')
|
||||
@patch(u'openlp.core.ui.listpreviewwidget.ListPreviewWidget.setRowHeight')
|
||||
@patch(u'openlp.core.ui.listpreviewwidget.ListPreviewWidget.cellWidget')
|
||||
@patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.resizeRowsToContents')
|
||||
@patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.setRowHeight')
|
||||
@patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.cellWidget')
|
||||
def row_resized_test_img_max(self, mocked_cellWidget, mocked_setRowHeight, mocked_resizeRowsToContents):
|
||||
"""
|
||||
Test if "Max height for non-text slides..." enabled, image-based slides are scaled in row_resized.
|
||||
|
@ -273,3 +277,101 @@ class TestListPreviewWidget(TestCase):
|
|||
|
||||
# THEN: self.cellWidget(row, 0).children()[1].setMaximumWidth() should be called
|
||||
mocked_cellWidget_child.setMaximumWidth.assert_called_once_with(150)
|
||||
|
||||
@patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.selectRow')
|
||||
@patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.scrollToItem')
|
||||
@patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.item')
|
||||
@patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.slide_count')
|
||||
def autoscroll_test_setting_invalid(self, mocked_slide_count, mocked_item, mocked_scrollToItem, mocked_selectRow):
|
||||
"""
|
||||
Test if 'advanced/autoscrolling' setting None or invalid, that no autoscrolling occurs on change_slide().
|
||||
"""
|
||||
# GIVEN: A setting for autoscrolling and a ListPreviewWidget.
|
||||
# Mock Settings().value('advanced/autoscrolling')
|
||||
self.mocked_Settings_obj.value.return_value = None
|
||||
# Mocked returns
|
||||
mocked_slide_count.return_value = 1
|
||||
mocked_item.return_value = None
|
||||
# init ListPreviewWidget and load service item
|
||||
list_preview_widget = ListPreviewWidget(None, 1)
|
||||
|
||||
# WHEN: change_slide() is called
|
||||
list_preview_widget.change_slide(0)
|
||||
self.mocked_Settings_obj.value.return_value = 1
|
||||
list_preview_widget.change_slide(0)
|
||||
self.mocked_Settings_obj.value.return_value = {'fail': 1}
|
||||
list_preview_widget.change_slide(0)
|
||||
self.mocked_Settings_obj.value.return_value = {'dist': 1, 'fail': 1}
|
||||
list_preview_widget.change_slide(0)
|
||||
self.mocked_Settings_obj.value.return_value = {'dist': 'fail', 'pos': 1}
|
||||
list_preview_widget.change_slide(0)
|
||||
self.mocked_Settings_obj.value.return_value = {'dist': 1, 'pos': 'fail'}
|
||||
list_preview_widget.change_slide(0)
|
||||
|
||||
# THEN: no further functions should be called
|
||||
self.assertEquals(mocked_slide_count.call_count, 0, 'Should not be called')
|
||||
self.assertEquals(mocked_scrollToItem.call_count, 0, 'Should not be called')
|
||||
self.assertEquals(mocked_selectRow.call_count, 0, 'Should not be called')
|
||||
self.assertEquals(mocked_item.call_count, 0, 'Should not be called')
|
||||
|
||||
@patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.selectRow')
|
||||
@patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.scrollToItem')
|
||||
@patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.item')
|
||||
@patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.slide_count')
|
||||
def autoscroll_test_dist_bounds(self, mocked_slide_count, mocked_item, mocked_scrollToItem, mocked_selectRow):
|
||||
"""
|
||||
Test if 'advanced/autoscrolling' setting asks to scroll beyond list bounds, that it does not beyond.
|
||||
"""
|
||||
# GIVEN: A setting for autoscrolling and a ListPreviewWidget.
|
||||
# Mock Settings().value('advanced/autoscrolling')
|
||||
self.mocked_Settings_obj.value.return_value = {'dist': -1, 'pos': 1}
|
||||
# Mocked returns
|
||||
mocked_slide_count.return_value = 1
|
||||
mocked_item.return_value = None
|
||||
# init ListPreviewWidget and load service item
|
||||
list_preview_widget = ListPreviewWidget(None, 1)
|
||||
|
||||
# WHEN: change_slide() is called
|
||||
list_preview_widget.change_slide(0)
|
||||
self.mocked_Settings_obj.value.return_value = {'dist': 1, 'pos': 1}
|
||||
list_preview_widget.change_slide(0)
|
||||
|
||||
# THEN: no further functions should be called
|
||||
self.assertEquals(mocked_slide_count.call_count, 3, 'Should be called')
|
||||
self.assertEquals(mocked_scrollToItem.call_count, 2, 'Should be called')
|
||||
self.assertEquals(mocked_selectRow.call_count, 2, 'Should be called')
|
||||
self.assertEquals(mocked_item.call_count, 2, 'Should be called')
|
||||
calls = [call(0, 0), call(0, 0)]
|
||||
mocked_item.assert_has_calls(calls)
|
||||
|
||||
@patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.selectRow')
|
||||
@patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.scrollToItem')
|
||||
@patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.item')
|
||||
@patch(u'openlp.core.ui.lib.listpreviewwidget.ListPreviewWidget.slide_count')
|
||||
def autoscroll_test_normal(self, mocked_slide_count, mocked_item, mocked_scrollToItem, mocked_selectRow):
|
||||
"""
|
||||
Test if 'advanced/autoscrolling' setting valid, autoscrolling called as expected.
|
||||
"""
|
||||
# GIVEN: A setting for autoscrolling and a ListPreviewWidget.
|
||||
# Mock Settings().value('advanced/autoscrolling')
|
||||
self.mocked_Settings_obj.value.return_value = {'dist': -1, 'pos': 1}
|
||||
# Mocked returns
|
||||
mocked_slide_count.return_value = 3
|
||||
mocked_item.return_value = None
|
||||
# init ListPreviewWidget and load service item
|
||||
list_preview_widget = ListPreviewWidget(None, 1)
|
||||
|
||||
# WHEN: change_slide() is called
|
||||
list_preview_widget.change_slide(1)
|
||||
self.mocked_Settings_obj.value.return_value = {'dist': 0, 'pos': 1}
|
||||
list_preview_widget.change_slide(1)
|
||||
self.mocked_Settings_obj.value.return_value = {'dist': 1, 'pos': 1}
|
||||
list_preview_widget.change_slide(1)
|
||||
|
||||
# THEN: no further functions should be called
|
||||
self.assertEquals(mocked_slide_count.call_count, 3, 'Should be called')
|
||||
self.assertEquals(mocked_scrollToItem.call_count, 3, 'Should be called')
|
||||
self.assertEquals(mocked_selectRow.call_count, 3, 'Should be called')
|
||||
self.assertEquals(mocked_item.call_count, 3, 'Should be called')
|
||||
calls = [call(0, 0), call(1, 0), call(2, 0)]
|
||||
mocked_item.assert_has_calls(calls)
|
|
@ -78,10 +78,11 @@ class TestMediaController(TestCase, TestMixin):
|
|||
"""
|
||||
Test that we don't try to play media when no players available
|
||||
"""
|
||||
# GIVEN: A mocked UiStrings, get_media_players, controller, display and service_item
|
||||
with patch('openlp.core.ui.media.mediacontroller.get_media_players') as mocked_get_media_players,\
|
||||
# GIVEN: A mocked UiStrings, get_used_players, controller, display and service_item
|
||||
with patch('openlp.core.ui.media.mediacontroller.MediaController._get_used_players') as \
|
||||
mocked_get_used_players,\
|
||||
patch('openlp.core.ui.media.mediacontroller.UiStrings') as mocked_uistrings:
|
||||
mocked_get_media_players.return_value = ([], '')
|
||||
mocked_get_used_players.return_value = ([])
|
||||
mocked_ret_uistrings = MagicMock()
|
||||
mocked_ret_uistrings.Automatic = 1
|
||||
mocked_uistrings.return_value = mocked_ret_uistrings
|
||||
|
@ -97,14 +98,14 @@ class TestMediaController(TestCase, TestMixin):
|
|||
# THEN: it should return False
|
||||
self.assertFalse(ret, '_check_file_type should return False when no mediaplayers are available.')
|
||||
|
||||
@patch('openlp.core.ui.media.mediacontroller.get_media_players')
|
||||
@patch('openlp.core.ui.media.mediacontroller.MediaController._get_used_players')
|
||||
@patch('openlp.core.ui.media.mediacontroller.UiStrings')
|
||||
def check_file_type_no_processor_test(self, mocked_uistrings, mocked_get_media_players):
|
||||
def check_file_type_no_processor_test(self, mocked_uistrings, mocked_get_used_players):
|
||||
"""
|
||||
Test that we don't try to play media when the processor for the service item is None
|
||||
"""
|
||||
# GIVEN: A mocked UiStrings, get_media_players, controller, display and service_item
|
||||
mocked_get_media_players.return_value = ([], '')
|
||||
mocked_get_used_players.return_value = ([], '')
|
||||
mocked_ret_uistrings = MagicMock()
|
||||
mocked_ret_uistrings.Automatic = 1
|
||||
mocked_uistrings.return_value = mocked_ret_uistrings
|
||||
|
@ -120,14 +121,14 @@ class TestMediaController(TestCase, TestMixin):
|
|||
# THEN: it should return False
|
||||
self.assertFalse(ret, '_check_file_type should return False when the processor for service_item is None.')
|
||||
|
||||
@patch('openlp.core.ui.media.mediacontroller.get_media_players')
|
||||
@patch('openlp.core.ui.media.mediacontroller.MediaController._get_used_players')
|
||||
@patch('openlp.core.ui.media.mediacontroller.UiStrings')
|
||||
def check_file_type_automatic_processor_test(self, mocked_uistrings, mocked_get_media_players):
|
||||
def check_file_type_automatic_processor_test(self, mocked_uistrings, mocked_get_used_players):
|
||||
"""
|
||||
Test that we can play media when players are available and we have a automatic processor from the service item
|
||||
"""
|
||||
# GIVEN: A mocked UiStrings, get_media_players, controller, display and service_item
|
||||
mocked_get_media_players.return_value = (['vlc', 'webkit'], '')
|
||||
mocked_get_used_players.return_value = (['vlc', 'webkit'])
|
||||
mocked_ret_uistrings = MagicMock()
|
||||
mocked_ret_uistrings.Automatic = 1
|
||||
mocked_uistrings.return_value = mocked_ret_uistrings
|
||||
|
@ -150,21 +151,21 @@ class TestMediaController(TestCase, TestMixin):
|
|||
self.assertTrue(ret, '_check_file_type should return True when mediaplayers are available and '
|
||||
'the service item has an automatic processor.')
|
||||
|
||||
@patch('openlp.core.ui.media.mediacontroller.get_media_players')
|
||||
@patch('openlp.core.ui.media.mediacontroller.MediaController._get_used_players')
|
||||
@patch('openlp.core.ui.media.mediacontroller.UiStrings')
|
||||
def check_file_type_processor_different_from_available_test(self, mocked_uistrings, mocked_get_media_players):
|
||||
def check_file_type_processor_different_from_available_test(self, mocked_uistrings, mocked_get_used_players):
|
||||
"""
|
||||
Test that we can play media when players available are different from the processor from the service item
|
||||
"""
|
||||
# GIVEN: A mocked UiStrings, get_media_players, controller, display and service_item
|
||||
mocked_get_media_players.return_value = (['phonon'], '')
|
||||
mocked_get_used_players.return_value = (['system'])
|
||||
mocked_ret_uistrings = MagicMock()
|
||||
mocked_ret_uistrings.Automatic = 'automatic'
|
||||
mocked_uistrings.return_value = mocked_ret_uistrings
|
||||
media_controller = MediaController()
|
||||
mocked_phonon = MagicMock()
|
||||
mocked_phonon.video_extensions_list = ['*.mp4']
|
||||
media_controller.media_players = {'phonon': mocked_phonon}
|
||||
media_controller.media_players = {'system': mocked_phonon}
|
||||
mocked_controller = MagicMock()
|
||||
mocked_suffix = MagicMock()
|
||||
mocked_suffix.return_value = 'mp4'
|
||||
|
|
|
@ -380,7 +380,6 @@ class TestVLCPlayer(TestCase, TestMixin):
|
|||
mocked_display.vlc_media_player.set_media.assert_called_with(mocked_vlc_media)
|
||||
mocked_vlc_media.parse.assert_called_with()
|
||||
mocked_volume.assert_called_with(mocked_display, 100)
|
||||
self.assertEqual(10, mocked_controller.media_info.length)
|
||||
self.assertTrue(result)
|
||||
|
||||
@patch('openlp.core.ui.media.vlcplayer.is_win')
|
||||
|
@ -426,7 +425,6 @@ class TestVLCPlayer(TestCase, TestMixin):
|
|||
mocked_display.vlc_media_player.set_media.assert_called_with(mocked_vlc_media)
|
||||
mocked_vlc_media.parse.assert_called_with()
|
||||
mocked_volume.assert_called_with(mocked_display, 100)
|
||||
self.assertEqual(10, mocked_controller.media_info.length)
|
||||
self.assertTrue(result)
|
||||
|
||||
@patch('openlp.core.ui.media.vlcplayer.is_win')
|
||||
|
@ -472,7 +470,6 @@ class TestVLCPlayer(TestCase, TestMixin):
|
|||
mocked_display.vlc_media_player.set_media.assert_called_with(mocked_vlc_media)
|
||||
mocked_vlc_media.parse.assert_called_with()
|
||||
mocked_volume.assert_called_with(mocked_display, 100)
|
||||
self.assertEqual(10, mocked_controller.media_info.length)
|
||||
self.assertTrue(result)
|
||||
|
||||
@patch('openlp.core.ui.media.vlcplayer.is_win')
|
||||
|
@ -628,7 +625,7 @@ class TestVLCPlayer(TestCase, TestMixin):
|
|||
mocked_display.controller = mocked_controller
|
||||
mocked_display.vlc_media_player.get_media.return_value = mocked_media
|
||||
vlc_player = VlcPlayer(None)
|
||||
vlc_player.state = MediaState.Paused
|
||||
vlc_player.set_state(MediaState.Paused, mocked_display)
|
||||
|
||||
# WHEN: play() is called
|
||||
with patch.object(vlc_player, 'media_state_wait') as mocked_media_state_wait, \
|
||||
|
@ -638,10 +635,8 @@ class TestVLCPlayer(TestCase, TestMixin):
|
|||
|
||||
# THEN: A bunch of things should happen to play the media
|
||||
mocked_thread.start.assert_called_with()
|
||||
self.assertEqual(50, mocked_controller.media_info.length)
|
||||
mocked_volume.assert_called_with(mocked_display, 100)
|
||||
mocked_controller.seek_slider.setMaximum.assert_called_with(50000)
|
||||
self.assertEqual(MediaState.Playing, vlc_player.state)
|
||||
self.assertEqual(MediaState.Playing, vlc_player.get_live_state())
|
||||
mocked_display.vlc_widget.raise_.assert_called_with()
|
||||
self.assertTrue(result, 'The value returned from play() should be True')
|
||||
|
||||
|
@ -661,7 +656,7 @@ class TestVLCPlayer(TestCase, TestMixin):
|
|||
mocked_display = MagicMock()
|
||||
mocked_display.controller = mocked_controller
|
||||
vlc_player = VlcPlayer(None)
|
||||
vlc_player.state = MediaState.Paused
|
||||
vlc_player.set_state(MediaState.Paused, mocked_display)
|
||||
|
||||
# WHEN: play() is called
|
||||
with patch.object(vlc_player, 'media_state_wait') as mocked_media_state_wait, \
|
||||
|
@ -695,7 +690,7 @@ class TestVLCPlayer(TestCase, TestMixin):
|
|||
mocked_display = MagicMock()
|
||||
mocked_display.controller = mocked_controller
|
||||
vlc_player = VlcPlayer(None)
|
||||
vlc_player.state = MediaState.Paused
|
||||
vlc_player.set_state(MediaState.Paused, mocked_display)
|
||||
|
||||
# WHEN: play() is called
|
||||
with patch.object(vlc_player, 'media_state_wait') as mocked_media_state_wait, \
|
||||
|
@ -709,10 +704,8 @@ class TestVLCPlayer(TestCase, TestMixin):
|
|||
mocked_display.vlc_media_player.play.assert_called_with()
|
||||
mocked_display.vlc_media_player.audio_set_track.assert_called_with(1)
|
||||
mocked_display.vlc_media_player.video_set_spu.assert_called_with(1)
|
||||
self.assertEqual(50, mocked_controller.media_info.length)
|
||||
mocked_volume.assert_called_with(mocked_display, 100)
|
||||
mocked_controller.seek_slider.setMaximum.assert_called_with(50000)
|
||||
self.assertEqual(MediaState.Playing, vlc_player.state)
|
||||
self.assertEqual(MediaState.Playing, vlc_player.get_live_state())
|
||||
mocked_display.vlc_widget.raise_.assert_called_with()
|
||||
self.assertTrue(result, 'The value returned from play() should be True')
|
||||
|
||||
|
@ -739,7 +732,7 @@ class TestVLCPlayer(TestCase, TestMixin):
|
|||
mocked_display.vlc_media.get_state.assert_called_with()
|
||||
mocked_display.vlc_media_player.pause.assert_called_with()
|
||||
mocked_media_state_wait.assert_called_with(mocked_display, 2)
|
||||
self.assertEqual(MediaState.Paused, vlc_player.state)
|
||||
self.assertEqual(MediaState.Paused, vlc_player.get_live_state())
|
||||
|
||||
@patch('openlp.core.ui.media.vlcplayer.get_vlc')
|
||||
def pause_not_playing_test(self, mocked_get_vlc):
|
||||
|
@ -805,7 +798,7 @@ class TestVLCPlayer(TestCase, TestMixin):
|
|||
# THEN: A thread should have been started to stop VLC
|
||||
mocked_threading.Thread.assert_called_with(target=mocked_stop)
|
||||
mocked_thread.start.assert_called_with()
|
||||
self.assertEqual(MediaState.Stopped, vlc_player.state)
|
||||
self.assertEqual(MediaState.Stopped, vlc_player.get_live_state())
|
||||
|
||||
def volume_test(self):
|
||||
"""
|
||||
|
@ -900,10 +893,10 @@ class TestVLCPlayer(TestCase, TestMixin):
|
|||
# WHEN: reset() is called
|
||||
vlc_player.reset(mocked_display)
|
||||
|
||||
# THEN: The media should be stopped and invsibile
|
||||
# THEN: The media should be stopped and invisible
|
||||
mocked_display.vlc_media_player.stop.assert_called_with()
|
||||
mocked_display.vlc_widget.setVisible.assert_called_with(False)
|
||||
self.assertEqual(MediaState.Off, vlc_player.state)
|
||||
self.assertEqual(MediaState.Off, vlc_player.get_live_state())
|
||||
|
||||
def set_visible_has_own_widget_test(self):
|
||||
"""
|
||||
|
|
|
@ -114,49 +114,3 @@ class TestMediaItem(TestCase, TestMixin):
|
|||
mocked_list_view.selectAll.assert_called_once_with()
|
||||
self.assertEqual(self.media_item.search_results, {})
|
||||
self.assertEqual(self.media_item.second_search_results, {})
|
||||
|
||||
def on_quick_reference_search_test(self):
|
||||
"""
|
||||
BOOM BOOM BANANAS
|
||||
"""
|
||||
|
||||
# GIVEN: A mocked build_display_results which returns an empty list
|
||||
self.media_item.quickVersionComboBox = MagicMock()
|
||||
self.media_item.quickSecondComboBox = MagicMock()
|
||||
self.media_item.quick_search_edit = MagicMock()
|
||||
|
||||
#mocked_text = self.media_item()
|
||||
#mocked_text.text.return_value = 'Gen. 1'
|
||||
#self.media_item.text = mocked_text
|
||||
#self.media_item.text.return_value = 'Gen. 1'
|
||||
# self.mocked_main_window.information_message = MagicMock()
|
||||
|
||||
self.media_item.search_results = MagicMock()
|
||||
self.media_item.advancedSearchButton = MagicMock()
|
||||
self.media_item.advancedSearchButton.setEnabled = MagicMock()
|
||||
|
||||
# WHEN: Calling display_results with a single bible version
|
||||
self.media_item.banana()
|
||||
|
||||
# THEN: No items should be added to the list, and select all should have been called.
|
||||
# self.assertEqual(0, self.mocked_main_window.information_message, 'lama')
|
||||
# mocked_media_item.assert_called_with(mocked_main_window.information_message)
|
||||
# self.mocked_text.text.assert_called_with('Gen. 1')
|
||||
# mocked_process_item.assert_called_once_with(mocked_item, 7)
|
||||
self.media_item.advancedSearchButton.setEnabled.assert_called_once_with(True)
|
||||
|
||||
|
||||
"""
|
||||
def on_quick_reference_search_test(self):
|
||||
|
||||
Test the display_results method a large number of results (> 100) are returned
|
||||
|
||||
|
||||
# GIVEN: A mocked build_display_results which returns a large list of results
|
||||
media_item = BibleMediaItem(MagicMock)
|
||||
|
||||
# WHEN: Calling display_results
|
||||
#self.media_item.on_quick_reference_search()
|
||||
|
||||
# THEN: addItem should have been called 100 times, and the lsit items should not be selected.
|
||||
"""
|
|
@ -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-2016 OpenLP Developers #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# This program is free software; you can redistribute it and/or modify it #
|
||||
# under the terms of the GNU General Public License as published by the Free #
|
||||
# Software Foundation; version 2 of the License. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT #
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
|
||||
# more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License along #
|
||||
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||
###############################################################################
|
||||
"""
|
||||
This module contains tests for the SWORD Bible importer.
|
||||
"""
|
||||
|
||||
import os
|
||||
import json
|
||||
from unittest import TestCase, SkipTest
|
||||
|
||||
from tests.functional import MagicMock, patch
|
||||
try:
|
||||
from openlp.plugins.bibles.lib.sword import SwordBible
|
||||
except ImportError:
|
||||
raise SkipTest('PySword is not installed, skipping SWORD test.')
|
||||
from openlp.plugins.bibles.lib.db import BibleDB
|
||||
|
||||
TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__),
|
||||
'..', '..', '..', 'resources', 'bibles'))
|
||||
|
||||
|
||||
class TestSwordImport(TestCase):
|
||||
"""
|
||||
Test the functions in the :mod:`swordimport` module.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.registry_patcher = patch('openlp.plugins.bibles.lib.db.Registry')
|
||||
self.registry_patcher.start()
|
||||
self.manager_patcher = patch('openlp.plugins.bibles.lib.db.Manager')
|
||||
self.manager_patcher.start()
|
||||
|
||||
def tearDown(self):
|
||||
self.registry_patcher.stop()
|
||||
self.manager_patcher.stop()
|
||||
|
||||
def create_importer_test(self):
|
||||
"""
|
||||
Test creating an instance of the Sword file importer
|
||||
"""
|
||||
# GIVEN: A mocked out "manager"
|
||||
mocked_manager = MagicMock()
|
||||
|
||||
# WHEN: An importer object is created
|
||||
importer = SwordBible(mocked_manager, path='.', name='.', filename='', sword_key='', sword_path='')
|
||||
|
||||
# THEN: The importer should be an instance of BibleDB
|
||||
self.assertIsInstance(importer, BibleDB)
|
||||
|
||||
@patch('openlp.plugins.bibles.lib.sword.SwordBible.application')
|
||||
@patch('openlp.plugins.bibles.lib.sword.modules')
|
||||
@patch('openlp.plugins.bibles.lib.db.BiblesResourcesDB')
|
||||
def simple_import_test(self, mocked_bible_res_db, mocked_pysword_modules, mocked_application):
|
||||
"""
|
||||
Test that a simple SWORD import works
|
||||
"""
|
||||
# GIVEN: Test files with a mocked out "manager", "import_wizard", and mocked functions
|
||||
# get_book_ref_id_by_name, create_verse, create_book, session and get_language.
|
||||
# Also mocked pysword structures
|
||||
mocked_manager = MagicMock()
|
||||
mocked_import_wizard = MagicMock()
|
||||
importer = SwordBible(mocked_manager, path='.', name='.', filename='', sword_key='', sword_path='')
|
||||
result_file = open(os.path.join(TEST_PATH, 'dk1933.json'), 'rb')
|
||||
test_data = json.loads(result_file.read().decode())
|
||||
importer.wizard = mocked_import_wizard
|
||||
importer.get_book_ref_id_by_name = MagicMock()
|
||||
importer.create_verse = MagicMock()
|
||||
importer.create_book = MagicMock()
|
||||
importer.session = MagicMock()
|
||||
mocked_bible_res_db.get_language.return_value = 'Danish'
|
||||
mocked_bible = MagicMock()
|
||||
mocked_genesis = MagicMock()
|
||||
mocked_genesis.name = 'Genesis'
|
||||
mocked_genesis.num_chapters = 1
|
||||
books = {'ot': [mocked_genesis]}
|
||||
mocked_structure = MagicMock()
|
||||
mocked_structure.get_books.return_value = books
|
||||
mocked_bible.get_structure.return_value = mocked_structure
|
||||
mocked_bible.get_iter.return_value = [verse[1] for verse in test_data['verses']]
|
||||
mocked_module = MagicMock()
|
||||
mocked_module.get_bible_from_module.return_value = mocked_bible
|
||||
mocked_pysword_modules.SwordModules.return_value = mocked_module
|
||||
|
||||
# WHEN: Importing bible file
|
||||
importer.do_import()
|
||||
|
||||
# THEN: The create_verse() method should have been called with each verse in the file.
|
||||
self.assertTrue(importer.create_verse.called)
|
||||
for verse_tag, verse_text in test_data['verses']:
|
||||
importer.create_verse.assert_any_call(importer.create_book().id, 1, int(verse_tag), verse_text)
|
|
@ -25,7 +25,7 @@ Test the media plugin
|
|||
from unittest import TestCase
|
||||
|
||||
from openlp.core import Registry
|
||||
from openlp.plugins.media.mediaplugin import MediaPlugin
|
||||
from openlp.plugins.media.mediaplugin import MediaPlugin, process_check_binary
|
||||
|
||||
from tests.functional import MagicMock, patch
|
||||
from tests.helpers.testmixin import TestMixin
|
||||
|
@ -54,8 +54,6 @@ class MediaPluginTest(TestCase, TestMixin):
|
|||
media_plugin.initialise()
|
||||
|
||||
# THEN: The settings should be upgraded and the base initialise() method should be called
|
||||
mocked_settings.get_files_from_config.assert_called_with(media_plugin)
|
||||
mocked_settings.setValue.assert_called_with('media/media files', True)
|
||||
mocked_initialise.assert_called_with()
|
||||
|
||||
def test_about_text(self):
|
||||
|
@ -65,3 +63,29 @@ class MediaPluginTest(TestCase, TestMixin):
|
|||
self.assertIsInstance(MediaPlugin.about(), str)
|
||||
# THEN: about() should return a non-empty string
|
||||
self.assertNotEquals(len(MediaPlugin.about()), 0)
|
||||
|
||||
@patch('openlp.plugins.media.mediaplugin.check_binary_exists')
|
||||
def process_check_binary_pass_test(self, mocked_checked_binary_exists):
|
||||
"""
|
||||
Test that the Process check returns true if found
|
||||
"""
|
||||
# GIVEN: A media plugin instance
|
||||
# WHEN: function is called with the correct name
|
||||
mocked_checked_binary_exists.return_value = str.encode('MediaInfo Command line')
|
||||
result = process_check_binary('MediaInfo')
|
||||
|
||||
# THEN: The the result should be True
|
||||
self.assertTrue(result, 'Mediainfo should have been found')
|
||||
|
||||
@patch('openlp.plugins.media.mediaplugin.check_binary_exists')
|
||||
def process_check_binary_fail_test(self, mocked_checked_binary_exists):
|
||||
"""
|
||||
Test that the Process check returns false if not found
|
||||
"""
|
||||
# GIVEN: A media plugin instance
|
||||
# WHEN: function is called with the wrong name
|
||||
mocked_checked_binary_exists.return_value = str.encode('MediaInfo1 Command line')
|
||||
result = process_check_binary("MediaInfo1")
|
||||
|
||||
# THEN: The the result should be True
|
||||
self.assertFalse(result, "Mediainfo should not have been found")
|
||||
|
|
|
@ -26,7 +26,7 @@ from unittest import TestCase
|
|||
|
||||
from openlp.plugins.songs.lib import VerseType, clean_string, clean_title, strip_rtf
|
||||
from openlp.plugins.songs.lib.songcompare import songs_probably_equal, _remove_typos, _op_length
|
||||
from tests.functional import patch, MagicMock
|
||||
from tests.functional import patch, MagicMock, PropertyMock
|
||||
|
||||
|
||||
class TestLib(TestCase):
|
||||
|
@ -477,3 +477,27 @@ class TestVerseType(TestCase):
|
|||
|
||||
# THEN: The result should be None
|
||||
self.assertIsNone(result, 'The result should be None, but was "%s"' % result)
|
||||
|
||||
@patch('openlp.plugins.songs.lib.VerseType.translated_tags', new_callable=PropertyMock, return_value=['x'])
|
||||
def from_loose_input_with_invalid_input_test(self, mocked_translated_tags):
|
||||
"""
|
||||
Test that the from_loose_input() method returns a sane default when passed an invalid tag and None as default.
|
||||
"""
|
||||
# GIVEN: A mocked VerseType.translated_tags
|
||||
# WHEN: We run the from_loose_input() method with an invalid verse type, we get the specified default back
|
||||
result = VerseType.from_loose_input('m', None)
|
||||
|
||||
# THEN: The result should be None
|
||||
self.assertIsNone(result, 'The result should be None, but was "%s"' % result)
|
||||
|
||||
@patch('openlp.plugins.songs.lib.VerseType.translated_tags', new_callable=PropertyMock, return_value=['x'])
|
||||
def from_loose_input_with_valid_input_test(self, mocked_translated_tags):
|
||||
"""
|
||||
Test that the from_loose_input() method returns valid output on valid input.
|
||||
"""
|
||||
# GIVEN: A mocked VerseType.translated_tags
|
||||
# WHEN: We run the from_loose_input() method with a valid verse type, we get the expected VerseType back
|
||||
result = VerseType.from_loose_input('v')
|
||||
|
||||
# THEN: The result should be a Verse
|
||||
self.assertEqual(result, VerseType.Verse, 'The result should be a verse, but was "%s"' % result)
|
||||
|
|
|
@ -53,6 +53,7 @@ class TestMediaItem(TestCase, TestMixin):
|
|||
self.media_item.list_view.save_auto_select_id = MagicMock()
|
||||
self.media_item.list_view.clear = MagicMock()
|
||||
self.media_item.list_view.addItem = MagicMock()
|
||||
self.media_item.list_view.setCurrentItem = MagicMock()
|
||||
self.media_item.auto_select_id = -1
|
||||
self.media_item.display_songbook = False
|
||||
self.media_item.display_copyright_symbol = False
|
||||
|
@ -79,13 +80,22 @@ class TestMediaItem(TestCase, TestMixin):
|
|||
mock_song.title = 'My Song'
|
||||
mock_song.sort_key = 'My Song'
|
||||
mock_song.authors = []
|
||||
mock_song_temp = MagicMock()
|
||||
mock_song_temp.id = 2
|
||||
mock_song_temp.title = 'My Temporary'
|
||||
mock_song_temp.sort_key = 'My Temporary'
|
||||
mock_song_temp.authors = []
|
||||
mock_author = MagicMock()
|
||||
mock_author.display_name = 'My Author'
|
||||
mock_song.authors.append(mock_author)
|
||||
mock_song_temp.authors.append(mock_author)
|
||||
mock_song.temporary = False
|
||||
mock_song_temp.temporary = True
|
||||
mock_search_results.append(mock_song)
|
||||
mock_search_results.append(mock_song_temp)
|
||||
mock_qlist_widget = MagicMock()
|
||||
MockedQListWidgetItem.return_value = mock_qlist_widget
|
||||
self.media_item.auto_select_id = 1
|
||||
|
||||
# WHEN: I display song search results
|
||||
self.media_item.display_results_song(mock_search_results)
|
||||
|
@ -93,9 +103,10 @@ class TestMediaItem(TestCase, TestMixin):
|
|||
# THEN: The current list view is cleared, the widget is created, and the relevant attributes set
|
||||
self.media_item.list_view.clear.assert_called_with()
|
||||
self.media_item.save_auto_select_id.assert_called_with()
|
||||
MockedQListWidgetItem.assert_called_with('My Song (My Author)')
|
||||
mock_qlist_widget.setData.assert_called_with(MockedUserRole, mock_song.id)
|
||||
self.media_item.list_view.addItem.assert_called_with(mock_qlist_widget)
|
||||
MockedQListWidgetItem.assert_called_once_with('My Song (My Author)')
|
||||
mock_qlist_widget.setData.assert_called_once_with(MockedUserRole, mock_song.id)
|
||||
self.media_item.list_view.addItem.assert_called_once_with(mock_qlist_widget)
|
||||
self.media_item.list_view.setCurrentItem.assert_called_with(mock_qlist_widget)
|
||||
|
||||
def display_results_author_test(self):
|
||||
"""
|
||||
|
@ -107,13 +118,19 @@ class TestMediaItem(TestCase, TestMixin):
|
|||
mock_search_results = []
|
||||
mock_author = MagicMock()
|
||||
mock_song = MagicMock()
|
||||
mock_song_temp = MagicMock()
|
||||
mock_author.display_name = 'My Author'
|
||||
mock_author.songs = []
|
||||
mock_song.id = 1
|
||||
mock_song.title = 'My Song'
|
||||
mock_song.sort_key = 'My Song'
|
||||
mock_song.temporary = False
|
||||
mock_song_temp.id = 2
|
||||
mock_song_temp.title = 'My Temporary'
|
||||
mock_song_temp.sort_key = 'My Temporary'
|
||||
mock_song_temp.temporary = True
|
||||
mock_author.songs.append(mock_song)
|
||||
mock_author.songs.append(mock_song_temp)
|
||||
mock_search_results.append(mock_author)
|
||||
mock_qlist_widget = MagicMock()
|
||||
MockedQListWidgetItem.return_value = mock_qlist_widget
|
||||
|
@ -123,9 +140,9 @@ class TestMediaItem(TestCase, TestMixin):
|
|||
|
||||
# THEN: The current list view is cleared, the widget is created, and the relevant attributes set
|
||||
self.media_item.list_view.clear.assert_called_with()
|
||||
MockedQListWidgetItem.assert_called_with('My Author (My Song)')
|
||||
mock_qlist_widget.setData.assert_called_with(MockedUserRole, mock_song.id)
|
||||
self.media_item.list_view.addItem.assert_called_with(mock_qlist_widget)
|
||||
MockedQListWidgetItem.assert_called_once_with('My Author (My Song)')
|
||||
mock_qlist_widget.setData.assert_called_once_with(MockedUserRole, mock_song.id)
|
||||
self.media_item.list_view.addItem.assert_called_once_with(mock_qlist_widget)
|
||||
|
||||
def display_results_book_test(self):
|
||||
"""
|
||||
|
@ -136,17 +153,27 @@ class TestMediaItem(TestCase, TestMixin):
|
|||
patch('openlp.core.lib.QtCore.Qt.UserRole') as MockedUserRole:
|
||||
mock_search_results = []
|
||||
mock_songbook_entry = MagicMock()
|
||||
mock_songbook_entry_temp = MagicMock()
|
||||
mock_songbook = MagicMock()
|
||||
mock_song = MagicMock()
|
||||
mock_song_temp = MagicMock()
|
||||
mock_songbook_entry.entry = '1'
|
||||
mock_songbook_entry_temp.entry = '2'
|
||||
mock_songbook.name = 'My Book'
|
||||
mock_song.id = 1
|
||||
mock_song.title = 'My Song'
|
||||
mock_song.sort_key = 'My Song'
|
||||
mock_song.temporary = False
|
||||
mock_song_temp.id = 2
|
||||
mock_song_temp.title = 'My Temporary'
|
||||
mock_song_temp.sort_key = 'My Temporary'
|
||||
mock_song_temp.temporary = True
|
||||
mock_songbook_entry.song = mock_song
|
||||
mock_songbook_entry.songbook = mock_songbook
|
||||
mock_songbook_entry_temp.song = mock_song_temp
|
||||
mock_songbook_entry_temp.songbook = mock_songbook
|
||||
mock_search_results.append(mock_songbook_entry)
|
||||
mock_search_results.append(mock_songbook_entry_temp)
|
||||
mock_qlist_widget = MagicMock()
|
||||
MockedQListWidgetItem.return_value = mock_qlist_widget
|
||||
|
||||
|
@ -155,9 +182,9 @@ class TestMediaItem(TestCase, TestMixin):
|
|||
|
||||
# THEN: The current list view is cleared, the widget is created, and the relevant attributes set
|
||||
self.media_item.list_view.clear.assert_called_with()
|
||||
MockedQListWidgetItem.assert_called_with('My Book #1: My Song')
|
||||
mock_qlist_widget.setData.assert_called_with(MockedUserRole, mock_songbook_entry.song.id)
|
||||
self.media_item.list_view.addItem.assert_called_with(mock_qlist_widget)
|
||||
MockedQListWidgetItem.assert_called_once_with('My Book #1: My Song')
|
||||
mock_qlist_widget.setData.assert_called_once_with(MockedUserRole, mock_songbook_entry.song.id)
|
||||
self.media_item.list_view.addItem.assert_called_once_with(mock_qlist_widget)
|
||||
|
||||
def display_results_topic_test(self):
|
||||
"""
|
||||
|
@ -169,13 +196,19 @@ class TestMediaItem(TestCase, TestMixin):
|
|||
mock_search_results = []
|
||||
mock_topic = MagicMock()
|
||||
mock_song = MagicMock()
|
||||
mock_song_temp = MagicMock()
|
||||
mock_topic.name = 'My Topic'
|
||||
mock_topic.songs = []
|
||||
mock_song.id = 1
|
||||
mock_song.title = 'My Song'
|
||||
mock_song.sort_key = 'My Song'
|
||||
mock_song.temporary = False
|
||||
mock_song_temp.id = 2
|
||||
mock_song_temp.title = 'My Temporary'
|
||||
mock_song_temp.sort_key = 'My Temporary'
|
||||
mock_song_temp.temporary = True
|
||||
mock_topic.songs.append(mock_song)
|
||||
mock_topic.songs.append(mock_song_temp)
|
||||
mock_search_results.append(mock_topic)
|
||||
mock_qlist_widget = MagicMock()
|
||||
MockedQListWidgetItem.return_value = mock_qlist_widget
|
||||
|
@ -185,9 +218,9 @@ class TestMediaItem(TestCase, TestMixin):
|
|||
|
||||
# THEN: The current list view is cleared, the widget is created, and the relevant attributes set
|
||||
self.media_item.list_view.clear.assert_called_with()
|
||||
MockedQListWidgetItem.assert_called_with('My Topic (My Song)')
|
||||
mock_qlist_widget.setData.assert_called_with(MockedUserRole, mock_song.id)
|
||||
self.media_item.list_view.addItem.assert_called_with(mock_qlist_widget)
|
||||
MockedQListWidgetItem.assert_called_once_with('My Topic (My Song)')
|
||||
mock_qlist_widget.setData.assert_called_once_with(MockedUserRole, mock_song.id)
|
||||
self.media_item.list_view.addItem.assert_called_once_with(mock_qlist_widget)
|
||||
|
||||
def display_results_themes_test(self):
|
||||
"""
|
||||
|
@ -198,12 +231,19 @@ class TestMediaItem(TestCase, TestMixin):
|
|||
patch('openlp.core.lib.QtCore.Qt.UserRole') as MockedUserRole:
|
||||
mock_search_results = []
|
||||
mock_song = MagicMock()
|
||||
mock_song_temp = MagicMock()
|
||||
mock_song.id = 1
|
||||
mock_song.title = 'My Song'
|
||||
mock_song.sort_key = 'My Song'
|
||||
mock_song.theme_name = 'My Theme'
|
||||
mock_song.temporary = False
|
||||
mock_song_temp.id = 2
|
||||
mock_song_temp.title = 'My Temporary'
|
||||
mock_song_temp.sort_key = 'My Temporary'
|
||||
mock_song_temp.theme_name = 'My Theme'
|
||||
mock_song_temp.temporary = True
|
||||
mock_search_results.append(mock_song)
|
||||
mock_search_results.append(mock_song_temp)
|
||||
mock_qlist_widget = MagicMock()
|
||||
MockedQListWidgetItem.return_value = mock_qlist_widget
|
||||
|
||||
|
@ -212,9 +252,9 @@ class TestMediaItem(TestCase, TestMixin):
|
|||
|
||||
# THEN: The current list view is cleared, the widget is created, and the relevant attributes set
|
||||
self.media_item.list_view.clear.assert_called_with()
|
||||
MockedQListWidgetItem.assert_called_with('My Theme (My Song)')
|
||||
mock_qlist_widget.setData.assert_called_with(MockedUserRole, mock_song.id)
|
||||
self.media_item.list_view.addItem.assert_called_with(mock_qlist_widget)
|
||||
MockedQListWidgetItem.assert_called_once_with('My Theme (My Song)')
|
||||
mock_qlist_widget.setData.assert_called_once_with(MockedUserRole, mock_song.id)
|
||||
self.media_item.list_view.addItem.assert_called_once_with(mock_qlist_widget)
|
||||
|
||||
def display_results_cclinumber_test(self):
|
||||
"""
|
||||
|
@ -225,12 +265,19 @@ class TestMediaItem(TestCase, TestMixin):
|
|||
patch('openlp.core.lib.QtCore.Qt.UserRole') as MockedUserRole:
|
||||
mock_search_results = []
|
||||
mock_song = MagicMock()
|
||||
mock_song_temp = MagicMock()
|
||||
mock_song.id = 1
|
||||
mock_song.title = 'My Song'
|
||||
mock_song.sort_key = 'My Song'
|
||||
mock_song.ccli_number = '12345'
|
||||
mock_song.temporary = False
|
||||
mock_song_temp.id = 2
|
||||
mock_song_temp.title = 'My Temporary'
|
||||
mock_song_temp.sort_key = 'My Temporary'
|
||||
mock_song_temp.ccli_number = '12346'
|
||||
mock_song_temp.temporary = True
|
||||
mock_search_results.append(mock_song)
|
||||
mock_search_results.append(mock_song_temp)
|
||||
mock_qlist_widget = MagicMock()
|
||||
MockedQListWidgetItem.return_value = mock_qlist_widget
|
||||
|
||||
|
@ -239,9 +286,9 @@ class TestMediaItem(TestCase, TestMixin):
|
|||
|
||||
# THEN: The current list view is cleared, the widget is created, and the relevant attributes set
|
||||
self.media_item.list_view.clear.assert_called_with()
|
||||
MockedQListWidgetItem.assert_called_with('12345 (My Song)')
|
||||
mock_qlist_widget.setData.assert_called_with(MockedUserRole, mock_song.id)
|
||||
self.media_item.list_view.addItem.assert_called_with(mock_qlist_widget)
|
||||
MockedQListWidgetItem.assert_called_once_with('12345 (My Song)')
|
||||
mock_qlist_widget.setData.assert_called_once_with(MockedUserRole, mock_song.id)
|
||||
self.media_item.list_view.addItem.assert_called_once_with(mock_qlist_widget)
|
||||
|
||||
def build_song_footer_one_author_test(self):
|
||||
"""
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||
|
||||
###############################################################################
|
||||
# OpenLP - Open Source Lyrics Projection #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Copyright (c) 2008-2016 OpenLP Developers #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# This program is free software; you can redistribute it and/or modify it #
|
||||
# under the terms of the GNU General Public License as published by the Free #
|
||||
# Software Foundation; version 2 of the License. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT #
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
|
||||
# more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License along #
|
||||
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||
###############################################################################
|
||||
"""
|
||||
This module contains tests for the OpenLP song importer.
|
||||
"""
|
||||
from unittest import TestCase
|
||||
|
||||
from openlp.plugins.songs.lib.importers.openlp import OpenLPSongImport
|
||||
from openlp.core.common import Registry
|
||||
from tests.functional import patch, MagicMock
|
||||
|
||||
|
||||
class TestOpenLPImport(TestCase):
|
||||
"""
|
||||
Test the functions in the :mod:`openlp` importer module.
|
||||
"""
|
||||
def setUp(self):
|
||||
"""
|
||||
Create the registry
|
||||
"""
|
||||
Registry.create()
|
||||
|
||||
def create_importer_test(self):
|
||||
"""
|
||||
Test creating an instance of the OpenLP database importer
|
||||
"""
|
||||
# GIVEN: A mocked out SongImport class, and a mocked out "manager"
|
||||
with patch('openlp.plugins.songs.lib.importers.openlp.SongImport'):
|
||||
mocked_manager = MagicMock()
|
||||
|
||||
# WHEN: An importer object is created
|
||||
importer = OpenLPSongImport(mocked_manager, filenames=[])
|
||||
|
||||
# THEN: The importer object should not be None
|
||||
self.assertIsNotNone(importer, 'Import should not be none')
|
||||
|
||||
def invalid_import_source_test(self):
|
||||
"""
|
||||
Test OpenLPSongImport.do_import handles different invalid import_source values
|
||||
"""
|
||||
# GIVEN: A mocked out SongImport class, and a mocked out "manager"
|
||||
with patch('openlp.plugins.songs.lib.importers.openlp.SongImport'):
|
||||
mocked_manager = MagicMock()
|
||||
mocked_import_wizard = MagicMock()
|
||||
importer = OpenLPSongImport(mocked_manager, filenames=[])
|
||||
importer.import_wizard = mocked_import_wizard
|
||||
importer.stop_import_flag = True
|
||||
|
||||
# WHEN: Import source is not a list
|
||||
for source in ['not a list', 0]:
|
||||
importer.import_source = source
|
||||
|
||||
# THEN: do_import should return none and the progress bar maximum should not be set.
|
||||
self.assertIsNone(importer.do_import(), 'do_import should return None when import_source is not a list')
|
||||
self.assertEqual(mocked_import_wizard.progress_bar.setMaximum.called, False,
|
||||
'setMaximum on import_wizard.progress_bar should not have been called')
|
||||
|
|
@ -716,8 +716,43 @@ class TestSongSelectForm(TestCase, TestMixin):
|
|||
# WHEN: The stop button is clicked
|
||||
ssform.on_stop_button_clicked()
|
||||
|
||||
# THEN: The view button should be enabled
|
||||
# THEN: The view button, search box and search button should be enabled
|
||||
mocked_song_select_importer.stop.assert_called_with()
|
||||
self.assertTrue(ssform.search_button.isEnabled())
|
||||
self.assertTrue(ssform.search_combobox.isEnabled())
|
||||
|
||||
@patch('openlp.plugins.songs.forms.songselectform.Settings')
|
||||
@patch('openlp.plugins.songs.forms.songselectform.QtCore.QThread')
|
||||
@patch('openlp.plugins.songs.forms.songselectform.SearchWorker')
|
||||
def on_search_button_clicked_test(self, MockedSearchWorker, MockedQtThread, MockedSettings):
|
||||
"""
|
||||
Test that search fields are disabled when search button is clicked.
|
||||
"""
|
||||
# GIVEN: A mocked SongSelect form
|
||||
ssform = SongSelectForm(None, MagicMock(), MagicMock())
|
||||
ssform.initialise()
|
||||
|
||||
# WHEN: The search button is clicked
|
||||
ssform.on_search_button_clicked()
|
||||
|
||||
# THEN: The search box and search button should be disabled
|
||||
self.assertFalse(ssform.search_button.isEnabled())
|
||||
self.assertFalse(ssform.search_combobox.isEnabled())
|
||||
|
||||
def on_search_finished_test(self):
|
||||
"""
|
||||
Test that search fields are enabled when search is finished.
|
||||
"""
|
||||
# GIVEN: A mocked SongSelect form
|
||||
ssform = SongSelectForm(None, MagicMock(), MagicMock())
|
||||
ssform.initialise()
|
||||
|
||||
# WHEN: The search is finished
|
||||
ssform.on_search_finished()
|
||||
|
||||
# THEN: The search box and search button should be enabled
|
||||
self.assertTrue(ssform.search_button.isEnabled())
|
||||
self.assertTrue(ssform.search_combobox.isEnabled())
|
||||
|
||||
|
||||
class TestSongSelectFileImport(SongImportTestHelper):
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||
###############################################################################
|
||||
"""
|
||||
Package to test the openlp.core.ui.listpreviewwidget.
|
||||
Package to test the openlp.core.ui.lib.listpreviewwidget.
|
||||
"""
|
||||
|
||||
from unittest import TestCase
|
||||
|
@ -29,7 +29,7 @@ from PyQt5 import QtGui, QtWidgets
|
|||
|
||||
from openlp.core.common import Registry
|
||||
from openlp.core.lib import ServiceItem
|
||||
from openlp.core.ui import listpreviewwidget
|
||||
from openlp.core.ui.lib import ListWidgetWithDnD, ListPreviewWidget
|
||||
from tests.interfaces import MagicMock, patch
|
||||
from tests.utils.osdinteraction import read_service_from_file
|
||||
from tests.helpers.testmixin import TestMixin
|
||||
|
@ -48,7 +48,7 @@ class TestListPreviewWidget(TestCase, TestMixin):
|
|||
self.image_manager = MagicMock()
|
||||
self.image_manager.get_image.return_value = self.image
|
||||
Registry().register('image_manager', self.image_manager)
|
||||
self.preview_widget = listpreviewwidget.ListPreviewWidget(self.main_window, 2)
|
||||
self.preview_widget = ListPreviewWidget(self.main_window, 2)
|
||||
|
||||
def tearDown(self):
|
||||
"""
|
|
@ -0,0 +1,21 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||
|
||||
###############################################################################
|
||||
# OpenLP - Open Source Lyrics Projection #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Copyright (c) 2008-2016 OpenLP Developers #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# This program is free software; you can redistribute it and/or modify it #
|
||||
# under the terms of the GNU General Public License as published by the Free #
|
||||
# Software Foundation; version 2 of the License. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT #
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
|
||||
# more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License along #
|
||||
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||
###############################################################################
|
|
@ -0,0 +1,51 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||
|
||||
###############################################################################
|
||||
# OpenLP - Open Source Lyrics Projection #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Copyright (c) 2008-2016 OpenLP Developers #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# This program is free software; you can redistribute it and/or modify it #
|
||||
# under the terms of the GNU General Public License as published by the Free #
|
||||
# Software Foundation; version 2 of the License. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT #
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
|
||||
# more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License along #
|
||||
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||
###############################################################################
|
||||
"""
|
||||
Package to test the openlp.core.ui.media package.
|
||||
"""
|
||||
|
||||
import os
|
||||
from unittest import TestCase
|
||||
|
||||
from openlp.core.ui.media.vendor.mediainfoWrapper import MediaInfoWrapper
|
||||
|
||||
TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'resources', 'media'))
|
||||
|
||||
TEST_MEDIA = [['avi_file.avi', 61495], ['mp3_file.mp3', 134426], ['mpg_file.mpg', 9404], ['mp4_file.mp4', 188336]]
|
||||
|
||||
|
||||
class TestMediainfoWrapper(TestCase):
|
||||
|
||||
def media_length_test(self):
|
||||
"""
|
||||
Test the Media Info basic functionality
|
||||
"""
|
||||
for test_data in TEST_MEDIA:
|
||||
# GIVEN: a media file
|
||||
full_path = os.path.normpath(os.path.join(TEST_PATH, test_data[0]))
|
||||
|
||||
# WHEN the media data is retrieved
|
||||
results = MediaInfoWrapper.parse(full_path)
|
||||
|
||||
# THEN you can determine the run time
|
||||
self.assertEqual(results.tracks[0].duration, test_data[1], 'The correct duration is returned for ' +
|
||||
test_data[0])
|
|
@ -27,7 +27,7 @@ from unittest import TestCase
|
|||
from PyQt5 import QtWidgets
|
||||
|
||||
from openlp.core.common import Registry
|
||||
from openlp.plugins.bibles.forms.bibleimportform import BibleImportForm, WebDownload
|
||||
import openlp.plugins.bibles.forms.bibleimportform as bibleimportform
|
||||
|
||||
from tests.helpers.testmixin import TestMixin
|
||||
from tests.functional import MagicMock, patch
|
||||
|
@ -46,7 +46,8 @@ class TestBibleImportForm(TestCase, TestMixin):
|
|||
self.setup_application()
|
||||
self.main_window = QtWidgets.QMainWindow()
|
||||
Registry().register('main_window', self.main_window)
|
||||
self.form = BibleImportForm(self.main_window, MagicMock(), MagicMock())
|
||||
bibleimportform.PYSWORD_AVAILABLE = False
|
||||
self.form = bibleimportform.BibleImportForm(self.main_window, MagicMock(), MagicMock())
|
||||
|
||||
def tearDown(self):
|
||||
"""
|
||||
|
@ -76,3 +77,16 @@ class TestBibleImportForm(TestCase, TestMixin):
|
|||
|
||||
# THEN: The webbible list should still be empty
|
||||
self.assertEqual(self.form.web_bible_list, {}, 'The webbible list should be empty')
|
||||
|
||||
def custom_init_test(self):
|
||||
"""
|
||||
Test that custom_init works as expected if pysword is unavailable
|
||||
"""
|
||||
# GIVEN: A mocked sword_tab_widget
|
||||
self.form.sword_tab_widget = MagicMock()
|
||||
|
||||
# WHEN: Running custom_init
|
||||
self.form.custom_init()
|
||||
|
||||
# THEN: sword_tab_widget.setDisabled(True) should have been called
|
||||
self.form.sword_tab_widget.setDisabled.assert_called_with(True)
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue