This commit is contained in:
Tim Bentley 2016-04-21 17:27:04 +01:00
commit 59b3c7b053
13 changed files with 513 additions and 199 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,
@ -207,7 +209,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

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

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

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

View File

@ -107,3 +107,20 @@ class TestPJLink(TestCase):
# THEN: process_inpt method should have been called with 31
mock_process_inpt.called_with('31',
"process_inpt should have been called with 31")
@patch.object(pjlink_test, 'projectorReceivedData')
def projector_process_lamp_test(self, mock_projectorReceivedData):
"""
Test setting lamp on/off and hours
"""
# GIVEN: Test object
pjlink = pjlink_test
# WHEN: Call process_command with lamp data
pjlink.process_command('LAMP', '22222 1')
# THEN: Lamp should have been set with status=ON and hours=22222
self.assertEquals(pjlink.lamp[0]['On'], True,
'Lamp power status should have been set to TRUE')
self.assertEquals(pjlink.lamp[0]['Hours'], 22222,
'Lamp hours should have been set to 22222')

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

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