Merge trunk to fix conflicts

This commit is contained in:
Ken Roberts 2016-04-22 04:41:29 -07:00
commit bec015142b
42 changed files with 1343 additions and 571 deletions

View File

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

View File

@ -56,6 +56,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')

View File

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

View File

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

View File

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

View File

@ -68,7 +68,6 @@ class DisplayControllerType(object):
"""
Live = 0
Preview = 1
Plugin = 2
class SingleColumnTableWidget(QtWidgets.QTableWidget):

View File

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

View File

@ -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,17 +239,28 @@ 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.'))
@ -277,3 +295,10 @@ class UiFirstTimeWizard(object):
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)

View File

@ -26,8 +26,8 @@ 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, ColorButton, build_icon
log = logging.getLogger(__name__)
@ -40,6 +40,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 +164,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 +247,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 +295,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 +329,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 +394,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

View File

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

View File

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

View File

@ -638,13 +638,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 +698,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 +1034,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 +1189,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 +1229,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 +1240,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 +1252,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 +1273,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):
"""

View File

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

View File

@ -33,12 +33,15 @@ from openlp.core.lib import OpenLPToolbar, 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.vendor.mediainfoWrapper import MediaInfoWrapper
from openlp.core.ui.media.mediaplayer import MediaPlayer
from openlp.core.common import AppLocation
from openlp.core.ui import DisplayControllerType
log = logging.getLogger(__name__)
TICK_TIME = 200
class MediaSlider(QtWidgets.QSlider):
"""
@ -51,10 +54,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 +69,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 +106,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 +186,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 +201,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 +295,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=':/slides/media_playback_stop.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 +327,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 +367,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 +386,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 +393,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 +407,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 +427,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 +464,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 +490,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 +507,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 +537,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 +556,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 +615,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 +635,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 +697,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 +729,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 +781,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 +792,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 +821,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 +839,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 +856,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

View File

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

View File

@ -133,12 +133,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:

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -147,6 +147,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 +187,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)

View File

@ -111,6 +111,7 @@ 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.setModal(True)
@ -210,6 +211,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 +223,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

View File

@ -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:'))
@ -155,6 +150,7 @@ class MediaMediaItem(MediaManagerItem, RegistryProperties):
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

View File

@ -60,13 +60,6 @@ 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 app_startup(self):

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 884 B

View File

@ -147,6 +147,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>

View File

@ -108,6 +108,23 @@ class TestPJLink(TestCase):
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):
"""

View File

@ -130,12 +130,14 @@ 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')
@ -236,6 +238,8 @@ 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')
@ -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.listpreviewwidget.ListPreviewWidget.selectRow')
@patch(u'openlp.core.ui.listpreviewwidget.ListPreviewWidget.scrollToItem')
@patch(u'openlp.core.ui.listpreviewwidget.ListPreviewWidget.item')
@patch(u'openlp.core.ui.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.listpreviewwidget.ListPreviewWidget.selectRow')
@patch(u'openlp.core.ui.listpreviewwidget.ListPreviewWidget.scrollToItem')
@patch(u'openlp.core.ui.listpreviewwidget.ListPreviewWidget.item')
@patch(u'openlp.core.ui.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.listpreviewwidget.ListPreviewWidget.selectRow')
@patch(u'openlp.core.ui.listpreviewwidget.ListPreviewWidget.scrollToItem')
@patch(u'openlp.core.ui.listpreviewwidget.ListPreviewWidget.item')
@patch(u'openlp.core.ui.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)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.