merge trunk

This commit is contained in:
Jonathan Springer 2017-07-08 09:12:31 -04:00
commit a7daffe54f
107 changed files with 981 additions and 510 deletions

View File

@ -251,8 +251,7 @@ class OpenLP(OpenLPMixin, QtWidgets.QApplication):
if QtWidgets.QMessageBox.question(None, translate('OpenLP', 'Backup'), if QtWidgets.QMessageBox.question(None, translate('OpenLP', 'Backup'),
translate('OpenLP', 'OpenLP has been upgraded, do you want to create\n' translate('OpenLP', 'OpenLP has been upgraded, do you want to create\n'
'a backup of the old data folder?'), 'a backup of the old data folder?'),
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No, defaultButton=QtWidgets.QMessageBox.Yes) == QtWidgets.QMessageBox.Yes:
QtWidgets.QMessageBox.Yes) == QtWidgets.QMessageBox.Yes:
# Create copy of data folder # Create copy of data folder
data_folder_path = AppLocation.get_data_path() data_folder_path = AppLocation.get_data_path()
timestamp = time.strftime("%Y%m%d-%H%M%S") timestamp = time.strftime("%Y%m%d-%H%M%S")

View File

@ -140,8 +140,8 @@ class LanguageManager(object):
reg_ex = QtCore.QRegExp("^.*i18n/(.*).qm") reg_ex = QtCore.QRegExp("^.*i18n/(.*).qm")
if reg_ex.exactMatch(qmf): if reg_ex.exactMatch(qmf):
name = '{regex}'.format(regex=reg_ex.cap(1)) name = '{regex}'.format(regex=reg_ex.cap(1))
# TODO: Test before converting to python3 string format LanguageManager.__qm_list__[
LanguageManager.__qm_list__['%#2i %s' % (counter + 1, LanguageManager.language_name(qmf))] = name '{count:>2i} {name}'.format(count=counter + 1, name=LanguageManager.language_name(qmf))] = name
@staticmethod @staticmethod
def get_qm_list(): def get_qm_list():

View File

@ -219,7 +219,11 @@ class Settings(QtCore.QSettings):
('shortcuts/offlineHelpItem', 'shortcuts/userManualItem', []), # Online and Offline help were combined in 2.6. ('shortcuts/offlineHelpItem', 'shortcuts/userManualItem', []), # Online and Offline help were combined in 2.6.
('shortcuts/onlineHelpItem', 'shortcuts/userManualItem', []), # Online and Offline help were combined in 2.6. ('shortcuts/onlineHelpItem', 'shortcuts/userManualItem', []), # Online and Offline help were combined in 2.6.
('bibles/advanced bible', '', []), # Common bible search widgets combined in 2.6 ('bibles/advanced bible', '', []), # Common bible search widgets combined in 2.6
('bibles/quick bible', 'bibles/primary bible', []) # Common bible search widgets combined in 2.6 ('bibles/quick bible', 'bibles/primary bible', []), # Common bible search widgets combined in 2.6
# Last search type was renamed to last used search type in 2.6 since Bible search value type changed in 2.6.
('songs/last search type', 'songs/last used search type', []),
('bibles/last search type', '', []),
('custom/last search type', 'custom/last used search type', [])
] ]
@staticmethod @staticmethod

View File

@ -154,8 +154,6 @@ class UiStrings(object):
self.Split = translate('OpenLP.Ui', 'Optional &Split') self.Split = translate('OpenLP.Ui', 'Optional &Split')
self.SplitToolTip = translate('OpenLP.Ui', self.SplitToolTip = translate('OpenLP.Ui',
'Split a slide into two only if it does not fit on the screen as one slide.') 'Split a slide into two only if it does not fit on the screen as one slide.')
# TODO: WHERE is this used at? cannot find where it's used at in code.
self.StartTimeCode = translate('OpenLP.Ui', 'Start {code}')
self.StopPlaySlidesInLoop = translate('OpenLP.Ui', 'Stop Play Slides in Loop') self.StopPlaySlidesInLoop = translate('OpenLP.Ui', 'Stop Play Slides in Loop')
self.StopPlaySlidesToEnd = translate('OpenLP.Ui', 'Stop Play Slides to End') self.StopPlaySlidesToEnd = translate('OpenLP.Ui', 'Stop Play Slides to End')
self.Theme = translate('OpenLP.Ui', 'Theme', 'Singular') self.Theme = translate('OpenLP.Ui', 'Theme', 'Singular')

View File

@ -25,12 +25,15 @@ The :mod:`db` module provides the core database functionality for OpenLP
""" """
import logging import logging
import os import os
from copy import copy
from urllib.parse import quote_plus as urlquote from urllib.parse import quote_plus as urlquote
from sqlalchemy import Table, MetaData, Column, types, create_engine from sqlalchemy import Table, MetaData, Column, types, create_engine
from sqlalchemy.exc import SQLAlchemyError, InvalidRequestError, DBAPIError, OperationalError from sqlalchemy.engine.url import make_url
from sqlalchemy.exc import SQLAlchemyError, InvalidRequestError, DBAPIError, OperationalError, ProgrammingError
from sqlalchemy.orm import scoped_session, sessionmaker, mapper from sqlalchemy.orm import scoped_session, sessionmaker, mapper
from sqlalchemy.pool import NullPool from sqlalchemy.pool import NullPool
from alembic.migration import MigrationContext from alembic.migration import MigrationContext
from alembic.operations import Operations from alembic.operations import Operations
@ -40,6 +43,66 @@ from openlp.core.lib.ui import critical_error_message_box
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
def database_exists(url):
"""Check if a database exists.
:param url: A SQLAlchemy engine URL.
Performs backend-specific testing to quickly determine if a database
exists on the server. ::
database_exists('postgres://postgres@localhost/name') #=> False
create_database('postgres://postgres@localhost/name')
database_exists('postgres://postgres@localhost/name') #=> True
Supports checking against a constructed URL as well. ::
engine = create_engine('postgres://postgres@localhost/name')
database_exists(engine.url) #=> False
create_database(engine.url)
database_exists(engine.url) #=> True
Borrowed from SQLAlchemy_Utils (v0.32.14 )since we only need this one function.
"""
url = copy(make_url(url))
database = url.database
if url.drivername.startswith('postgresql'):
url.database = 'template1'
else:
url.database = None
engine = create_engine(url)
if engine.dialect.name == 'postgresql':
text = "SELECT 1 FROM pg_database WHERE datname='{db}'".format(db=database)
return bool(engine.execute(text).scalar())
elif engine.dialect.name == 'mysql':
text = ("SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA "
"WHERE SCHEMA_NAME = '{db}'".format(db=database))
return bool(engine.execute(text).scalar())
elif engine.dialect.name == 'sqlite':
if database:
return database == ':memory:' or os.path.exists(database)
else:
# The default SQLAlchemy database is in memory,
# and :memory is not required, thus we should support that use-case
return True
else:
text = 'SELECT 1'
try:
url.database = database
engine = create_engine(url)
engine.execute(text)
return True
except (ProgrammingError, OperationalError):
return False
def init_db(url, auto_flush=True, auto_commit=False, base=None): def init_db(url, auto_flush=True, auto_commit=False, base=None):
""" """
Initialise and return the session and metadata for a database Initialise and return the session and metadata for a database
@ -144,6 +207,12 @@ def upgrade_db(url, upgrade):
:param url: The url of the database to upgrade. :param url: The url of the database to upgrade.
:param upgrade: The python module that contains the upgrade instructions. :param upgrade: The python module that contains the upgrade instructions.
""" """
if not database_exists(url):
log.warn("Database {db} doesn't exist - skipping upgrade checks".format(db=url))
return (0, 0)
log.debug('Checking upgrades for DB {db}'.format(db=url))
session, metadata = init_db(url) session, metadata = init_db(url)
class Metadata(BaseModel): class Metadata(BaseModel):
@ -160,17 +229,15 @@ def upgrade_db(url, upgrade):
metadata_table.create(checkfirst=True) metadata_table.create(checkfirst=True)
mapper(Metadata, metadata_table) mapper(Metadata, metadata_table)
version_meta = session.query(Metadata).get('version') version_meta = session.query(Metadata).get('version')
if version_meta is None: if version_meta:
# Tables have just been created - fill the version field with the most recent version version = int(version_meta.value)
if session.query(Metadata).get('dbversion'):
version = 0
else: else:
version = upgrade.__version__ # Due to issues with other checks, if the version is not set in the DB then default to 0
# and let the upgrade function handle the checks
version = 0
version_meta = Metadata.populate(key='version', value=version) version_meta = Metadata.populate(key='version', value=version)
session.add(version_meta) session.add(version_meta)
session.commit() session.commit()
else:
version = int(version_meta.value)
if version > upgrade.__version__: if version > upgrade.__version__:
session.remove() session.remove()
return version, upgrade.__version__ return version, upgrade.__version__

View File

@ -24,7 +24,6 @@ The :mod:`~openlp.core.lib.exceptions` module contains custom exceptions
""" """
# TODO: Test __init__ & __str__
class ValidationError(Exception): class ValidationError(Exception):
""" """
The :class:`~openlp.core.lib.exceptions.ValidationError` exception provides a custom exception for validating The :class:`~openlp.core.lib.exceptions.ValidationError` exception provides a custom exception for validating

View File

@ -110,6 +110,8 @@ class Image(object):
:param width: The width of the image, defaults to -1 meaning that the screen width will be used. :param width: The width of the image, defaults to -1 meaning that the screen width will be used.
:param height: The height of the image, defaults to -1 meaning that the screen height will be used. :param height: The height of the image, defaults to -1 meaning that the screen height will be used.
""" """
if not os.path.exists(path):
raise FileNotFoundError('{path} not found'.format(path=path))
self.path = path self.path = path
self.image = None self.image = None
self.image_bytes = None self.image_bytes = None
@ -119,8 +121,6 @@ class Image(object):
self.timestamp = 0 self.timestamp = 0
self.width = width self.width = width
self.height = height self.height = height
# FIXME: We assume that the path exist. The caller has to take care that it exists!
if os.path.exists(path):
self.timestamp = os.stat(path).st_mtime self.timestamp = os.stat(path).st_mtime
self.secondary_priority = Image.secondary_priority self.secondary_priority = Image.secondary_priority
Image.secondary_priority += 1 Image.secondary_priority += 1

View File

@ -57,35 +57,115 @@ LF = chr(0x0A) # \n
PJLINK_PORT = 4352 PJLINK_PORT = 4352
TIMEOUT = 30.0 TIMEOUT = 30.0
PJLINK_MAX_PACKET = 136 PJLINK_MAX_PACKET = 136
# NOTE: Change format to account for some commands are both class 1 and 2 # NOTE: Changed format to account for some commands are both class 1 and 2
PJLINK_VALID_CMD = { PJLINK_VALID_CMD = {
'ACKN': ['2', ], # UDP Reply to 'SRCH' 'ACKN': {'version': ['2', ],
'AVMT': ['1', ], # Shutter option 'description': translate('OpenLP.PJLinkConstants',
'CLSS': ['1', ], # PJLink class support query 'Acknowledge a PJLink SRCH command - returns MAC address.')
'ERST': ['1', '2'], # Error status option },
'FILT': ['2', ], # Get current filter usage time 'AVMT': {'version': ['1', ],
'FREZ': ['2', ], # Set freeze/unfreeze picture being projected 'description': translate('OpenLP.PJLinkConstants',
'INF1': ['1', ], # Manufacturer name query 'Blank/unblank video and/or mute audio.')
'INF2': ['1', ], # Product name query },
'INFO': ['1', ], # Other information query 'CLSS': {'version': ['1', ],
'INNM': ['2', ], # Get Video source input terminal name 'description': translate('OpenLP.PJLinkConstants',
'INPT': ['1', ], # Video sources option 'Query projector PJLink class support.')
'INST': ['1', ], # Input sources available query },
'IRES': ['2', ], # Get Video source resolution 'ERST': {'version': ['1', '2'],
'LAMP': ['1', ], # Lamp(s) query (Includes fans) 'description': translate('OpenLP.PJLinkConstants',
'LKUP': ['2', ], # UPD Linkup status notification 'Query error status from projector. '
'MVOL': ['2', ], # Set microphone volume 'Returns fan/lamp/temp/cover/filter/other error status.')
'NAME': ['1', ], # Projector name query },
'PJLINK': ['1', ], # Initial connection 'FILT': {'version': ['2', ],
'POWR': ['1', ], # Power option 'description': translate('OpenLP.PJLinkConstants',
'RFIL': ['2', ], # Get replacement air filter model number 'Query number of hours on filter.')
'RLMP': ['2', ], # Get lamp replacement model number },
'RRES': ['2', ], # Get projector recommended video resolution 'FREZ': {'version': ['2', ],
'SNUM': ['2', ], # Get projector serial number 'description': translate('OpenLP.PJLinkConstants',
'SRCH': ['2', ], # UDP broadcast search for available projectors on local network 'Freeze or unfreeze current image being projected.')
'SVER': ['2', ], # Get projector software version },
'SVOL': ['2', ] # Set speaker volume 'INF1': {'version': ['1', ],
'description': translate('OpenLP.PJLinkConstants',
'Query projector manufacturer name.')
},
'INF2': {'version': ['1', ],
'description': translate('OpenLP.PJLinkConstants',
'Query projector product name.')
},
'INFO': {'version': ['1', ],
'description': translate('OpenLP.PJLinkConstants',
'Query projector for other information set by manufacturer.')
},
'INNM': {'version': ['2', ],
'description': translate('OpenLP.PJLinkConstants',
'Query specified input source name')
},
'INPT': {'version': ['1', ],
'description': translate('OpenLP.PJLinkConstants',
'Switch to specified video source.')
},
'INST': {'version': ['1', ],
'description': translate('OpenLP.PJLinkConstants',
'Query available input sources.')
},
'IRES': {'version:': ['2', ],
'description': translate('OpenLP.PJLinkConstants',
'Query current input resolution.')
},
'LAMP': {'version': ['1', ],
'description': translate('OpenLP.PJLinkConstants',
'Query lamp time and on/off status. Multiple lamps supported.')
},
'LKUP': {'version': ['2', ],
'description': translate('OpenLP.PJLinkConstants',
'UDP Status - Projector is now available on network. Includes MAC address.')
},
'MVOL': {'version': ['2', ],
'description': translate('OpenLP.PJLinkConstants',
'Adjust microphone volume by 1 step.')
},
'NAME': {'version': ['1', ],
'description': translate('OpenLP.PJLinkConstants',
'Query customer-set projector name.')
},
'PJLINK': {'version': ['1', ],
'description': translate('OpenLP.PJLinkConstants',
'Initial connection with authentication/no authentication request.')
},
'POWR': {'version': ['1', ],
'description': translate('OpenLP.PJLinkConstants',
'Turn lamp on or off/standby.')
},
'RFIL': {'version': ['2', ],
'description': translate('OpenLP.PJLinkConstants',
'Query replacement air filter model number.')
},
'RLMP': {'version': ['2', ],
'description': translate('OpenLP.PJLinkConstants',
'Query replacement lamp model number.')
},
'RRES': {'version': ['2', ],
'description': translate('OpenLP.PJLinkConstants',
'Query recommended resolution.')
},
'SNUM': {'version': ['2', ],
'description': translate('OpenLP.PJLinkConstants',
'Query projector serial number.')
},
'SRCH': {'version': ['2', ],
'description': translate('OpenLP.PJLinkConstants',
'UDP broadcast search request for available projectors.')
},
'SVER': {'version': ['2', ],
'description': translate('OpenLP.PJLinkConstants',
'Query projector software version number.')
},
'SVOL': {'version': ['2', ],
'description': translate('OpenLP.PJLinkConstants',
'Adjust speaker volume by 1 step.')
} }
}
# Error and status codes # Error and status codes
S_OK = E_OK = 0 # E_OK included since I sometimes forget S_OK = E_OK = 0 # E_OK included since I sometimes forget
# Error codes. Start at 200 so we don't duplicate system error codes. # Error codes. Start at 200 so we don't duplicate system error codes.

View File

@ -44,6 +44,7 @@ from sqlalchemy.orm import relationship
from openlp.core.lib.db import Manager, init_db, init_url from openlp.core.lib.db import Manager, init_db, init_url
from openlp.core.lib.projector.constants import PJLINK_DEFAULT_CODES from openlp.core.lib.projector.constants import PJLINK_DEFAULT_CODES
from openlp.core.lib.projector import upgrade
Base = declarative_base(MetaData()) Base = declarative_base(MetaData())
@ -166,13 +167,14 @@ class Projector(CommonBase, Base):
""" """
Return basic representation of Source table entry. Return basic representation of Source table entry.
""" """
return '< Projector(id="{data}", ip="{ip}", port="{port}", pin="{pin}", name="{name}", ' \ return '< Projector(id="{data}", ip="{ip}", port="{port}", mac_adx="{mac}", pin="{pin}", name="{name}", ' \
'location="{location}", notes="{notes}", pjlink_name="{pjlink_name}", ' \ 'location="{location}", notes="{notes}", pjlink_name="{pjlink_name}", ' \
'manufacturer="{manufacturer}", model="{model}", serial_no="{serial}", other="{other}", ' \ 'manufacturer="{manufacturer}", model="{model}", serial_no="{serial}", other="{other}", ' \
'sources="{sources}", source_list="{source_list}", model_filter="{mfilter}", ' \ 'sources="{sources}", source_list="{source_list}", model_filter="{mfilter}", ' \
'model_lamp="{mlamp}", sw_version="{sw_ver}") >'.format(data=self.id, 'model_lamp="{mlamp}", sw_version="{sw_ver}") >'.format(data=self.id,
ip=self.ip, ip=self.ip,
port=self.port, port=self.port,
mac=self.mac_adx,
pin=self.pin, pin=self.pin,
name=self.name, name=self.name,
location=self.location, location=self.location,
@ -189,6 +191,7 @@ class Projector(CommonBase, Base):
sw_ver=self.sw_version) sw_ver=self.sw_version)
ip = Column(String(100)) ip = Column(String(100))
port = Column(String(8)) port = Column(String(8))
mac_adx = Column(String(18))
pin = Column(String(20)) pin = Column(String(20))
name = Column(String(20)) name = Column(String(20))
location = Column(String(30)) location = Column(String(30))
@ -243,7 +246,9 @@ class ProjectorDB(Manager):
""" """
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
log.debug('ProjectorDB().__init__(args="{arg}", kwargs="{kwarg}")'.format(arg=args, kwarg=kwargs)) log.debug('ProjectorDB().__init__(args="{arg}", kwargs="{kwarg}")'.format(arg=args, kwarg=kwargs))
super().__init__(plugin_name='projector', init_schema=self.init_schema) super().__init__(plugin_name='projector',
init_schema=self.init_schema,
upgrade_mod=upgrade)
log.debug('ProjectorDB() Initialized using db url {db}'.format(db=self.db_url)) log.debug('ProjectorDB() Initialized using db url {db}'.format(db=self.db_url))
log.debug('Session: {session}'.format(session=self.session)) log.debug('Session: {session}'.format(session=self.session))

View File

@ -80,25 +80,8 @@ class PJLink(QtNetwork.QTcpSocket):
projectorNoAuthentication = QtCore.pyqtSignal(str) # PIN set and no authentication needed projectorNoAuthentication = QtCore.pyqtSignal(str) # PIN set and no authentication needed
projectorReceivedData = QtCore.pyqtSignal() # Notify when received data finished processing projectorReceivedData = QtCore.pyqtSignal() # Notify when received data finished processing
projectorUpdateIcons = QtCore.pyqtSignal() # Update the status icons on toolbar projectorUpdateIcons = QtCore.pyqtSignal() # Update the status icons on toolbar
# New commands available in PJLink Class 2
pjlink_future = [
'ACKN', # UDP Reply to 'SRCH'
'FILT', # Get current filter usage time
'FREZ', # Set freeze/unfreeze picture being projected
'INNM', # Get Video source input terminal name
'IRES', # Get Video source resolution
'LKUP', # UPD Linkup status notification
'MVOL', # Set microphone volume
'RFIL', # Get replacement air filter model number
'RLMP', # Get lamp replacement model number
'RRES', # Get projector recommended video resolution
'SNUM', # Get projector serial number
'SRCH', # UDP broadcast search for available projectors on local network
'SVER', # Get projector software version
'SVOL', # Set speaker volume
'TESTMEONLY' # For testing when other commands have been implemented
]
# New commands available in PJLink Class 2
pjlink_udp_commands = [ pjlink_udp_commands = [
'ACKN', 'ACKN',
'ERST', # Class 1 or 2 'ERST', # Class 1 or 2
@ -129,13 +112,14 @@ class PJLink(QtNetwork.QTcpSocket):
self.ip = ip self.ip = ip
self.port = port self.port = port
self.pin = pin self.pin = pin
super(PJLink, self).__init__() super().__init__()
self.mac_adx = kwargs.get('mac_adx')
self.dbid = None self.dbid = None
self.location = None self.location = None
self.notes = None self.notes = None
self.dbid = None if 'dbid' not in kwargs else kwargs['dbid'] self.dbid = kwargs.get('dbid')
self.location = None if 'location' not in kwargs else kwargs['location'] self.location = kwargs.get('location')
self.notes = None if 'notes' not in kwargs else kwargs['notes'] self.notes = kwargs.get('notes')
# Poll time 20 seconds unless called with something else # Poll time 20 seconds unless called with something else
self.poll_time = 20000 if 'poll_time' not in kwargs else kwargs['poll_time'] * 1000 self.poll_time = 20000 if 'poll_time' not in kwargs else kwargs['poll_time'] * 1000
# Timeout 5 seconds unless called with something else # Timeout 5 seconds unless called with something else
@ -186,10 +170,15 @@ class PJLink(QtNetwork.QTcpSocket):
self.pjlink_name = None self.pjlink_name = None
self.manufacturer = None self.manufacturer = None
self.model = None self.model = None
self.serial_no = None
self.sw_version = None
self.shutter = None self.shutter = None
self.mute = None self.mute = None
self.lamp = None self.lamp = None
self.model_lamp = None
self.fan = None self.fan = None
self.filter_time = None
self.model_filter = None
self.source_available = None self.source_available = None
self.source = None self.source = None
self.other_info = None self.other_info = None
@ -451,18 +440,18 @@ class PJLink(QtNetwork.QTcpSocket):
return return
data_split = data.split('=') data_split = data.split('=')
try: try:
(prefix, class_, cmd, data) = (data_split[0][0], data_split[0][1], data_split[0][2:], data_split[1]) (prefix, version, cmd, data) = (data_split[0][0], data_split[0][1], data_split[0][2:], data_split[1])
except ValueError as e: except ValueError as e:
log.warning('({ip}) get_data(): Invalid packet - expected header + command + data'.format(ip=self.ip)) log.warning('({ip}) get_data(): Invalid packet - expected header + command + data'.format(ip=self.ip))
log.warning('({ip}) get_data(): Received data: "{data}"'.format(ip=self.ip, data=data_in.strip())) log.warning('({ip}) get_data(): Received data: "{data}"'.format(ip=self.ip, data=data_in.strip()))
self.change_status(E_INVALID_DATA) self.change_status(E_INVALID_DATA)
self.receive_data_signal() self.receive_data_signal()
return return
if not (cmd in PJLINK_VALID_CMD and class_ in PJLINK_VALID_CMD[cmd]): if cmd not in PJLINK_VALID_CMD:
log.warning('({ip}) get_data(): Invalid packet - unknown command "{data}"'.format(ip=self.ip, data=cmd)) log.warning('({ip}) get_data(): Invalid packet - unknown command "{data}"'.format(ip=self.ip, data=cmd))
self.receive_data_signal() self.receive_data_signal()
return return
if int(self.pjlink_class) < int(class_): if int(self.pjlink_class) < int(version):
log.warn('({ip}) get_data(): Projector returned class reply higher ' log.warn('({ip}) get_data(): Projector returned class reply higher '
'than projector stated class'.format(ip=self.ip)) 'than projector stated class'.format(ip=self.ip))
return self.process_command(cmd, data) return self.process_command(cmd, data)
@ -507,14 +496,25 @@ class PJLink(QtNetwork.QTcpSocket):
log.warning('({ip}) send_command(): Not connected - returning'.format(ip=self.ip)) log.warning('({ip}) send_command(): Not connected - returning'.format(ip=self.ip))
self.send_queue = [] self.send_queue = []
return return
if cmd not in PJLINK_VALID_CMD:
log.error('({ip}) send_command(): Invalid command requested - ignoring.'.format(ip=self.ip))
return
self.projectorNetwork.emit(S_NETWORK_SENDING) self.projectorNetwork.emit(S_NETWORK_SENDING)
log.debug('({ip}) send_command(): Building cmd="{command}" opts="{data}"{salt}'.format(ip=self.ip, log.debug('({ip}) send_command(): Building cmd="{command}" opts="{data}"{salt}'.format(ip=self.ip,
command=cmd, command=cmd,
data=opts, data=opts,
salt='' if salt is None salt='' if salt is None
else ' with hash')) else ' with hash'))
# TODO: Check for class of command rather than default to projector PJLink class cmd_ver = PJLINK_VALID_CMD[cmd]['version']
if self.pjlink_class in cmd_ver:
header = PJLINK_HEADER.format(linkclass=self.pjlink_class) header = PJLINK_HEADER.format(linkclass=self.pjlink_class)
elif len(cmd_ver) == 1 and (int(cmd_ver[0]) < int(self.pjlink_class)):
# Typically a class 1 only command
header = PJLINK_HEADER.format(linkclass=cmd_ver[0])
else:
# NOTE: Once we get to version 3 then think about looping
log.error('({ip}): send_command(): PJLink class check issue? aborting'.format(ip=self.ip))
return
out = '{salt}{header}{command} {options}{suffix}'.format(salt="" if salt is None else salt, out = '{salt}{header}{command} {options}{suffix}'.format(salt="" if salt is None else salt,
header=header, header=header,
command=cmd, command=cmd,
@ -589,10 +589,13 @@ class PJLink(QtNetwork.QTcpSocket):
cmd=cmd, cmd=cmd,
data=data)) data=data))
# Check if we have a future command not available yet # Check if we have a future command not available yet
if cmd in self.pjlink_future: if cmd not in PJLINK_VALID_CMD:
self._not_implemented(cmd) log.error('({ip}) Unknown command received - ignoring'.format(ip=self.ip))
return return
if data in PJLINK_ERRORS: elif cmd not in self.pjlink_functions:
log.warn('({ip}) Future command received - unable to process yet'.format(ip=self.ip))
return
elif data in PJLINK_ERRORS:
# Oops - projector error # Oops - projector error
log.error('({ip}) Projector returned error "{data}"'.format(ip=self.ip, data=data)) log.error('({ip}) Projector returned error "{data}"'.format(ip=self.ip, data=data))
if data.upper() == 'ERRA': if data.upper() == 'ERRA':
@ -624,14 +627,11 @@ class PJLink(QtNetwork.QTcpSocket):
self.send_busy = False self.send_busy = False
self.projectorReceivedData.emit() self.projectorReceivedData.emit()
return return
# Command checks already passed
if cmd in self.pjlink_functions:
log.debug('({ip}) Calling function for {cmd}'.format(ip=self.ip, cmd=cmd)) log.debug('({ip}) Calling function for {cmd}'.format(ip=self.ip, cmd=cmd))
self.pjlink_functions[cmd](data)
else:
log.warning('({ip}) Invalid command {data}'.format(ip=self.ip, data=cmd))
self.send_busy = False self.send_busy = False
self.projectorReceivedData.emit() self.projectorReceivedData.emit()
self.pjlink_functions[cmd](data)
def process_lamp(self, data): def process_lamp(self, data):
""" """

View File

@ -0,0 +1,85 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2017 OpenLP Developers #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
:mod:`openlp.core.lib.projector.pjlink2` module provides the PJLink Class 2
updates from PJLink Class 1.
This module only handles the UDP socket functionality. Command/query/status
change messages will still be processed by the PJLink 1 module.
Currently, the only variance is the addition of a UDP "search" command to
query the local network for Class 2 capable projectors,
and UDP "notify" messages from projectors to connected software of status
changes (i.e., power change, input change, error changes).
Differences between Class 1 and Class 2 PJLink specifications are as follows.
New Functionality:
* Search - UDP Query local network for Class 2 capabable projector(s).
* Status - UDP Status change with connected projector(s). Status change
messages consist of:
* Initial projector power up when network communication becomes available
* Lamp off/standby to warmup or on
* Lamp on to cooldown or off/standby
* Input source select change completed
* Error status change (i.e., fan/lamp/temp/cover open/filter/other error(s))
New Commands:
* Query serial number of projector
* Query version number of projector software
* Query model number of replacement lamp
* Query model number of replacement air filter
* Query current projector screen resolution
* Query recommended screen resolution
* Query name of specific input terminal (video source)
* Adjust projector microphone in 1-step increments
* Adjust projector speacker in 1-step increments
Extended Commands:
* Addition of INTERNAL terminal (video source) for a total of 6 types of terminals.
* Number of terminals (video source) has been expanded from [1-9]
to [1-9a-z] (Addition of 26 terminals for each type of input).
See PJLink Class 2 Specifications for details.
http://pjlink.jbmia.or.jp/english/dl_class2.html
Section 5-1 PJLink Specifications
Section 5-5 Guidelines for Input Terminals
"""
import logging
log = logging.getLogger(__name__)
log.debug('pjlink2 loaded')
from PyQt5 import QtCore, QtNetwork
class PJLinkUDP(QtNetwork.QTcpSocket):
"""
Socket service for handling datagram (UDP) sockets.
"""
log.debug('PJLinkUDP loaded')
# Class varialbe for projector list. Should be replaced by ProjectorManager's
# projector list after being loaded there.
projector_list = None
projectors_found = None # UDP search found list

View File

@ -0,0 +1,76 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2017 OpenLP Developers #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
The :mod:`upgrade` module provides a way for the database and schema that is the
backend for the projector setup.
"""
import logging
# Not all imports used at this time, but keep for future upgrades
from sqlalchemy import Table, Column, types, inspect
from sqlalchemy.exc import NoSuchTableError
from sqlalchemy.sql.expression import null
from openlp.core.common.db import drop_columns
from openlp.core.lib.db import get_upgrade_op
log = logging.getLogger(__name__)
# Initial projector DB was unversioned
__version__ = 2
log.debug('Projector DB upgrade module loading')
def upgrade_1(session, metadata):
"""
Version 1 upgrade - old db might/might not be versioned.
"""
log.debug('Skipping upgrade_1 of projector DB - not used')
def upgrade_2(session, metadata):
"""
Version 2 upgrade.
Update Projector() table to include new data defined in PJLink version 2 changes
mac_adx: Column(String(18))
serial_no: Column(String(30))
sw_version: Column(String(30))
model_filter: Column(String(30))
model_lamp: Column(String(30))
:param session: DB session instance
:param metadata: Metadata of current DB
"""
projector_table = Table('projector', metadata, autoload=True)
if 'mac_adx' not in [col.name for col in projector_table.c.values()]:
log.debug("Upgrading projector DB to version '2'")
new_op = get_upgrade_op(session)
new_op.add_column('projector', Column('mac_adx', types.String(18), server_default=null()))
new_op.add_column('projector', Column('serial_no', types.String(30), server_default=null()))
new_op.add_column('projector', Column('sw_version', types.String(30), server_default=null()))
new_op.add_column('projector', Column('model_filter', types.String(30), server_default=null()))
new_op.add_column('projector', Column('model_lamp', types.String(30), server_default=null()))
else:
log.warn("Skipping upgrade_2 of projector DB")

View File

@ -105,7 +105,7 @@ class SearchEdit(QtWidgets.QLineEdit):
self.setPlaceholderText(action.placeholder_text) self.setPlaceholderText(action.placeholder_text)
self.menu_button.setDefaultAction(action) self.menu_button.setDefaultAction(action)
self._current_search_type = identifier self._current_search_type = identifier
Settings().setValue('{section}/last search type'.format(section=self.settings_section), identifier) Settings().setValue('{section}/last used search type'.format(section=self.settings_section), identifier)
self.searchTypeChanged.emit(identifier) self.searchTypeChanged.emit(identifier)
return True return True
@ -141,7 +141,7 @@ class SearchEdit(QtWidgets.QLineEdit):
self.menu_button.resize(QtCore.QSize(28, 18)) self.menu_button.resize(QtCore.QSize(28, 18))
self.menu_button.setMenu(menu) self.menu_button.setMenu(menu)
self.set_current_search_type( self.set_current_search_type(
Settings().value('{section}/last search type'.format(section=self.settings_section))) Settings().value('{section}/last used search type'.format(section=self.settings_section)))
self.menu_button.show() self.menu_button.show()
self._update_style_sheet() self._update_style_sheet()

View File

@ -49,6 +49,7 @@ def add_welcome_page(parent, image):
parent.title_label = QtWidgets.QLabel(parent.welcome_page) parent.title_label = QtWidgets.QLabel(parent.welcome_page)
parent.title_label.setObjectName('title_label') parent.title_label.setObjectName('title_label')
parent.welcome_layout.addWidget(parent.title_label) parent.welcome_layout.addWidget(parent.title_label)
parent.title_label.setWordWrap(True)
parent.welcome_layout.addSpacing(40) parent.welcome_layout.addSpacing(40)
parent.information_label = QtWidgets.QLabel(parent.welcome_page) parent.information_label = QtWidgets.QLabel(parent.welcome_page)
parent.information_label.setWordWrap(True) parent.information_label.setWordWrap(True)

View File

@ -99,7 +99,7 @@ from .themelayoutform import ThemeLayoutForm
from .themeform import ThemeForm from .themeform import ThemeForm
from .filerenameform import FileRenameForm from .filerenameform import FileRenameForm
from .starttimeform import StartTimeForm from .starttimeform import StartTimeForm
from .maindisplay import MainDisplay, Display from .maindisplay import MainDisplay, Display, AudioPlayer
from .servicenoteform import ServiceNoteForm from .servicenoteform import ServiceNoteForm
from .serviceitemeditform import ServiceItemEditForm from .serviceitemeditform import ServiceItemEditForm
from .slidecontroller import SlideController, DisplayController, PreviewController, LiveController from .slidecontroller import SlideController, DisplayController, PreviewController, LiveController
@ -120,8 +120,8 @@ from .projector.tab import ProjectorTab
from .projector.editform import ProjectorEditForm from .projector.editform import ProjectorEditForm
__all__ = ['SplashScreen', 'AboutForm', 'SettingsForm', 'MainDisplay', 'SlideController', 'ServiceManager', 'ThemeForm', __all__ = ['SplashScreen', 'AboutForm', 'SettingsForm', 'MainDisplay', 'SlideController', 'ServiceManager', 'ThemeForm',
'ThemeManager', 'ServiceItemEditForm', 'FirstTimeForm', 'FirstTimeLanguageForm', 'ThemeManager', 'ServiceItemEditForm', 'FirstTimeForm', 'FirstTimeLanguageForm', 'Display', 'AudioPlayer',
'Display', 'ServiceNoteForm', 'ThemeLayoutForm', 'FileRenameForm', 'StartTimeForm', 'MainDisplay', 'ServiceNoteForm', 'ThemeLayoutForm', 'FileRenameForm', 'StartTimeForm', 'MainDisplay',
'SlideController', 'DisplayController', 'GeneralTab', 'ThemesTab', 'AdvancedTab', 'PluginForm', 'SlideController', 'DisplayController', 'GeneralTab', 'ThemesTab', 'AdvancedTab', 'PluginForm',
'FormattingTagForm', 'ShortcutListForm', 'FormattingTagController', 'SingleColumnTableWidget', 'FormattingTagForm', 'ShortcutListForm', 'FormattingTagController', 'SingleColumnTableWidget',
'ProjectorManager', 'ProjectorTab', 'ProjectorEditForm'] 'ProjectorManager', 'ProjectorTab', 'ProjectorEditForm']

View File

@ -40,7 +40,8 @@ class AboutForm(QtWidgets.QDialog, UiAboutDialog):
""" """
Do some initialisation stuff Do some initialisation stuff
""" """
super(AboutForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint) super(AboutForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint |
QtCore.Qt.WindowCloseButtonHint)
self._setup() self._setup()
def _setup(self): def _setup(self):

View File

@ -495,9 +495,7 @@ class AdvancedTab(SettingsTab):
'location of the OpenLP data directory to:\n\n{path}' 'location of the OpenLP data directory to:\n\n{path}'
'\n\nThe data directory will be changed when OpenLP is ' '\n\nThe data directory will be changed when OpenLP is '
'closed.').format(path=new_data_path), 'closed.').format(path=new_data_path),
QtWidgets.QMessageBox.StandardButtons(QtWidgets.QMessageBox.Yes | defaultButton=QtWidgets.QMessageBox.No)
QtWidgets.QMessageBox.No),
QtWidgets.QMessageBox.No)
if answer != QtWidgets.QMessageBox.Yes: if answer != QtWidgets.QMessageBox.Yes:
self.data_directory_path_edit.path = AppLocation.get_data_path() self.data_directory_path_edit.path = AppLocation.get_data_path()
return return

View File

@ -38,8 +38,8 @@ class FileRenameForm(QtWidgets.QDialog, Ui_FileRenameDialog, RegistryProperties)
""" """
Constructor Constructor
""" """
super(FileRenameForm, self).__init__(Registry().get('main_window'), super(FileRenameForm, self).__init__(Registry().get('main_window'), QtCore.Qt.WindowSystemMenuHint |
QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint) QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowCloseButtonHint)
self._setup() self._setup()
def _setup(self): def _setup(self):

View File

@ -206,7 +206,6 @@ class FirstTimeForm(QtWidgets.QWizard, UiFirstTimeWizard, RegistryProperties):
trace_error_handler(log) trace_error_handler(log)
self.update_screen_list_combo() self.update_screen_list_combo()
self.application.process_events() self.application.process_events()
# TODO: Tested at home
self.downloading = translate('OpenLP.FirstTimeWizard', 'Downloading {name}...') self.downloading = translate('OpenLP.FirstTimeWizard', 'Downloading {name}...')
if self.has_run_wizard: if self.has_run_wizard:
self.songs_check_box.setChecked(self.plugin_manager.get_plugin_by_name('songs').is_active()) self.songs_check_box.setChecked(self.plugin_manager.get_plugin_by_name('songs').is_active())
@ -563,7 +562,6 @@ class FirstTimeForm(QtWidgets.QWizard, UiFirstTimeWizard, RegistryProperties):
item = self.songs_list_widget.item(i) item = self.songs_list_widget.item(i)
if item.checkState() == QtCore.Qt.Checked: if item.checkState() == QtCore.Qt.Checked:
filename, sha256 = item.data(QtCore.Qt.UserRole) filename, sha256 = item.data(QtCore.Qt.UserRole)
# TODO: Tested at home
self._increment_progress_bar(self.downloading.format(name=filename), 0) self._increment_progress_bar(self.downloading.format(name=filename), 0)
self.previous_size = 0 self.previous_size = 0
destination = os.path.join(songs_destination, str(filename)) destination = os.path.join(songs_destination, str(filename))
@ -576,7 +574,6 @@ class FirstTimeForm(QtWidgets.QWizard, UiFirstTimeWizard, RegistryProperties):
item = bibles_iterator.value() item = bibles_iterator.value()
if item.parent() and item.checkState(0) == QtCore.Qt.Checked: if item.parent() and item.checkState(0) == QtCore.Qt.Checked:
bible, sha256 = item.data(0, QtCore.Qt.UserRole) bible, sha256 = item.data(0, QtCore.Qt.UserRole)
# TODO: Tested at home
self._increment_progress_bar(self.downloading.format(name=bible), 0) self._increment_progress_bar(self.downloading.format(name=bible), 0)
self.previous_size = 0 self.previous_size = 0
if not url_get_file(self, '{path}{name}'.format(path=self.bibles_url, name=bible), if not url_get_file(self, '{path}{name}'.format(path=self.bibles_url, name=bible),
@ -589,7 +586,6 @@ class FirstTimeForm(QtWidgets.QWizard, UiFirstTimeWizard, RegistryProperties):
item = self.themes_list_widget.item(i) item = self.themes_list_widget.item(i)
if item.checkState() == QtCore.Qt.Checked: if item.checkState() == QtCore.Qt.Checked:
theme, sha256 = item.data(QtCore.Qt.UserRole) theme, sha256 = item.data(QtCore.Qt.UserRole)
# TODO: Tested at home
self._increment_progress_bar(self.downloading.format(name=theme), 0) self._increment_progress_bar(self.downloading.format(name=theme), 0)
self.previous_size = 0 self.previous_size = 0
if not url_get_file(self, '{path}{name}'.format(path=self.themes_url, name=theme), if not url_get_file(self, '{path}{name}'.format(path=self.themes_url, name=theme),

View File

@ -37,7 +37,8 @@ class FirstTimeLanguageForm(QtWidgets.QDialog, Ui_FirstTimeLanguageDialog):
""" """
Constructor Constructor
""" """
super(FirstTimeLanguageForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint) super(FirstTimeLanguageForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint |
QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowCloseButtonHint)
self.setupUi(self) self.setupUi(self)
self.qm_list = LanguageManager.get_qm_list() self.qm_list = LanguageManager.get_qm_list()
self.language_combo_box.addItem('Autodetect') self.language_combo_box.addItem('Autodetect')

View File

@ -130,8 +130,7 @@ class FormattingTagController(object):
elif not match.group('empty'): elif not match.group('empty'):
end_tags.append(tag) end_tags.append(tag)
match = self.html_tag_regex.search(start_html, match.end()) match = self.html_tag_regex.search(start_html, match.end())
# TODO: Verify format() works with lambda return ''.join(map(lambda tag: '</{tag}>'.format(tag=tag), reversed(end_tags)))
return ''.join(map(lambda tag: '</%s>' % tag, reversed(end_tags)))
def start_tag_changed(self, start_html, end_html): def start_tag_changed(self, start_html, end_html):
""" """

View File

@ -51,7 +51,8 @@ class FormattingTagForm(QtWidgets.QDialog, Ui_FormattingTagDialog, FormattingTag
""" """
Constructor Constructor
""" """
super(FormattingTagForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint) super(FormattingTagForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint |
QtCore.Qt.WindowCloseButtonHint)
self.setupUi(self) self.setupUi(self)
self._setup() self._setup()
@ -122,8 +123,7 @@ class FormattingTagForm(QtWidgets.QDialog, Ui_FormattingTagDialog, FormattingTag
self.tag_table_widget.item(count, 2).text(), self.tag_table_widget.item(count, 2).text(),
self.tag_table_widget.item(count, 3).text()) self.tag_table_widget.item(count, 3).text())
if error: if error:
QtWidgets.QMessageBox.warning(self, translate('OpenLP.FormattingTagForm', 'Validation Error'), error, QtWidgets.QMessageBox.warning(self, translate('OpenLP.FormattingTagForm', 'Validation Error'), error)
QtWidgets.QMessageBox.Ok)
self.tag_table_widget.selectRow(count) self.tag_table_widget.selectRow(count)
return return
count += 1 count += 1
@ -198,6 +198,5 @@ class FormattingTagForm(QtWidgets.QDialog, Ui_FormattingTagDialog, FormattingTag
if tag: if tag:
self.tag_table_widget.setItem(pre_row, 3, QtWidgets.QTableWidgetItem(tag)) self.tag_table_widget.setItem(pre_row, 3, QtWidgets.QTableWidgetItem(tag))
if errors: if errors:
QtWidgets.QMessageBox.warning(self, translate('OpenLP.FormattingTagForm', 'Validation Error'), errors, QtWidgets.QMessageBox.warning(self, translate('OpenLP.FormattingTagForm', 'Validation Error'), errors)
QtWidgets.QMessageBox.Ok)
self.tag_table_widget.resizeRowsToContents() self.tag_table_widget.resizeRowsToContents()

View File

@ -48,7 +48,7 @@ class PathEdit(QtWidgets.QWidget):
:type parent: QWidget or None :type parent: QWidget or None
:param dialog_caption: Used to customise the caption in the QFileDialog. :param dialog_caption: Used to customise the caption in the QFileDialog.
:param dialog_caption: str :type dialog_caption: str
:param default_path: The default path. This is set as the path when the revert button is clicked :param default_path: The default path. This is set as the path when the revert button is clicked
:type default_path: str :type default_path: str

View File

@ -25,7 +25,7 @@ The :mod:``wizard`` module provides generic wizard tools for OpenLP.
import logging import logging
import os import os
from PyQt5 import QtGui, QtWidgets from PyQt5 import QtCore, QtGui, QtWidgets
from openlp.core.common import Registry, RegistryProperties, Settings, UiStrings, translate, is_macosx from openlp.core.common import Registry, RegistryProperties, Settings, UiStrings, translate, is_macosx
from openlp.core.lib import build_icon from openlp.core.lib import build_icon
@ -50,13 +50,13 @@ class WizardStrings(object):
# These strings should need a good reason to be retranslated elsewhere. # These strings should need a good reason to be retranslated elsewhere.
FinishedImport = translate('OpenLP.Ui', 'Finished import.') FinishedImport = translate('OpenLP.Ui', 'Finished import.')
FormatLabel = translate('OpenLP.Ui', 'Format:') FormatLabel = translate('OpenLP.Ui', 'Format:')
HeaderStyle = '<span style="font-size:14pt; font-weight:600;">%s</span>' HeaderStyle = '<span style="font-size:14pt; font-weight:600;">{text}</span>'
Importing = translate('OpenLP.Ui', 'Importing') Importing = translate('OpenLP.Ui', 'Importing')
ImportingType = translate('OpenLP.Ui', 'Importing "%s"...') ImportingType = translate('OpenLP.Ui', 'Importing "{source}"...')
ImportSelect = translate('OpenLP.Ui', 'Select Import Source') ImportSelect = translate('OpenLP.Ui', 'Select Import Source')
ImportSelectLong = translate('OpenLP.Ui', 'Select the import format and the location to import from.') ImportSelectLong = translate('OpenLP.Ui', 'Select the import format and the location to import from.')
OpenTypeFile = translate('OpenLP.Ui', 'Open %s File') OpenTypeFile = translate('OpenLP.Ui', 'Open {file_type} File')
OpenTypeFolder = translate('OpenLP.Ui', 'Open %s Folder') OpenTypeFolder = translate('OpenLP.Ui', 'Open {folder_name} Folder')
PercentSymbolFormat = translate('OpenLP.Ui', '%p%') PercentSymbolFormat = translate('OpenLP.Ui', '%p%')
Ready = translate('OpenLP.Ui', 'Ready.') Ready = translate('OpenLP.Ui', 'Ready.')
StartingImport = translate('OpenLP.Ui', 'Starting import...') StartingImport = translate('OpenLP.Ui', 'Starting import...')
@ -93,7 +93,10 @@ class OpenLPWizard(QtWidgets.QWizard, RegistryProperties):
""" """
Constructor Constructor
""" """
super(OpenLPWizard, self).__init__(parent) # QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint remove the "?" buttons from windows,
# QtCore.Qt.WindowCloseButtonHint enables the "x" button to close these windows.
super(OpenLPWizard, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint |
QtCore.Qt.WindowCloseButtonHint)
self.plugin = plugin self.plugin = plugin
self.with_progress_page = add_progress_page self.with_progress_page = add_progress_page
self.setFixedWidth(640) self.setFixedWidth(640)

View File

@ -689,7 +689,7 @@ class AudioPlayer(OpenLPMixin, QtCore.QObject):
""" """
Skip forward to the next track in the list Skip forward to the next track in the list
""" """
self.playerlist.next() self.playlist.next()
def go_to(self, index): def go_to(self, index):
""" """

View File

@ -920,8 +920,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
QtWidgets.QMessageBox.information(self, translate('OpenLP.MainWindow', 'Import settings'), QtWidgets.QMessageBox.information(self, translate('OpenLP.MainWindow', 'Import settings'),
translate('OpenLP.MainWindow', translate('OpenLP.MainWindow',
'OpenLP will now close. Imported settings will ' 'OpenLP will now close. Imported settings will '
'be applied the next time you start OpenLP.'), 'be applied the next time you start OpenLP.'))
QtWidgets.QMessageBox.StandardButtons(QtWidgets.QMessageBox.Ok))
self.settings_imported = True self.settings_imported = True
self.clean_up() self.clean_up()
QtCore.QCoreApplication.exit() QtCore.QCoreApplication.exit()
@ -1316,7 +1315,6 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
self.recent_files_menu.clear() self.recent_files_menu.clear()
for file_id, filename in enumerate(recent_files_to_display): for file_id, filename in enumerate(recent_files_to_display):
log.debug('Recent file name: {name}'.format(name=filename)) log.debug('Recent file name: {name}'.format(name=filename))
# TODO: Should be good
action = create_action(self, '', action = create_action(self, '',
text='&{n} {name}'.format(n=file_id + 1, text='&{n} {name}'.format(n=file_id + 1,
name=os.path.splitext(os.path.basename(str(filename)))[0]), name=os.path.splitext(os.path.basename(str(filename)))[0]),

View File

@ -468,9 +468,10 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties):
player = self.media_players[used_players[0]] player = self.media_players[used_players[0]]
if suffix not in player.video_extensions_list and suffix not in player.audio_extensions_list: if suffix not in player.video_extensions_list and suffix not in player.audio_extensions_list:
# Media could not be loaded correctly # Media could not be loaded correctly
critical_error_message_box(translate('MediaPlugin.MediaItem', 'Unsupported Media File'), critical_error_message_box(
translate('MediaPlugin.MediaItem', 'File %s not supported using player %s') % translate('MediaPlugin.MediaItem', 'Unsupported Media File'),
(service_item.get_frame_path(), used_players[0])) translate('MediaPlugin.MediaItem', 'File {file_path} not supported using player {player_name}'
).format(file_path=service_item.get_frame_path(), player_name=used_players[0]))
return False return False
media_data = MediaInfoWrapper.parse(service_item.get_frame_path()) media_data = MediaInfoWrapper.parse(service_item.get_frame_path())
# duration returns in milli seconds # duration returns in milli seconds

View File

@ -41,7 +41,8 @@ class PluginForm(QtWidgets.QDialog, Ui_PluginViewDialog, RegistryProperties):
""" """
Constructor Constructor
""" """
super(PluginForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint) super(PluginForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint |
QtCore.Qt.WindowCloseButtonHint)
self.active_plugin = None self.active_plugin = None
self.programatic_change = False self.programatic_change = False
self.setupUi(self) self.setupUi(self)
@ -60,7 +61,6 @@ class PluginForm(QtWidgets.QDialog, Ui_PluginViewDialog, RegistryProperties):
self._clear_details() self._clear_details()
self.programatic_change = True self.programatic_change = True
plugin_list_width = 0 plugin_list_width = 0
# TODO: Tested at home
for plugin in self.plugin_manager.plugins: for plugin in self.plugin_manager.plugins:
item = QtWidgets.QListWidgetItem(self.plugin_list_widget) item = QtWidgets.QListWidgetItem(self.plugin_list_widget)
# We do this just to make 100% sure the status is an integer as # We do this just to make 100% sure the status is an integer as
@ -137,7 +137,6 @@ class PluginForm(QtWidgets.QDialog, Ui_PluginViewDialog, RegistryProperties):
self.active_plugin.app_startup() self.active_plugin.app_startup()
else: else:
self.active_plugin.toggle_status(PluginStatus.Inactive) self.active_plugin.toggle_status(PluginStatus.Inactive)
# TODO: Tested at home
status_text = translate('OpenLP.PluginForm', '{name} (Inactive)') status_text = translate('OpenLP.PluginForm', '{name} (Inactive)')
if self.active_plugin.status == PluginStatus.Active: if self.active_plugin.status == PluginStatus.Active:
status_text = translate('OpenLP.PluginForm', '{name} (Active)') status_text = translate('OpenLP.PluginForm', '{name} (Active)')

View File

@ -125,8 +125,8 @@ class PrintServiceForm(QtWidgets.QDialog, Ui_PrintServiceDialog, RegistryPropert
""" """
Constructor Constructor
""" """
super(PrintServiceForm, self).__init__(Registry().get('main_window'), super(PrintServiceForm, self).__init__(Registry().get('main_window'), QtCore.Qt.WindowSystemMenuHint |
QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint) QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowCloseButtonHint)
self.printer = QtPrintSupport.QPrinter() self.printer = QtPrintSupport.QPrinter()
self.print_dialog = QtPrintSupport.QPrintDialog(self.printer, self) self.print_dialog = QtPrintSupport.QPrintDialog(self.printer, self)
self.document = QtGui.QTextDocument() self.document = QtGui.QTextDocument()

View File

@ -142,7 +142,8 @@ class ProjectorEditForm(QtWidgets.QDialog, Ui_ProjectorEditForm):
editProjector = QtCore.pyqtSignal(object) editProjector = QtCore.pyqtSignal(object)
def __init__(self, parent=None, projectordb=None): def __init__(self, parent=None, projectordb=None):
super(ProjectorEditForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint) super(ProjectorEditForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint |
QtCore.Qt.WindowCloseButtonHint)
self.projectordb = projectordb self.projectordb = projectordb
self.setupUi(self) self.setupUi(self)
self.button_box.accepted.connect(self.accept_me) self.button_box.accepted.connect(self.accept_me)

View File

@ -39,6 +39,7 @@ from openlp.core.lib.projector.constants import ERROR_MSG, ERROR_STRING, E_AUTHE
S_INITIALIZE, S_NOT_CONNECTED, S_OFF, S_ON, S_STANDBY, S_WARMUP S_INITIALIZE, S_NOT_CONNECTED, S_OFF, S_ON, S_STANDBY, S_WARMUP
from openlp.core.lib.projector.db import ProjectorDB from openlp.core.lib.projector.db import ProjectorDB
from openlp.core.lib.projector.pjlink1 import PJLink from openlp.core.lib.projector.pjlink1 import PJLink
from openlp.core.lib.projector.pjlink2 import PJLinkUDP
from openlp.core.ui.projector.editform import ProjectorEditForm from openlp.core.ui.projector.editform import ProjectorEditForm
from openlp.core.ui.projector.sourceselectform import SourceSelectTabs, SourceSelectSingle from openlp.core.ui.projector.sourceselectform import SourceSelectTabs, SourceSelectSingle
@ -290,6 +291,8 @@ class ProjectorManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, UiProjecto
self.settings_section = 'projector' self.settings_section = 'projector'
self.projectordb = projectordb self.projectordb = projectordb
self.projector_list = [] self.projector_list = []
self.pjlink_udp = PJLinkUDP()
self.pjlink_udp.projector_list = self.projector_list
self.source_select_form = None self.source_select_form = None
def bootstrap_initialise(self): def bootstrap_initialise(self):
@ -662,6 +665,20 @@ class ProjectorManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, UiProjecto
message = '%s<b>%s</b>: %s<br />' % (message, message = '%s<b>%s</b>: %s<br />' % (message,
translate('OpenLP.ProjectorManager', 'Current source input is'), translate('OpenLP.ProjectorManager', 'Current source input is'),
projector.link.source) projector.link.source)
if projector.link.pjlink_class == '2':
# Information only available for PJLink Class 2 projectors
message += '<b>{title}</b>: {data}<br /><br />'.format(title=translate('OpenLP.ProjectorManager',
'Serial Number'),
data=projector.serial_no)
message += '<b>{title}</b>: {data}<br /><br />'.format(title=translate('OpenLP.ProjectorManager',
'Software Version'),
data=projector.sw_version)
message += '<b>{title}</b>: {data}<br /><br />'.format(title=translate('OpenLP.ProjectorManager',
'Lamp type'),
data=projector.model_lamp)
message += '<b>{title}</b>: {data}<br /><br />'.format(title=translate('OpenLP.ProjectorManager',
'Filter type'),
data=projector.model_filter)
count = 1 count = 1
for item in projector.link.lamp: for item in projector.link.lamp:
message += '<b>{title} {count}</b> {status} '.format(title=translate('OpenLP.ProjectorManager', message += '<b>{title} {count}</b> {status} '.format(title=translate('OpenLP.ProjectorManager',
@ -973,7 +990,7 @@ class ProjectorItem(QtCore.QObject):
self.poll_time = None self.poll_time = None
self.socket_timeout = None self.socket_timeout = None
self.status = S_NOT_CONNECTED self.status = S_NOT_CONNECTED
super(ProjectorItem, self).__init__() super().__init__()
def not_implemented(function): def not_implemented(function):

View File

@ -233,7 +233,8 @@ class SourceSelectTabs(QtWidgets.QDialog):
:param projectordb: ProjectorDB session to use :param projectordb: ProjectorDB session to use
""" """
log.debug('Initializing SourceSelectTabs()') log.debug('Initializing SourceSelectTabs()')
super(SourceSelectTabs, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint) super(SourceSelectTabs, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint |
QtCore.Qt.WindowCloseButtonHint)
self.setMinimumWidth(350) self.setMinimumWidth(350)
self.projectordb = projectordb self.projectordb = projectordb
self.edit = edit self.edit = edit
@ -388,7 +389,8 @@ class SourceSelectSingle(QtWidgets.QDialog):
""" """
log.debug('Initializing SourceSelectSingle()') log.debug('Initializing SourceSelectSingle()')
self.projectordb = projectordb self.projectordb = projectordb
super(SourceSelectSingle, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint) super(SourceSelectSingle, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint |
QtCore.Qt.WindowCloseButtonHint)
self.edit = edit self.edit = edit
if self.edit: if self.edit:
title = translate('OpenLP.SourceSelectForm', 'Edit Projector Source Text') title = translate('OpenLP.SourceSelectForm', 'Edit Projector Source Text')

View File

@ -37,8 +37,8 @@ class ServiceItemEditForm(QtWidgets.QDialog, Ui_ServiceItemEditDialog, RegistryP
""" """
Constructor Constructor
""" """
super(ServiceItemEditForm, self).__init__(Registry().get('main_window'), super(ServiceItemEditForm, self).__init__(Registry().get('main_window'), QtCore.Qt.WindowSystemMenuHint |
QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint) QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowCloseButtonHint)
self.setupUi(self) self.setupUi(self)
self.item_list = [] self.item_list = []
self.list_widget.currentRowChanged.connect(self.on_current_row_changed) self.list_widget.currentRowChanged.connect(self.on_current_row_changed)

View File

@ -66,6 +66,12 @@ class ServiceManagerList(QtWidgets.QTreeWidget):
elif event.key() == QtCore.Qt.Key_Down: elif event.key() == QtCore.Qt.Key_Down:
self.service_manager.on_move_selection_down() self.service_manager.on_move_selection_down()
event.accept() event.accept()
elif event.key() == QtCore.Qt.Key_Right:
self.service_manager.on_expand_selection()
event.accept()
elif event.key() == QtCore.Qt.Key_Left:
self.service_manager.on_collapse_selection()
event.accept()
elif event.key() == QtCore.Qt.Key_Delete: elif event.key() == QtCore.Qt.Key_Delete:
self.service_manager.on_delete_from_service() self.service_manager.on_delete_from_service()
event.accept() event.accept()
@ -1119,6 +1125,35 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ServiceMa
return return
self.service_manager_list.setCurrentItem(item_after) self.service_manager_list.setCurrentItem(item_after)
def on_expand_selection(self):
"""
Expands cursor selection on the window. Called by the right arrow
"""
item = self.service_manager_list.currentItem()
# Since we only have 2 levels we find them by checking for children
if item.childCount():
if not self.service_manager_list.isExpanded(self.service_manager_list.currentIndex()):
self.service_manager_list.expandItem(item)
self.service_manager.expanded(item)
# If not expanded, Expand it
self.service_manager_list.setCurrentItem(self.service_manager_list.itemBelow(item))
# Then move selection down to child whether it needed to be expanded or not
def on_collapse_selection(self):
"""
Collapses cursor selection on the window Called by the left arrow
"""
item = self.service_manager_list.currentItem()
# Since we only have 2 levels we find them by checking for children
if item.childCount():
if self.service_manager_list.isExpanded(self.service_manager_list.currentIndex()):
self.service_manager_list.collapseItem(item)
self.service_manager.collapsed(item)
else: # If selection is lower level
self.service_manager_list.collapseItem(item.parent())
self.service_manager.collapsed(item.parent())
self.service_manager_list.setCurrentItem(item.parent())
def on_collapse_all(self, field=None): def on_collapse_all(self, field=None):
""" """
Collapse all the service items. Collapse all the service items.

View File

@ -37,8 +37,8 @@ class ServiceNoteForm(QtWidgets.QDialog, RegistryProperties):
""" """
Constructor Constructor
""" """
super(ServiceNoteForm, self).__init__(Registry().get('main_window'), super(ServiceNoteForm, self).__init__(Registry().get('main_window'), QtCore.Qt.WindowSystemMenuHint |
QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint) QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowCloseButtonHint)
self.setupUi() self.setupUi()
self.retranslateUi() self.retranslateUi()

View File

@ -46,7 +46,8 @@ class SettingsForm(QtWidgets.QDialog, Ui_SettingsDialog, RegistryProperties):
""" """
Registry().register('settings_form', self) Registry().register('settings_form', self)
Registry().register_function('bootstrap_post_set_up', self.bootstrap_post_set_up) Registry().register_function('bootstrap_post_set_up', self.bootstrap_post_set_up)
super(SettingsForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint) super(SettingsForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint |
QtCore.Qt.WindowCloseButtonHint)
self.processes = [] self.processes = []
self.setupUi(self) self.setupUi(self)
self.setting_list_widget.currentRowChanged.connect(self.list_item_changed) self.setting_list_widget.currentRowChanged.connect(self.list_item_changed)

View File

@ -44,7 +44,8 @@ class ShortcutListForm(QtWidgets.QDialog, Ui_ShortcutListDialog, RegistryPropert
""" """
Constructor Constructor
""" """
super(ShortcutListForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint) super(ShortcutListForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint |
QtCore.Qt.WindowCloseButtonHint)
self.setupUi(self) self.setupUi(self)
self.changed_actions = {} self.changed_actions = {}
self.action_list = ActionList.get_instance() self.action_list = ActionList.get_instance()
@ -279,9 +280,7 @@ class ShortcutListForm(QtWidgets.QDialog, Ui_ShortcutListDialog, RegistryPropert
return return
if QtWidgets.QMessageBox.question(self, translate('OpenLP.ShortcutListDialog', 'Restore Default Shortcuts'), if QtWidgets.QMessageBox.question(self, translate('OpenLP.ShortcutListDialog', 'Restore Default Shortcuts'),
translate('OpenLP.ShortcutListDialog', 'Do you want to restore all ' translate('OpenLP.ShortcutListDialog', 'Do you want to restore all '
'shortcuts to their defaults?'), 'shortcuts to their defaults?')
QtWidgets.QMessageBox.StandardButtons(QtWidgets.QMessageBox.Yes |
QtWidgets.QMessageBox.No)
) == QtWidgets.QMessageBox.No: ) == QtWidgets.QMessageBox.No:
return return
self._adjust_button(self.primary_push_button, False, text='') self._adjust_button(self.primary_push_button, False, text='')

View File

@ -38,8 +38,8 @@ class StartTimeForm(QtWidgets.QDialog, Ui_StartTimeDialog, RegistryProperties):
""" """
Constructor Constructor
""" """
super(StartTimeForm, self).__init__(Registry().get('main_window'), super(StartTimeForm, self).__init__(Registry().get('main_window'), QtCore.Qt.WindowSystemMenuHint |
QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint) QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowCloseButtonHint)
self.setupUi(self) self.setupUi(self)
def exec(self): def exec(self):

View File

@ -257,10 +257,9 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ThemeManage
Renames an existing theme to a new name Renames an existing theme to a new name
:param field: :param field:
""" """
# TODO: Check for delayed format() conversions
if self._validate_theme_action(translate('OpenLP.ThemeManager', 'You must select a theme to rename.'), if self._validate_theme_action(translate('OpenLP.ThemeManager', 'You must select a theme to rename.'),
translate('OpenLP.ThemeManager', 'Rename Confirmation'), translate('OpenLP.ThemeManager', 'Rename Confirmation'),
translate('OpenLP.ThemeManager', 'Rename %s theme?'), False, False): translate('OpenLP.ThemeManager', 'Rename {theme_name} theme?'), False, False):
item = self.theme_list_widget.currentItem() item = self.theme_list_widget.currentItem()
old_theme_name = item.data(QtCore.Qt.UserRole) old_theme_name = item.data(QtCore.Qt.UserRole)
self.file_rename_form.file_name_edit.setText(old_theme_name) self.file_rename_form.file_name_edit.setText(old_theme_name)
@ -334,10 +333,9 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ThemeManage
Delete a theme triggered by the UI. Delete a theme triggered by the UI.
:param field: :param field:
""" """
# TODO: Verify delayed format() conversions
if self._validate_theme_action(translate('OpenLP.ThemeManager', 'You must select a theme to delete.'), if self._validate_theme_action(translate('OpenLP.ThemeManager', 'You must select a theme to delete.'),
translate('OpenLP.ThemeManager', 'Delete Confirmation'), translate('OpenLP.ThemeManager', 'Delete Confirmation'),
translate('OpenLP.ThemeManager', 'Delete %s theme?')): translate('OpenLP.ThemeManager', 'Delete {theme_name} theme?')):
item = self.theme_list_widget.currentItem() item = self.theme_list_widget.currentItem()
theme = item.text() theme = item.text()
row = self.theme_list_widget.row(item) row = self.theme_list_widget.row(item)
@ -539,9 +537,7 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ThemeManage
translate('OpenLP.ThemeManager', translate('OpenLP.ThemeManager',
'Theme {name} already exists. ' 'Theme {name} already exists. '
'Do you want to replace it?').format(name=theme_name), 'Do you want to replace it?').format(name=theme_name),
QtWidgets.QMessageBox.StandardButtons(QtWidgets.QMessageBox.Yes | defaultButton=QtWidgets.QMessageBox.No)
QtWidgets.QMessageBox.No),
QtWidgets.QMessageBox.No)
return ret == QtWidgets.QMessageBox.Yes return ret == QtWidgets.QMessageBox.Yes
def unzip_theme(self, file_name, directory): def unzip_theme(self, file_name, directory):
@ -785,9 +781,8 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ThemeManage
# confirm deletion # confirm deletion
if confirm: if confirm:
answer = QtWidgets.QMessageBox.question( answer = QtWidgets.QMessageBox.question(
self, confirm_title, confirm_text % theme, self, confirm_title, confirm_text.format(theme_name=theme),
QtWidgets.QMessageBox.StandardButtons(QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No), defaultButton=QtWidgets.QMessageBox.No)
QtWidgets.QMessageBox.No)
if answer == QtWidgets.QMessageBox.No: if answer == QtWidgets.QMessageBox.No:
return False return False
# should be the same unless default # should be the same unless default

View File

@ -88,21 +88,20 @@ JAVASCRIPT = """
} }
} }
""" """
# TODO: Verify format() with variable templates
CSS = """ CSS = """
#alert { #alert {{
position: absolute; position: absolute;
left: 0px; left: 0px;
top: 0px; top: 0px;
z-index: 10; z-index: 10;
width: 100%%; width: 100%;
vertical-align: %s; vertical-align: {vertical_align};
font-family: %s; font-family: {font_family};
font-size: %spt; font-size: {font_size:d}pt;
color: %s; color: {color};
background-color: %s; background-color: {background_color};
word-wrap: break-word; word-wrap: break-word;
} }}
""" """
HTML = """ HTML = """
@ -228,8 +227,11 @@ class AlertsPlugin(Plugin):
Add CSS to the main display. Add CSS to the main display.
""" """
align = VerticalType.Names[self.settings_tab.location] align = VerticalType.Names[self.settings_tab.location]
return CSS % (align, self.settings_tab.font_face, self.settings_tab.font_size, self.settings_tab.font_color, return CSS.format(vertical_align=align,
self.settings_tab.background_color) font_family=self.settings_tab.font_face,
font_size=self.settings_tab.font_size,
color=self.settings_tab.font_color,
background_color=self.settings_tab.background_color)
@staticmethod @staticmethod
def get_display_html(): def get_display_html():

View File

@ -36,8 +36,8 @@ class AlertForm(QtWidgets.QDialog, Ui_AlertDialog):
""" """
Initialise the alert form Initialise the alert form
""" """
super(AlertForm, self).__init__(Registry().get('main_window'), super(AlertForm, self).__init__(Registry().get('main_window'), QtCore.Qt.WindowSystemMenuHint |
QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint) QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowCloseButtonHint)
self.manager = plugin.manager self.manager = plugin.manager
self.plugin = plugin self.plugin = plugin
self.item_id = None self.item_id = None
@ -180,9 +180,7 @@ class AlertForm(QtWidgets.QDialog, Ui_AlertDialog):
translate('AlertsPlugin.AlertForm', 'No Parameter Found'), translate('AlertsPlugin.AlertForm', 'No Parameter Found'),
translate('AlertsPlugin.AlertForm', translate('AlertsPlugin.AlertForm',
'You have not entered a parameter to be replaced.\n' 'You have not entered a parameter to be replaced.\n'
'Do you want to continue anyway?'), 'Do you want to continue anyway?')
QtWidgets.QMessageBox.StandardButtons(
QtWidgets.QMessageBox.No | QtWidgets.QMessageBox.Yes)
) == QtWidgets.QMessageBox.No: ) == QtWidgets.QMessageBox.No:
self.parameter_edit.setFocus() self.parameter_edit.setFocus()
return False return False
@ -193,9 +191,7 @@ class AlertForm(QtWidgets.QDialog, Ui_AlertDialog):
translate('AlertsPlugin.AlertForm', 'No Placeholder Found'), translate('AlertsPlugin.AlertForm', 'No Placeholder Found'),
translate('AlertsPlugin.AlertForm', translate('AlertsPlugin.AlertForm',
'The alert text does not contain \'<>\'.\n' 'The alert text does not contain \'<>\'.\n'
'Do you want to continue anyway?'), 'Do you want to continue anyway?')
QtWidgets.QMessageBox.StandardButtons(
QtWidgets.QMessageBox.No | QtWidgets.QMessageBox.Yes)
) == QtWidgets.QMessageBox.No: ) == QtWidgets.QMessageBox.No:
self.parameter_edit.setFocus() self.parameter_edit.setFocus()
return False return False

View File

@ -38,7 +38,7 @@ __default_settings__ = {
'bibles/db password': '', 'bibles/db password': '',
'bibles/db hostname': '', 'bibles/db hostname': '',
'bibles/db database': '', 'bibles/db database': '',
'bibles/last search type': BibleSearch.Combined, 'bibles/last used search type': BibleSearch.Combined,
'bibles/reset to combined quick search': True, 'bibles/reset to combined quick search': True,
'bibles/verse layout style': LayoutStyle.VersePerSlide, 'bibles/verse layout style': LayoutStyle.VersePerSlide,
'bibles/book name language': LanguageSelection.Bible, 'bibles/book name language': LanguageSelection.Bible,

View File

@ -421,8 +421,8 @@ class BibleImportForm(OpenLPWizard):
Allow for localisation of the bible import wizard. Allow for localisation of the bible import wizard.
""" """
self.setWindowTitle(translate('BiblesPlugin.ImportWizardForm', 'Bible Import Wizard')) self.setWindowTitle(translate('BiblesPlugin.ImportWizardForm', 'Bible Import Wizard'))
self.title_label.setText(WizardStrings.HeaderStyle % translate('OpenLP.Ui', self.title_label.setText(WizardStrings.HeaderStyle.format(text=translate('OpenLP.Ui',
'Welcome to the Bible Import Wizard')) 'Welcome to the Bible Import Wizard')))
self.information_label.setText( self.information_label.setText(
translate('BiblesPlugin.ImportWizardForm', translate('BiblesPlugin.ImportWizardForm',
'This wizard will help you to import Bibles from a variety of ' 'This wizard will help you to import Bibles from a variety of '

View File

@ -49,7 +49,8 @@ class BookNameForm(QDialog, Ui_BookNameDialog):
""" """
Constructor Constructor
""" """
super(BookNameForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint) super(BookNameForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint |
QtCore.Qt.WindowCloseButtonHint)
self.setupUi(self) self.setupUi(self)
self.custom_signals() self.custom_signals()
self.book_names = BibleStrings().BookNames self.book_names = BibleStrings().BookNames

View File

@ -45,7 +45,8 @@ class EditBibleForm(QtWidgets.QDialog, Ui_EditBibleDialog, RegistryProperties):
""" """
Constructor Constructor
""" """
super(EditBibleForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint) super(EditBibleForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint |
QtCore.Qt.WindowCloseButtonHint)
self.media_item = media_item self.media_item = media_item
self.book_names = BibleStrings().BookNames self.book_names = BibleStrings().BookNames
self.setupUi(self) self.setupUi(self)

View File

@ -47,7 +47,8 @@ class LanguageForm(QDialog, Ui_LanguageDialog):
""" """
Constructor Constructor
""" """
super(LanguageForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint) super(LanguageForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint |
QtCore.Qt.WindowCloseButtonHint)
self.setupUi(self) self.setupUi(self)
def exec(self, bible_name): def exec(self, bible_name):

View File

@ -221,18 +221,16 @@ def update_reference_separators():
REFERENCE_SEPARATORS['sep_{role}'.format(role=role)] = '\s*(?:{source})\s*'.format(source=source_string) REFERENCE_SEPARATORS['sep_{role}'.format(role=role)] = '\s*(?:{source})\s*'.format(source=source_string)
REFERENCE_SEPARATORS['sep_{role}_default'.format(role=role)] = default_separators[index] REFERENCE_SEPARATORS['sep_{role}_default'.format(role=role)] = default_separators[index]
# verse range match: (<chapter>:)?<verse>(-((<chapter>:)?<verse>|end)?)? # verse range match: (<chapter>:)?<verse>(-((<chapter>:)?<verse>|end)?)?
# TODO: Check before converting this string range_regex = '(?:(?P<from_chapter>[0-9]+){sep_v})?' \
range_regex = '(?:(?P<from_chapter>[0-9]+)%(sep_v)s)?' \ '(?P<from_verse>[0-9]+)(?P<range_to>{sep_r}(?:(?:(?P<to_chapter>' \
'(?P<from_verse>[0-9]+)(?P<range_to>%(sep_r)s(?:(?:(?P<to_chapter>' \ '[0-9]+){sep_v})?(?P<to_verse>[0-9]+)|{sep_e})?)?'.format_map(REFERENCE_SEPARATORS)
'[0-9]+)%(sep_v)s)?(?P<to_verse>[0-9]+)|%(sep_e)s)?)?' % REFERENCE_SEPARATORS REFERENCE_MATCHES['range'] = re.compile(r'^\s*{range}\s*$'.format(range=range_regex), re.UNICODE)
# TODO: Test before converting re.compile strings
REFERENCE_MATCHES['range'] = re.compile('^\s*%s\s*$' % range_regex, re.UNICODE)
REFERENCE_MATCHES['range_separator'] = re.compile(REFERENCE_SEPARATORS['sep_l'], re.UNICODE) REFERENCE_MATCHES['range_separator'] = re.compile(REFERENCE_SEPARATORS['sep_l'], re.UNICODE)
# full reference match: <book>(<range>(,(?!$)|(?=$)))+ # full reference match: <book>(<range>(,(?!$)|(?=$)))+
REFERENCE_MATCHES['full'] = \ REFERENCE_MATCHES['full'] = \
re.compile('^\s*(?!\s)(?P<book>[\d]*[^\d\.]+)\.*(?<!\s)\s*' re.compile(r'^\s*(?!\s)(?P<book>[\d]*[.]?[^\d\.]+)\.*(?<!\s)\s*'
'(?P<ranges>(?:%(range_regex)s(?:%(sep_l)s(?!\s*$)|(?=\s*$)))+)\s*$' r'(?P<ranges>(?:{range_regex}(?:{sep_l}(?!\s*$)|(?=\s*$)))+)\s*$'.format(
% dict(list(REFERENCE_SEPARATORS.items()) + [('range_regex', range_regex)]), re.UNICODE) range_regex=range_regex, sep_l=REFERENCE_SEPARATORS['sep_l']), re.UNICODE)
def get_reference_separator(separator_type): def get_reference_separator(separator_type):
@ -326,7 +324,7 @@ def parse_reference(reference, bible, language_selection, book_ref_id=False):
``^\s*(?!\s)(?P<book>[\d]*[^\d]+)(?<!\s)\s*`` ``^\s*(?!\s)(?P<book>[\d]*[^\d]+)(?<!\s)\s*``
The ``book`` group starts with the first non-whitespace character. There are optional leading digits followed by The ``book`` group starts with the first non-whitespace character. There are optional leading digits followed by
non-digits. The group ends before the whitspace, or a full stop in front of the next digit. non-digits. The group ends before the whitespace, or a full stop in front of the next digit.
``(?P<ranges>(?:%(range_regex)s(?:%(sep_l)s(?!\s*$)|(?=\s*$)))+)\s*$`` ``(?P<ranges>(?:%(range_regex)s(?:%(sep_l)s(?!\s*$)|(?=\s*$)))+)\s*$``
The second group contains all ``ranges``. This can be multiple declarations of range_regex separated by a list The second group contains all ``ranges``. This can be multiple declarations of range_regex separated by a list

View File

@ -306,9 +306,8 @@ class BibleDB(Manager):
book_escaped = book book_escaped = book
for character in RESERVED_CHARACTERS: for character in RESERVED_CHARACTERS:
book_escaped = book_escaped.replace(character, '\\' + character) book_escaped = book_escaped.replace(character, '\\' + character)
# TODO: Verify regex patters before using format() regex_book = re.compile('\\s*{book}\\s*'.format(book='\\s*'.join(book_escaped.split())),
regex_book = re.compile('\s*%s\s*' % '\s*'.join( re.UNICODE | re.IGNORECASE)
book_escaped.split()), re.UNICODE | re.IGNORECASE)
if language_selection == LanguageSelection.Bible: if language_selection == LanguageSelection.Bible:
db_book = self.get_book(book) db_book = self.get_book(book)
if db_book: if db_book:

View File

@ -90,6 +90,8 @@ class BGExtract(RegistryProperties):
""" """
Extract verses from BibleGateway Extract verses from BibleGateway
""" """
NAME = 'BibleGateway'
def __init__(self, proxy_url=None): def __init__(self, proxy_url=None):
log.debug('BGExtract.init("{url}")'.format(url=proxy_url)) log.debug('BGExtract.init("{url}")'.format(url=proxy_url))
self.proxy_url = proxy_url self.proxy_url = proxy_url
@ -357,6 +359,8 @@ class BSExtract(RegistryProperties):
""" """
Extract verses from Bibleserver.com Extract verses from Bibleserver.com
""" """
NAME = 'BibleServer'
def __init__(self, proxy_url=None): def __init__(self, proxy_url=None):
log.debug('BSExtract.init("{url}")'.format(url=proxy_url)) log.debug('BSExtract.init("{url}")'.format(url=proxy_url))
self.proxy_url = proxy_url self.proxy_url = proxy_url
@ -458,6 +462,8 @@ class CWExtract(RegistryProperties):
""" """
Extract verses from CrossWalk/BibleStudyTools Extract verses from CrossWalk/BibleStudyTools
""" """
NAME = 'Crosswalk'
def __init__(self, proxy_url=None): def __init__(self, proxy_url=None):
log.debug('CWExtract.init("{url}")'.format(url=proxy_url)) log.debug('CWExtract.init("{url}")'.format(url=proxy_url))
self.proxy_url = proxy_url self.proxy_url = proxy_url

View File

@ -414,7 +414,9 @@ class BibleMediaItem(MediaManagerItem):
if self.bible: if self.bible:
book_data = self.get_common_books(self.bible, self.second_bible) book_data = self.get_common_books(self.bible, self.second_bible)
language_selection = self.plugin.manager.get_language_selection(self.bible.name) language_selection = self.plugin.manager.get_language_selection(self.bible.name)
books = [book.get_name(language_selection) for book in book_data] # Get book names + add a space to the end. Thus Psalm23 becomes Psalm 23
# when auto complete is used and user does not need to add the space manually.
books = [book.get_name(language_selection) + ' ' for book in book_data]
books.sort(key=get_locale_key) books.sort(key=get_locale_key)
set_case_insensitive_completer(books, self.search_edit) set_case_insensitive_completer(books, self.search_edit)

View File

@ -40,7 +40,7 @@ __default_settings__ = {
'custom/db password': '', 'custom/db password': '',
'custom/db hostname': '', 'custom/db hostname': '',
'custom/db database': '', 'custom/db database': '',
'custom/last search type': CustomSearch.Titles, 'custom/last used search type': CustomSearch.Titles,
'custom/display footer': True, 'custom/display footer': True,
'custom/add custom from service': True 'custom/add custom from service': True
} }

View File

@ -44,7 +44,8 @@ class EditCustomForm(QtWidgets.QDialog, Ui_CustomEditDialog):
""" """
Constructor Constructor
""" """
super(EditCustomForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint) super(EditCustomForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint |
QtCore.Qt.WindowCloseButtonHint)
self.manager = manager self.manager = manager
self.media_item = media_item self.media_item = media_item
self.setupUi(self) self.setupUi(self)

View File

@ -39,7 +39,8 @@ class EditCustomSlideForm(QtWidgets.QDialog, Ui_CustomSlideEditDialog):
""" """
Constructor Constructor
""" """
super(EditCustomSlideForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint) super(EditCustomSlideForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint |
QtCore.Qt.WindowCloseButtonHint)
self.setupUi(self) self.setupUi(self)
# Connecting signals and slots # Connecting signals and slots
self.insert_button.clicked.connect(self.on_insert_button_clicked) self.insert_button.clicked.connect(self.on_insert_button_clicked)

View File

@ -190,9 +190,7 @@ class CustomMediaItem(MediaManagerItem):
translate('CustomPlugin.MediaItem', translate('CustomPlugin.MediaItem',
'Are you sure you want to delete the "{items:d}" ' 'Are you sure you want to delete the "{items:d}" '
'selected custom slide(s)?').format(items=len(items)), 'selected custom slide(s)?').format(items=len(items)),
QtWidgets.QMessageBox.StandardButtons( defaultButton=QtWidgets.QMessageBox.Yes) == QtWidgets.QMessageBox.No:
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No),
QtWidgets.QMessageBox.Yes) == QtWidgets.QMessageBox.No:
return return
row_list = [item.row() for item in self.list_view.selectedIndexes()] row_list = [item.row() for item in self.list_view.selectedIndexes()]
row_list.sort(reverse=True) row_list.sort(reverse=True)

View File

@ -35,7 +35,8 @@ class AddGroupForm(QtWidgets.QDialog, Ui_AddGroupDialog):
""" """
Constructor Constructor
""" """
super(AddGroupForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint) super(AddGroupForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint |
QtCore.Qt.WindowCloseButtonHint)
self.setupUi(self) self.setupUi(self)
def exec(self, clear=True, show_top_level_group=False, selected_group=None): def exec(self, clear=True, show_top_level_group=False, selected_group=None):

View File

@ -33,7 +33,8 @@ class ChooseGroupForm(QtWidgets.QDialog, Ui_ChooseGroupDialog):
""" """
Constructor Constructor
""" """
super(ChooseGroupForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint) super(ChooseGroupForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint |
QtCore.Qt.WindowCloseButtonHint)
self.setupUi(self) self.setupUi(self)
def exec(self, selected_group=None): def exec(self, selected_group=None):

View File

@ -246,9 +246,7 @@ class ImageMediaItem(MediaManagerItem):
translate('ImagePlugin.MediaItem', 'Remove group'), translate('ImagePlugin.MediaItem', 'Remove group'),
translate('ImagePlugin.MediaItem', translate('ImagePlugin.MediaItem',
'Are you sure you want to remove "{name}" and everything in it?' 'Are you sure you want to remove "{name}" and everything in it?'
).format(name=item_data.group_name), ).format(name=item_data.group_name)
QtWidgets.QMessageBox.StandardButtons(QtWidgets.QMessageBox.Yes |
QtWidgets.QMessageBox.No)
) == QtWidgets.QMessageBox.Yes: ) == QtWidgets.QMessageBox.Yes:
self.recursively_delete_group(item_data) self.recursively_delete_group(item_data)
self.manager.delete_object(ImageGroups, row_item.data(0, QtCore.Qt.UserRole).id) self.manager.delete_object(ImageGroups, row_item.data(0, QtCore.Qt.UserRole).id)
@ -597,8 +595,7 @@ class ImageMediaItem(MediaManagerItem):
self, translate('ImagePlugin.MediaItem', 'Missing Image(s)'), self, translate('ImagePlugin.MediaItem', 'Missing Image(s)'),
translate('ImagePlugin.MediaItem', 'The following image(s) no longer exist: {names}\n' translate('ImagePlugin.MediaItem', 'The following image(s) no longer exist: {names}\n'
'Do you want to add the other images anyway?' 'Do you want to add the other images anyway?'
).format(names='\n'.join(missing_items_file_names)), ).format(names='\n'.join(missing_items_file_names))) == \
QtWidgets.QMessageBox.StandardButtons(QtWidgets.QMessageBox.No | QtWidgets.QMessageBox.Yes)) == \
QtWidgets.QMessageBox.No: QtWidgets.QMessageBox.No:
return False return False
# Continue with the existing images. # Continue with the existing images.

View File

@ -52,7 +52,8 @@ class MediaClipSelectorForm(QtWidgets.QDialog, Ui_MediaClipSelector, RegistryPro
""" """
Constructor Constructor
""" """
super(MediaClipSelectorForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint) super(MediaClipSelectorForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint |
QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowCloseButtonHint)
self.vlc_instance = None self.vlc_instance = None
self.vlc_media_player = None self.vlc_media_player = None
self.vlc_media = None self.vlc_media = None

View File

@ -253,15 +253,14 @@ class PdfDocument(PresentationDocument):
try: try:
if not os.path.isdir(self.get_temp_folder()): if not os.path.isdir(self.get_temp_folder()):
os.makedirs(self.get_temp_folder()) os.makedirs(self.get_temp_folder())
# The %03d in the file name is handled by each binary
if self.controller.mudrawbin: if self.controller.mudrawbin:
log.debug('loading presentation using mudraw') log.debug('loading presentation using mudraw')
# TODO: Find out where the string conversion actually happens
runlog = check_output([self.controller.mudrawbin, '-w', str(size.width()), '-h', str(size.height()), runlog = check_output([self.controller.mudrawbin, '-w', str(size.width()), '-h', str(size.height()),
'-o', os.path.join(self.get_temp_folder(), 'mainslide%03d.png'), self.file_path], '-o', os.path.join(self.get_temp_folder(), 'mainslide%03d.png'), self.file_path],
startupinfo=self.startupinfo) startupinfo=self.startupinfo)
elif self.controller.mutoolbin: elif self.controller.mutoolbin:
log.debug('loading presentation using mutool') log.debug('loading presentation using mutool')
# TODO: Find out where the string convertsion actually happens
runlog = check_output([self.controller.mutoolbin, 'draw', '-w', str(size.width()), '-h', runlog = check_output([self.controller.mutoolbin, 'draw', '-w', str(size.width()), '-h',
str(size.height()), str(size.height()),
'-o', os.path.join(self.get_temp_folder(), 'mainslide%03d.png'), self.file_path], '-o', os.path.join(self.get_temp_folder(), 'mainslide%03d.png'), self.file_path],
@ -269,7 +268,6 @@ class PdfDocument(PresentationDocument):
elif self.controller.gsbin: elif self.controller.gsbin:
log.debug('loading presentation using gs') log.debug('loading presentation using gs')
resolution = self.gs_get_resolution(size) resolution = self.gs_get_resolution(size)
# TODO: Find out where the string conversion actually happens
runlog = check_output([self.controller.gsbin, '-dSAFER', '-dNOPAUSE', '-dBATCH', '-sDEVICE=png16m', runlog = check_output([self.controller.gsbin, '-dSAFER', '-dNOPAUSE', '-dBATCH', '-sDEVICE=png16m',
'-r' + str(resolution), '-dTextAlphaBits=4', '-dGraphicsAlphaBits=4', '-r' + str(resolution), '-dTextAlphaBits=4', '-dGraphicsAlphaBits=4',
'-sOutputFile=' + os.path.join(self.get_temp_folder(), 'mainslide%03d.png'), '-sOutputFile=' + os.path.join(self.get_temp_folder(), 'mainslide%03d.png'),

View File

@ -81,7 +81,7 @@ class PowerpointController(PresentationController):
if app_version >= 12: if app_version >= 12:
self.also_supports = ['odp'] self.also_supports = ['odp']
except (OSError, ValueError): except (OSError, ValueError):
log.warning('Detection of powerpoint version using registry failed.') log.exception('Detection of powerpoint version using registry failed.')
return True return True
except OSError: except OSError:
pass pass
@ -109,9 +109,8 @@ class PowerpointController(PresentationController):
if self.process.Presentations.Count > 0: if self.process.Presentations.Count > 0:
return return
self.process.Quit() self.process.Quit()
except (AttributeError, pywintypes.com_error) as e: except (AttributeError, pywintypes.com_error):
log.exception('Exception caught while killing powerpoint process') log.exception('Exception caught while killing powerpoint process')
log.exception(e)
trace_error_handler(log) trace_error_handler(log)
self.process = None self.process = None
@ -154,9 +153,8 @@ class PowerpointDocument(PresentationDocument):
if len(ScreenList().screen_list) > 1: if len(ScreenList().screen_list) > 1:
Registry().get('main_window').activateWindow() Registry().get('main_window').activateWindow()
return True return True
except (AttributeError, pywintypes.com_error) as e: except (AttributeError, pywintypes.com_error):
log.exception('Exception caught while loading Powerpoint presentation') log.exception('Exception caught while loading Powerpoint presentation')
log.exception(e)
trace_error_handler(log) trace_error_handler(log)
return False return False
@ -192,9 +190,8 @@ class PowerpointDocument(PresentationDocument):
if self.presentation: if self.presentation:
try: try:
self.presentation.Close() self.presentation.Close()
except (AttributeError, pywintypes.com_error) as e: except (AttributeError, pywintypes.com_error):
log.exception('Caught exception while closing powerpoint presentation') log.exception('Caught exception while closing powerpoint presentation')
log.exception(e)
trace_error_handler(log) trace_error_handler(log)
self.presentation = None self.presentation = None
self.controller.remove_doc(self) self.controller.remove_doc(self)
@ -210,9 +207,8 @@ class PowerpointDocument(PresentationDocument):
try: try:
if self.controller.process.Presentations.Count == 0: if self.controller.process.Presentations.Count == 0:
return False return False
except (AttributeError, pywintypes.com_error) as e: except (AttributeError, pywintypes.com_error):
log.exception('Caught exception while in is_loaded') log.exception('Caught exception while in is_loaded')
log.exception(e)
trace_error_handler(log) trace_error_handler(log)
return False return False
return True return True
@ -229,9 +225,8 @@ class PowerpointDocument(PresentationDocument):
return False return False
if self.presentation.SlideShowWindow.View is None: if self.presentation.SlideShowWindow.View is None:
return False return False
except (AttributeError, pywintypes.com_error) as e: except (AttributeError, pywintypes.com_error):
log.exception('Caught exception while in is_active') log.exception('Caught exception while in is_active')
log.exception(e)
trace_error_handler(log) trace_error_handler(log)
return False return False
return True return True
@ -249,9 +244,8 @@ class PowerpointDocument(PresentationDocument):
self.presentation.SlideShowWindow.View.GotoSlide(self.index_map[self.blank_slide], False) self.presentation.SlideShowWindow.View.GotoSlide(self.index_map[self.blank_slide], False)
if self.blank_click: if self.blank_click:
self.presentation.SlideShowWindow.View.GotoClick(self.blank_click) self.presentation.SlideShowWindow.View.GotoClick(self.blank_click)
except (AttributeError, pywintypes.com_error) as e: except (AttributeError, pywintypes.com_error):
log.exception('Caught exception while in unblank_screen') log.exception('Caught exception while in unblank_screen')
log.exception(e)
trace_error_handler(log) trace_error_handler(log)
self.show_error_msg() self.show_error_msg()
# Stop powerpoint from flashing in the taskbar # Stop powerpoint from flashing in the taskbar
@ -273,9 +267,8 @@ class PowerpointDocument(PresentationDocument):
self.blank_click = self.presentation.SlideShowWindow.View.GetClickIndex() self.blank_click = self.presentation.SlideShowWindow.View.GetClickIndex()
# ppSlideShowBlackScreen = 3 # ppSlideShowBlackScreen = 3
self.presentation.SlideShowWindow.View.State = 3 self.presentation.SlideShowWindow.View.State = 3
except (AttributeError, pywintypes.com_error) as e: except (AttributeError, pywintypes.com_error):
log.exception('Caught exception while in blank_screen') log.exception('Caught exception while in blank_screen')
log.exception(e)
trace_error_handler(log) trace_error_handler(log)
self.show_error_msg() self.show_error_msg()
@ -288,9 +281,8 @@ class PowerpointDocument(PresentationDocument):
try: try:
# ppSlideShowBlackScreen = 3 # ppSlideShowBlackScreen = 3
return self.presentation.SlideShowWindow.View.State == 3 return self.presentation.SlideShowWindow.View.State == 3
except (AttributeError, pywintypes.com_error) as e: except (AttributeError, pywintypes.com_error):
log.exception('Caught exception while in is_blank') log.exception('Caught exception while in is_blank')
log.exception(e)
trace_error_handler(log) trace_error_handler(log)
self.show_error_msg() self.show_error_msg()
else: else:
@ -303,9 +295,8 @@ class PowerpointDocument(PresentationDocument):
log.debug('stop_presentation') log.debug('stop_presentation')
try: try:
self.presentation.SlideShowWindow.View.Exit() self.presentation.SlideShowWindow.View.Exit()
except (AttributeError, pywintypes.com_error) as e: except (AttributeError, pywintypes.com_error):
log.exception('Caught exception while in stop_presentation') log.exception('Caught exception while in stop_presentation')
log.exception(e)
trace_error_handler(log) trace_error_handler(log)
self.show_error_msg() self.show_error_msg()
@ -328,9 +319,8 @@ class PowerpointDocument(PresentationDocument):
ppt_window = None ppt_window = None
try: try:
ppt_window = self.presentation.SlideShowSettings.Run() ppt_window = self.presentation.SlideShowSettings.Run()
except (AttributeError, pywintypes.com_error) as e: except (AttributeError, pywintypes.com_error):
log.exception('Caught exception while in start_presentation') log.exception('Caught exception while in start_presentation')
log.exception(e)
trace_error_handler(log) trace_error_handler(log)
self.show_error_msg() self.show_error_msg()
if ppt_window and not Settings().value('presentations/powerpoint control window'): if ppt_window and not Settings().value('presentations/powerpoint control window'):
@ -339,9 +329,8 @@ class PowerpointDocument(PresentationDocument):
ppt_window.Height = size.height() * 72 / dpi ppt_window.Height = size.height() * 72 / dpi
ppt_window.Left = size.x() * 72 / dpi ppt_window.Left = size.x() * 72 / dpi
ppt_window.Width = size.width() * 72 / dpi ppt_window.Width = size.width() * 72 / dpi
except AttributeError as e: except AttributeError:
log.exception('AttributeError while in start_presentation') log.exception('AttributeError while in start_presentation')
log.exception(e)
# Find the presentation window and save the handle for later # Find the presentation window and save the handle for later
self.presentation_hwnd = None self.presentation_hwnd = None
if ppt_window: if ppt_window:
@ -399,9 +388,8 @@ class PowerpointDocument(PresentationDocument):
ret = next((key for key, slidenum in self.index_map.items() if slidenum == ret), None) ret = next((key for key, slidenum in self.index_map.items() if slidenum == ret), None)
else: else:
ret = self.presentation.SlideShowWindow.View.CurrentShowPosition ret = self.presentation.SlideShowWindow.View.CurrentShowPosition
except (AttributeError, pywintypes.com_error) as e: except (AttributeError, pywintypes.com_error):
log.exception('Caught exception while in get_slide_number') log.exception('Caught exception while in get_slide_number')
log.exception(e)
trace_error_handler(log) trace_error_handler(log)
self.show_error_msg() self.show_error_msg()
return ret return ret
@ -431,9 +419,8 @@ class PowerpointDocument(PresentationDocument):
self.next_step() self.next_step()
else: else:
self.presentation.SlideShowWindow.View.GotoSlide(self.index_map[slide_no]) self.presentation.SlideShowWindow.View.GotoSlide(self.index_map[slide_no])
except (AttributeError, pywintypes.com_error) as e: except (AttributeError, pywintypes.com_error):
log.exception('Caught exception while in goto_slide') log.exception('Caught exception while in goto_slide')
log.exception(e)
trace_error_handler(log) trace_error_handler(log)
self.show_error_msg() self.show_error_msg()
@ -445,9 +432,8 @@ class PowerpointDocument(PresentationDocument):
try: try:
self.presentation.SlideShowWindow.Activate() self.presentation.SlideShowWindow.Activate()
self.presentation.SlideShowWindow.View.Next() self.presentation.SlideShowWindow.View.Next()
except (AttributeError, pywintypes.com_error) as e: except (AttributeError, pywintypes.com_error):
log.exception('Caught exception while in next_step') log.exception('Caught exception while in next_step')
log.exception(e)
trace_error_handler(log) trace_error_handler(log)
self.show_error_msg() self.show_error_msg()
return return
@ -468,9 +454,8 @@ class PowerpointDocument(PresentationDocument):
log.debug('previous_step') log.debug('previous_step')
try: try:
self.presentation.SlideShowWindow.View.Previous() self.presentation.SlideShowWindow.View.Previous()
except (AttributeError, pywintypes.com_error) as e: except (AttributeError, pywintypes.com_error):
log.exception('Caught exception while in previous_step') log.exception('Caught exception while in previous_step')
log.exception(e)
trace_error_handler(log) trace_error_handler(log)
self.show_error_msg() self.show_error_msg()
@ -503,8 +488,8 @@ class PowerpointDocument(PresentationDocument):
slide = self.presentation.Slides(self.index_map[num + 1]) slide = self.presentation.Slides(self.index_map[num + 1])
try: try:
text = slide.Shapes.Title.TextFrame.TextRange.Text text = slide.Shapes.Title.TextFrame.TextRange.Text
except Exception as e: except Exception:
log.exception(e) log.exception('Exception raised when getting title text')
text = '' text = ''
titles.append(text.replace('\n', ' ').replace('\x0b', ' ') + '\n') titles.append(text.replace('\n', ' ').replace('\x0b', ' ') + '\n')
note = _get_text_from_shapes(slide.NotesPage.Shapes) note = _get_text_from_shapes(slide.NotesPage.Shapes)
@ -519,9 +504,8 @@ class PowerpointDocument(PresentationDocument):
""" """
try: try:
self.presentation.SlideShowWindow.View.Exit() self.presentation.SlideShowWindow.View.Exit()
except (AttributeError, pywintypes.com_error) as e: except (AttributeError, pywintypes.com_error):
log.exception('Failed to exit Powerpoint presentation after error') log.exception('Failed to exit Powerpoint presentation after error')
log.exception(e)
critical_error_message_box(UiStrings().Error, translate('PresentationPlugin.PowerpointDocument', critical_error_message_box(UiStrings().Error, translate('PresentationPlugin.PowerpointDocument',
'An error occurred in the PowerPoint integration ' 'An error occurred in the PowerPoint integration '
'and the presentation will be stopped. ' 'and the presentation will be stopped. '
@ -540,7 +524,6 @@ def _get_text_from_shapes(shapes):
if shape.PlaceholderFormat.Type == 2: # 2 from is enum PpPlaceholderType.ppPlaceholderBody if shape.PlaceholderFormat.Type == 2: # 2 from is enum PpPlaceholderType.ppPlaceholderBody
if shape.HasTextFrame and shape.TextFrame.HasText: if shape.HasTextFrame and shape.TextFrame.HasText:
text += shape.TextFrame.TextRange.Text + '\n' text += shape.TextFrame.TextRange.Text + '\n'
except pywintypes.com_error as e: except pywintypes.com_error:
log.warning('Failed to extract text from powerpoint slide') log.exception('Failed to extract text from powerpoint slide')
log.warning(e)
return text return text

View File

@ -122,5 +122,4 @@ class RemotesPlugin(Plugin):
translate('RemotePlugin', 'Server Config Change'), translate('RemotePlugin', 'Server Config Change'),
translate('RemotePlugin', translate('RemotePlugin',
'Server configuration changes will require a restart ' 'Server configuration changes will require a restart '
'to take effect.'), 'to take effect.'))
QtWidgets.QMessageBox.StandardButtons(QtWidgets.QMessageBox.Ok))

View File

@ -35,7 +35,8 @@ class AuthorsForm(QtWidgets.QDialog, Ui_AuthorsDialog):
""" """
Set up the screen and common data Set up the screen and common data
""" """
super(AuthorsForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint) super(AuthorsForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint |
QtCore.Qt.WindowCloseButtonHint)
self.setupUi(self) self.setupUi(self)
self.auto_display_name = False self.auto_display_name = False
self.first_name_edit.textEdited.connect(self.on_first_name_edited) self.first_name_edit.textEdited.connect(self.on_first_name_edited)

View File

@ -82,6 +82,9 @@ class DuplicateSongRemovalForm(OpenLPWizard, RegistryProperties):
self.finish_button.clicked.connect(self.on_wizard_exit) self.finish_button.clicked.connect(self.on_wizard_exit)
self.cancel_button.clicked.connect(self.on_wizard_exit) self.cancel_button.clicked.connect(self.on_wizard_exit)
def closeEvent(self, event):
self.on_wizard_exit()
def add_custom_pages(self): def add_custom_pages(self):
""" """
Add song wizard specific pages. Add song wizard specific pages.
@ -130,9 +133,9 @@ class DuplicateSongRemovalForm(OpenLPWizard, RegistryProperties):
Song wizard localisation. Song wizard localisation.
""" """
self.setWindowTitle(translate('Wizard', 'Wizard')) self.setWindowTitle(translate('Wizard', 'Wizard'))
# TODO: Check format() using template strings self.title_label.setText(
self.title_label.setText(WizardStrings.HeaderStyle % translate('OpenLP.Ui', WizardStrings.HeaderStyle.format(text=translate('OpenLP.Ui',
'Welcome to the Duplicate Song Removal Wizard')) 'Welcome to the Duplicate Song Removal Wizard')))
self.information_label.setText( self.information_label.setText(
translate("Wizard", translate("Wizard",
'This wizard will help you to remove duplicate songs from the song database. You will have a ' 'This wizard will help you to remove duplicate songs from the song database. You will have a '
@ -216,8 +219,7 @@ class DuplicateSongRemovalForm(OpenLPWizard, RegistryProperties):
self.button(QtWidgets.QWizard.CancelButton).hide() self.button(QtWidgets.QWizard.CancelButton).hide()
QtWidgets.QMessageBox.information( QtWidgets.QMessageBox.information(
self, translate('Wizard', 'Information'), self, translate('Wizard', 'Information'),
translate('Wizard', 'No duplicate songs have been found in the database.'), translate('Wizard', 'No duplicate songs have been found in the database.'))
QtWidgets.QMessageBox.StandardButtons(QtWidgets.QMessageBox.Ok))
def add_duplicates_to_song_list(self, search_song, duplicate_song): def add_duplicates_to_song_list(self, search_song, duplicate_song):
""" """

View File

@ -56,7 +56,8 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
""" """
Constructor Constructor
""" """
super(EditSongForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint) super(EditSongForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint |
QtCore.Qt.WindowCloseButtonHint)
self.media_item = media_item self.media_item = media_item
self.song = None self.song = None
# can this be automated? # can this be automated?
@ -203,8 +204,7 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
'There is no verse corresponding to "{invalid}". Valid entries are {valid}.\n' 'There is no verse corresponding to "{invalid}". Valid entries are {valid}.\n'
'Please enter the verses separated by spaces.').format(invalid=invalid_verses[0], 'Please enter the verses separated by spaces.').format(invalid=invalid_verses[0],
valid=valid) valid=valid)
critical_error_message_box(title=translate('SongsPlugin.EditSongForm', 'Invalid Verse Order'), critical_error_message_box(title=translate('SongsPlugin.EditSongForm', 'Invalid Verse Order'), message=msg)
message=msg)
return len(invalid_verses) == 0 return len(invalid_verses) == 0
def _validate_song(self): def _validate_song(self):
@ -579,8 +579,7 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
self, self,
translate('SongsPlugin.EditSongForm', 'Add Author'), translate('SongsPlugin.EditSongForm', 'Add Author'),
translate('SongsPlugin.EditSongForm', 'This author does not exist, do you want to add them?'), translate('SongsPlugin.EditSongForm', 'This author does not exist, do you want to add them?'),
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No, defaultButton=QtWidgets.QMessageBox.Yes) == QtWidgets.QMessageBox.Yes:
QtWidgets.QMessageBox.Yes) == QtWidgets.QMessageBox.Yes:
if text.find(' ') == -1: if text.find(' ') == -1:
author = Author.populate(first_name='', last_name='', display_name=text) author = Author.populate(first_name='', last_name='', display_name=text)
else: else:
@ -658,8 +657,7 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
if QtWidgets.QMessageBox.question( if QtWidgets.QMessageBox.question(
self, translate('SongsPlugin.EditSongForm', 'Add Topic'), self, translate('SongsPlugin.EditSongForm', 'Add Topic'),
translate('SongsPlugin.EditSongForm', 'This topic does not exist, do you want to add it?'), translate('SongsPlugin.EditSongForm', 'This topic does not exist, do you want to add it?'),
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No, defaultButton=QtWidgets.QMessageBox.Yes) == QtWidgets.QMessageBox.Yes:
QtWidgets.QMessageBox.Yes) == QtWidgets.QMessageBox.Yes:
topic = Topic.populate(name=text) topic = Topic.populate(name=text)
self.manager.save_object(topic) self.manager.save_object(topic)
topic_item = QtWidgets.QListWidgetItem(str(topic.name)) topic_item = QtWidgets.QListWidgetItem(str(topic.name))
@ -705,8 +703,7 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
if QtWidgets.QMessageBox.question( if QtWidgets.QMessageBox.question(
self, translate('SongsPlugin.EditSongForm', 'Add Songbook'), self, translate('SongsPlugin.EditSongForm', 'Add Songbook'),
translate('SongsPlugin.EditSongForm', 'This Songbook does not exist, do you want to add it?'), translate('SongsPlugin.EditSongForm', 'This Songbook does not exist, do you want to add it?'),
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No, defaultButton=QtWidgets.QMessageBox.Yes) == QtWidgets.QMessageBox.Yes:
QtWidgets.QMessageBox.Yes) == QtWidgets.QMessageBox.Yes:
songbook = Book.populate(name=text) songbook = Book.populate(name=text)
self.manager.save_object(songbook) self.manager.save_object(songbook)
self.add_songbook_entry_to_list(songbook.id, songbook.name, self.songbook_entry_edit.text()) self.add_songbook_entry_to_list(songbook.id, songbook.name, self.songbook_entry_edit.text())

View File

@ -43,7 +43,8 @@ class EditVerseForm(QtWidgets.QDialog, Ui_EditVerseDialog):
""" """
Constructor Constructor
""" """
super(EditVerseForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint) super(EditVerseForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint |
QtCore.Qt.WindowCloseButtonHint)
self.setupUi(self) self.setupUi(self)
self.has_single_verse = False self.has_single_verse = False
self.insert_button.clicked.connect(self.on_insert_button_clicked) self.insert_button.clicked.connect(self.on_insert_button_clicked)

View File

@ -37,7 +37,8 @@ class MediaFilesForm(QtWidgets.QDialog, Ui_MediaFilesDialog):
log.info('{name} MediaFilesForm loaded'.format(name=__name__)) log.info('{name} MediaFilesForm loaded'.format(name=__name__))
def __init__(self, parent): def __init__(self, parent):
super(MediaFilesForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint) super(MediaFilesForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint |
QtCore.Qt.WindowCloseButtonHint)
self.setupUi(self) self.setupUi(self)
def populate_files(self, files): def populate_files(self, files):

View File

@ -38,7 +38,8 @@ class SongBookForm(QtWidgets.QDialog, Ui_SongBookDialog):
""" """
Constructor Constructor
""" """
super(SongBookForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint) super(SongBookForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint |
QtCore.Qt.WindowCloseButtonHint)
self.setupUi(self) self.setupUi(self)
def exec(self, clear=True): def exec(self, clear=True):

View File

@ -121,7 +121,7 @@ class SongExportForm(OpenLPWizard):
self.selected_list_widget = QtWidgets.QListWidget(self.export_song_page) self.selected_list_widget = QtWidgets.QListWidget(self.export_song_page)
self.selected_list_widget.setObjectName('selected_list_widget') self.selected_list_widget.setObjectName('selected_list_widget')
self.grid_layout.addWidget(self.selected_list_widget, 1, 0, 1, 1) self.grid_layout.addWidget(self.selected_list_widget, 1, 0, 1, 1)
# FIXME: self.horizontal_layout is already defined above?!?!? # FIXME: self.horizontal_layout is already defined above?!?!? Replace with Path Eidt!
self.horizontal_layout = QtWidgets.QHBoxLayout() self.horizontal_layout = QtWidgets.QHBoxLayout()
self.horizontal_layout.setObjectName('horizontal_layout') self.horizontal_layout.setObjectName('horizontal_layout')
self.directory_label = QtWidgets.QLabel(self.export_song_page) self.directory_label = QtWidgets.QLabel(self.export_song_page)
@ -143,9 +143,8 @@ class SongExportForm(OpenLPWizard):
Song wizard localisation. Song wizard localisation.
""" """
self.setWindowTitle(translate('SongsPlugin.ExportWizardForm', 'Song Export Wizard')) self.setWindowTitle(translate('SongsPlugin.ExportWizardForm', 'Song Export Wizard'))
# TODO: Verify format() with template variables self.title_label.setText(
self.title_label.setText(WizardStrings.HeaderStyle % WizardStrings.HeaderStyle.format(text=translate('OpenLP.Ui', 'Welcome to the Song Export Wizard')))
translate('OpenLP.Ui', 'Welcome to the Song Export Wizard'))
self.information_label.setText( self.information_label.setText(
translate('SongsPlugin.ExportWizardForm', 'This wizard will help to export your songs to the open and free ' translate('SongsPlugin.ExportWizardForm', 'This wizard will help to export your songs to the open and free '
'<strong>OpenLyrics </strong> worship song format.')) '<strong>OpenLyrics </strong> worship song format.'))

View File

@ -132,9 +132,8 @@ class SongImportForm(OpenLPWizard, RegistryProperties):
Song wizard localisation. Song wizard localisation.
""" """
self.setWindowTitle(translate('SongsPlugin.ImportWizardForm', 'Song Import Wizard')) self.setWindowTitle(translate('SongsPlugin.ImportWizardForm', 'Song Import Wizard'))
# TODO: Verify format() with template variables self.title_label.setText(
self.title_label.setText(WizardStrings.HeaderStyle % translate('OpenLP.Ui', WizardStrings.HeaderStyle.format(text=translate('OpenLP.Ui', 'Welcome to the Song Import Wizard')))
'Welcome to the Song Import Wizard'))
self.information_label.setText( self.information_label.setText(
translate('SongsPlugin.ImportWizardForm', translate('SongsPlugin.ImportWizardForm',
'This wizard will help you to import songs from a variety of formats. Click the next button ' 'This wizard will help you to import songs from a variety of formats. Click the next button '
@ -272,12 +271,11 @@ class SongImportForm(OpenLPWizard, RegistryProperties):
select_mode, format_name, ext_filter = SongFormat.get(this_format, 'selectMode', 'name', 'filter') select_mode, format_name, ext_filter = SongFormat.get(this_format, 'selectMode', 'name', 'filter')
file_path_edit = self.format_widgets[this_format]['file_path_edit'] file_path_edit = self.format_widgets[this_format]['file_path_edit']
if select_mode == SongFormatSelect.SingleFile: if select_mode == SongFormatSelect.SingleFile:
# TODO: Verify format() with template variables self.get_file_name(WizardStrings.OpenTypeFile.format(file_type=format_name),
self.get_file_name( file_path_edit, 'last directory import', ext_filter)
WizardStrings.OpenTypeFile % format_name, file_path_edit, 'last directory import', ext_filter)
elif select_mode == SongFormatSelect.SingleFolder: elif select_mode == SongFormatSelect.SingleFolder:
# TODO: Verify format() with template variables self.get_folder(
self.get_folder(WizardStrings.OpenTypeFolder % format_name, file_path_edit, 'last directory import') WizardStrings.OpenTypeFolder.format(folder_name=format_name), file_path_edit, 'last directory import')
def on_add_button_clicked(self): def on_add_button_clicked(self):
""" """
@ -286,8 +284,7 @@ class SongImportForm(OpenLPWizard, RegistryProperties):
this_format = self.current_format this_format = self.current_format
select_mode, format_name, ext_filter, custom_title = \ select_mode, format_name, ext_filter, custom_title = \
SongFormat.get(this_format, 'selectMode', 'name', 'filter', 'getFilesTitle') SongFormat.get(this_format, 'selectMode', 'name', 'filter', 'getFilesTitle')
# TODO: Verify format() with template variables title = custom_title if custom_title else WizardStrings.OpenTypeFile.format(file_type=format_name)
title = custom_title if custom_title else WizardStrings.OpenTypeFile % format_name
if select_mode == SongFormatSelect.MultipleFiles: if select_mode == SongFormatSelect.MultipleFiles:
self.get_files(title, self.format_widgets[this_format]['file_list_widget'], ext_filter) self.get_files(title, self.format_widgets[this_format]['file_list_widget'], ext_filter)
self.source_page.completeChanged.emit() self.source_page.completeChanged.emit()

View File

@ -39,7 +39,7 @@ class Ui_SongMaintenanceDialog(object):
song_maintenance_dialog.setObjectName('song_maintenance_dialog') song_maintenance_dialog.setObjectName('song_maintenance_dialog')
song_maintenance_dialog.setWindowIcon(build_icon(':/icon/openlp-logo.svg')) song_maintenance_dialog.setWindowIcon(build_icon(':/icon/openlp-logo.svg'))
song_maintenance_dialog.setWindowModality(QtCore.Qt.ApplicationModal) song_maintenance_dialog.setWindowModality(QtCore.Qt.ApplicationModal)
song_maintenance_dialog.resize(10, 350) song_maintenance_dialog.resize(600, 600)
self.dialog_layout = QtWidgets.QGridLayout(song_maintenance_dialog) self.dialog_layout = QtWidgets.QGridLayout(song_maintenance_dialog)
self.dialog_layout.setObjectName('dialog_layout') self.dialog_layout.setObjectName('dialog_layout')
self.type_list_widget = QtWidgets.QListWidget(song_maintenance_dialog) self.type_list_widget = QtWidgets.QListWidget(song_maintenance_dialog)

View File

@ -44,7 +44,8 @@ class SongMaintenanceForm(QtWidgets.QDialog, Ui_SongMaintenanceDialog, RegistryP
""" """
Constructor Constructor
""" """
super(SongMaintenanceForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint) super(SongMaintenanceForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint |
QtCore.Qt.WindowCloseButtonHint)
self.setupUi(self) self.setupUi(self)
self.manager = manager self.manager = manager
self.author_form = AuthorsForm(self) self.author_form = AuthorsForm(self)

View File

@ -81,7 +81,8 @@ class SongSelectForm(QtWidgets.QDialog, Ui_SongSelectDialog):
""" """
def __init__(self, parent=None, plugin=None, db_manager=None): def __init__(self, parent=None, plugin=None, db_manager=None):
QtWidgets.QDialog.__init__(self, parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint) QtWidgets.QDialog.__init__(self, parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint |
QtCore.Qt.WindowCloseButtonHint)
self.plugin = plugin self.plugin = plugin
self.db_manager = db_manager self.db_manager = db_manager
self.setup_ui(self) self.setup_ui(self)
@ -248,8 +249,7 @@ class SongSelectForm(QtWidgets.QDialog, Ui_SongSelectDialog):
translate('SongsPlugin.SongSelectForm', 'WARNING: Saving your username and password is INSECURE, your ' translate('SongsPlugin.SongSelectForm', 'WARNING: Saving your username and password is INSECURE, your '
'password is stored in PLAIN TEXT. Click Yes to save your ' 'password is stored in PLAIN TEXT. Click Yes to save your '
'password or No to cancel this.'), 'password or No to cancel this.'),
QtWidgets.QMessageBox.StandardButtons(QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No), defaultButton=QtWidgets.QMessageBox.No)
QtWidgets.QMessageBox.No)
if answer == QtWidgets.QMessageBox.No: if answer == QtWidgets.QMessageBox.No:
self.save_password_checkbox.setChecked(False) self.save_password_checkbox.setChecked(False)
@ -397,8 +397,7 @@ class SongSelectForm(QtWidgets.QDialog, Ui_SongSelectDialog):
translate('SongsPlugin.SongSelectForm', translate('SongsPlugin.SongSelectForm',
'Your song has been imported, would you ' 'Your song has been imported, would you '
'like to import more songs?'), 'like to import more songs?'),
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No, defaultButton=QtWidgets.QMessageBox.Yes) == QtWidgets.QMessageBox.Yes:
QtWidgets.QMessageBox.Yes) == QtWidgets.QMessageBox.Yes:
self.on_back_button_clicked() self.on_back_button_clicked()
else: else:
self.application.process_events() self.application.process_events()

View File

@ -38,7 +38,8 @@ class TopicsForm(QtWidgets.QDialog, Ui_TopicsDialog):
""" """
Constructor Constructor
""" """
super(TopicsForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint) super(TopicsForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint |
QtCore.Qt.WindowCloseButtonHint)
self.setupUi(self) self.setupUi(self)
def exec(self, clear=True): def exec(self, clear=True):

View File

@ -265,7 +265,7 @@ class SongFormat(object):
}, },
EasyWorshipService: { EasyWorshipService: {
'class': EasyWorshipSongImport, 'class': EasyWorshipSongImport,
'name': 'EasyWorship Service File', 'name': 'EasyWorship Service',
'prefix': 'ew', 'prefix': 'ew',
'selectMode': SongFormatSelect.SingleFile, 'selectMode': SongFormatSelect.SingleFile,
'filter': '{text} (*.ews)'.format(text=translate('SongsPlugin.ImportWizardForm', 'filter': '{text} (*.ews)'.format(text=translate('SongsPlugin.ImportWizardForm',

View File

@ -121,8 +121,8 @@ class FoilPresenterImport(SongImport):
for file_path in self.import_source: for file_path in self.import_source:
if self.stop_import_flag: if self.stop_import_flag:
return return
# TODO: Verify format() with template strings self.import_wizard.increment_progress_bar(
self.import_wizard.increment_progress_bar(WizardStrings.ImportingType % os.path.basename(file_path)) WizardStrings.ImportingType.format(source=os.path.basename(file_path)))
try: try:
parsed_file = etree.parse(file_path, parser) parsed_file = etree.parse(file_path, parser)
xml = etree.tostring(parsed_file).decode() xml = etree.tostring(parsed_file).decode()

View File

@ -275,11 +275,9 @@ class OpenLPSongImport(SongImport):
self.manager.save_object(new_song) self.manager.save_object(new_song)
if progress_dialog: if progress_dialog:
progress_dialog.setValue(progress_dialog.value() + 1) progress_dialog.setValue(progress_dialog.value() + 1)
# TODO: Verify format() with template strings progress_dialog.setLabelText(WizardStrings.ImportingType.format(source=new_song.title))
progress_dialog.setLabelText(WizardStrings.ImportingType % new_song.title)
else: else:
# TODO: Verify format() with template strings self.import_wizard.increment_progress_bar(WizardStrings.ImportingType.format(source=new_song.title))
self.import_wizard.increment_progress_bar(WizardStrings.ImportingType % new_song.title)
if self.stop_import_flag: if self.stop_import_flag:
break break
self.source_session.close() self.source_session.close()

View File

@ -58,8 +58,8 @@ class OpenLyricsImport(SongImport):
for file_path in self.import_source: for file_path in self.import_source:
if self.stop_import_flag: if self.stop_import_flag:
return return
# TODO: Verify format() with template strings self.import_wizard.increment_progress_bar(
self.import_wizard.increment_progress_bar(WizardStrings.ImportingType % os.path.basename(file_path)) WizardStrings.ImportingType.format(source=os.path.basename(file_path)))
try: try:
# Pass a file object, because lxml does not cope with some # Pass a file object, because lxml does not cope with some
# special characters in the path (see lp:757673 and lp:744337). # special characters in the path (see lp:757673 and lp:744337).

View File

@ -41,8 +41,8 @@ class PowerPraiseImport(SongImport):
for file_path in self.import_source: for file_path in self.import_source:
if self.stop_import_flag: if self.stop_import_flag:
return return
# TODO: Verify format() with template strings self.import_wizard.increment_progress_bar(
self.import_wizard.increment_progress_bar(WizardStrings.ImportingType % os.path.basename(file_path)) WizardStrings.ImportingType.format(source=os.path.basename(file_path)))
root = objectify.parse(open(file_path, 'rb')).getroot() root = objectify.parse(open(file_path, 'rb')).getroot()
self.process_song(root) self.process_song(root)

View File

@ -44,8 +44,8 @@ class PresentationManagerImport(SongImport):
for file_path in self.import_source: for file_path in self.import_source:
if self.stop_import_flag: if self.stop_import_flag:
return return
# TODO: Verify format() with template strings self.import_wizard.increment_progress_bar(
self.import_wizard.increment_progress_bar(WizardStrings.ImportingType % os.path.basename(file_path)) WizardStrings.ImportingType.format(source=os.path.basename(file_path)))
try: try:
tree = etree.parse(file_path, parser=etree.XMLParser(recover=True)) tree = etree.parse(file_path, parser=etree.XMLParser(recover=True))
except etree.XMLSyntaxError: except etree.XMLSyntaxError:

View File

@ -46,8 +46,8 @@ class ProPresenterImport(SongImport):
for file_path in self.import_source: for file_path in self.import_source:
if self.stop_import_flag: if self.stop_import_flag:
return return
# TODO: Verify format() with template strings self.import_wizard.increment_progress_bar(
self.import_wizard.increment_progress_bar(WizardStrings.ImportingType % os.path.basename(file_path)) WizardStrings.ImportingType.format(source=os.path.basename(file_path)))
root = objectify.parse(open(file_path, 'rb')).getroot() root = objectify.parse(open(file_path, 'rb')).getroot()
self.process_song(root, file_path) self.process_song(root, file_path)

View File

@ -347,8 +347,7 @@ class SongImport(QtCore.QObject):
song = Song() song = Song()
song.title = self.title song.title = self.title
if self.import_wizard is not None: if self.import_wizard is not None:
# TODO: Verify format() with template variables self.import_wizard.increment_progress_bar(WizardStrings.ImportingType.format(source=song.title))
self.import_wizard.increment_progress_bar(WizardStrings.ImportingType % song.title)
song.alternate_title = self.alternate_title song.alternate_title = self.alternate_title
# Values will be set when cleaning the song. # Values will be set when cleaning the song.
song.search_title = '' song.search_title = ''

View File

@ -100,8 +100,7 @@ class SongShowPlusImport(SongImport):
self.other_count = 0 self.other_count = 0
self.other_list = {} self.other_list = {}
file_name = os.path.split(file)[1] file_name = os.path.split(file)[1]
# TODO: Verify format() with template variables self.import_wizard.increment_progress_bar(WizardStrings.ImportingType.format(source=file_name), 0)
self.import_wizard.increment_progress_bar(WizardStrings.ImportingType % file_name, 0)
song_data = open(file, 'rb') song_data = open(file, 'rb')
while True: while True:
block_key, = struct.unpack("I", song_data.read(4)) block_key, = struct.unpack("I", song_data.read(4))

View File

@ -231,9 +231,14 @@ class SongMediaItem(MediaManagerItem):
def search_entire(self, search_keywords): def search_entire(self, search_keywords):
search_string = '%{text}%'.format(text=clean_string(search_keywords)) search_string = '%{text}%'.format(text=clean_string(search_keywords))
return self.plugin.manager.get_all_objects( return self.plugin.manager.session.query(Song) \
Song, or_(Song.search_title.like(search_string), Song.search_lyrics.like(search_string), .join(SongBookEntry, isouter=True) \
Song.comments.like(search_string))) .join(Book, isouter=True) \
.filter(or_(Book.name.like(search_string), SongBookEntry.entry.like(search_string),
# hint: search_title contains alternate title
Song.search_title.like(search_string), Song.search_lyrics.like(search_string),
Song.comments.like(search_string))) \
.all()
def on_song_list_load(self): def on_song_list_load(self):
""" """
@ -500,8 +505,7 @@ class SongMediaItem(MediaManagerItem):
translate('SongsPlugin.MediaItem', translate('SongsPlugin.MediaItem',
'Are you sure you want to delete the "{items:d}" ' 'Are you sure you want to delete the "{items:d}" '
'selected song(s)?').format(items=len(items)), 'selected song(s)?').format(items=len(items)),
QtWidgets.QMessageBox.StandardButtons(QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No), defaultButton=QtWidgets.QMessageBox.Yes) == QtWidgets.QMessageBox.No:
QtWidgets.QMessageBox.Yes) == QtWidgets.QMessageBox.No:
return return
self.application.set_busy_cursor() self.application.set_busy_cursor()
self.main_window.display_progress_bar(len(items)) self.main_window.display_progress_bar(len(items))

View File

@ -70,8 +70,7 @@ from openlp.plugins.songs.lib.db import Author, AuthorType, Book, Song, Topic
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
NAMESPACE = 'http://openlyrics.info/namespace/2009/song' NAMESPACE = 'http://openlyrics.info/namespace/2009/song'
# TODO: Verify format() with template variable NSMAP = '{{' + NAMESPACE + '}}{tag}'
NSMAP = '{' + NAMESPACE + '}' + '%s'
class SongXML(object): class SongXML(object):
@ -616,15 +615,13 @@ class OpenLyrics(object):
text = '' text = ''
use_endtag = True use_endtag = True
# Skip <comment> elements - not yet supported. # Skip <comment> elements - not yet supported.
# TODO: Verify format() with template variables if element.tag == NSMAP.format(tag='comment'):
if element.tag == NSMAP % 'comment':
if element.tail: if element.tail:
# Append tail text at comment element. # Append tail text at comment element.
text += element.tail text += element.tail
return text return text
# Convert chords to ChordPro format which OpenLP uses internally # Convert chords to ChordPro format which OpenLP uses internally
# TODO: Verify format() with template variables elif element.tag == NSMAP.format(tag='chord'):
elif element.tag == NSMAP % 'chord':
if Settings().value('songs/enable chords') and not Settings().value('songs/disable chords import'): if Settings().value('songs/enable chords') and not Settings().value('songs/disable chords import'):
text += '[{chord}]'.format(chord=element.get('name')) text += '[{chord}]'.format(chord=element.get('name'))
if element.tail: if element.tail:
@ -632,15 +629,13 @@ class OpenLyrics(object):
text += element.tail text += element.tail
return text return text
# Convert line breaks <br/> to \n. # Convert line breaks <br/> to \n.
# TODO: Verify format() with template variables elif newlines and element.tag == NSMAP.format(tag='br'):
elif newlines and element.tag == NSMAP % 'br':
text += '\n' text += '\n'
if element.tail: if element.tail:
text += element.tail text += element.tail
return text return text
# Start formatting tag. # Start formatting tag.
# TODO: Verify format() with template variables if element.tag == NSMAP.format(tag='tag'):
if element.tag == NSMAP % 'tag':
text += '{{{name}}}'.format(name=element.get('name')) text += '{{{name}}}'.format(name=element.get('name'))
# Some formattings may have only start tag. # Some formattings may have only start tag.
# Handle this case if element has no children and contains no text. # Handle this case if element has no children and contains no text.
@ -654,8 +649,7 @@ class OpenLyrics(object):
# Use recursion since nested formatting tags are allowed. # Use recursion since nested formatting tags are allowed.
text += self._process_lines_mixed_content(child, newlines) text += self._process_lines_mixed_content(child, newlines)
# Append text from tail and add formatting end tag. # Append text from tail and add formatting end tag.
# TODO: Verify format() with template variables if element.tag == NSMAP.format(tag='tag') and use_endtag:
if element.tag == NSMAP % 'tag' and use_endtag:
text += '{{/{name}}}'.format(name=element.get('name')) text += '{{/{name}}}'.format(name=element.get('name'))
# Append text from tail. # Append text from tail.
if element.tail: if element.tail:
@ -682,8 +676,7 @@ class OpenLyrics(object):
# Loop over the "line" elements removing comments # Loop over the "line" elements removing comments
for line in element: for line in element:
# Skip comment lines. # Skip comment lines.
# TODO: Verify format() with template variables if line.tag == NSMAP.format(tag='comment'):
if line.tag == NSMAP % 'comment':
continue continue
if text: if text:
text += '\n' text += '\n'

View File

@ -52,6 +52,7 @@ def upgrade_1(session, metadata):
:param metadata: :param metadata:
""" """
op = get_upgrade_op(session) op = get_upgrade_op(session)
metadata.reflect()
if 'media_files_songs' in [t.name for t in metadata.tables.values()]: if 'media_files_songs' in [t.name for t in metadata.tables.values()]:
op.drop_table('media_files_songs') op.drop_table('media_files_songs')
op.add_column('media_files', Column('song_id', types.Integer(), server_default=null())) op.add_column('media_files', Column('song_id', types.Integer(), server_default=null()))
@ -122,6 +123,7 @@ def upgrade_6(session, metadata):
This version corrects the errors in upgrades 4 and 5 This version corrects the errors in upgrades 4 and 5
""" """
op = get_upgrade_op(session) op = get_upgrade_op(session)
metadata.reflect()
# Move upgrade 4 to here and correct it (authors_songs table, not songs table) # Move upgrade 4 to here and correct it (authors_songs table, not songs table)
authors_songs = Table('authors_songs', metadata, autoload=True) authors_songs = Table('authors_songs', metadata, autoload=True)
if 'author_type' not in [col.name for col in authors_songs.c.values()]: if 'author_type' not in [col.name for col in authors_songs.c.values()]:

View File

@ -54,7 +54,7 @@ __default_settings__ = {
'songs/db password': '', 'songs/db password': '',
'songs/db hostname': '', 'songs/db hostname': '',
'songs/db database': '', 'songs/db database': '',
'songs/last search type': SongSearch.Entire, 'songs/last used search type': SongSearch.Entire,
'songs/last import type': SongFormat.OpenLyrics, 'songs/last import type': SongFormat.OpenLyrics,
'songs/update service on edit': False, 'songs/update service on edit': False,
'songs/add song from service': True, 'songs/add song from service': True,

View File

@ -37,7 +37,7 @@ class SongUsageDeleteForm(QtWidgets.QDialog, Ui_SongUsageDeleteDialog, RegistryP
""" """
self.manager = manager self.manager = manager
super(SongUsageDeleteForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | super(SongUsageDeleteForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint |
QtCore.Qt.WindowTitleHint) QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowCloseButtonHint)
self.setupUi(self) self.setupUi(self)
self.button_box.clicked.connect(self.on_button_box_clicked) self.button_box.clicked.connect(self.on_button_box_clicked)
@ -53,9 +53,7 @@ class SongUsageDeleteForm(QtWidgets.QDialog, Ui_SongUsageDeleteDialog, RegistryP
'Delete Selected Song Usage Events?'), 'Delete Selected Song Usage Events?'),
translate('SongUsagePlugin.SongUsageDeleteForm', translate('SongUsagePlugin.SongUsageDeleteForm',
'Are you sure you want to delete selected Song Usage data?'), 'Are you sure you want to delete selected Song Usage data?'),
QtWidgets.QMessageBox.StandardButtons(QtWidgets.QMessageBox.Yes | defaultButton=QtWidgets.QMessageBox.No)
QtWidgets.QMessageBox.No),
QtWidgets.QMessageBox.No)
if ret == QtWidgets.QMessageBox.Yes: if ret == QtWidgets.QMessageBox.Yes:
delete_date = self.delete_calendar.selectedDate().toPyDate() delete_date = self.delete_calendar.selectedDate().toPyDate()
self.manager.delete_all_objects(SongUsageItem, SongUsageItem.usagedate <= delete_date) self.manager.delete_all_objects(SongUsageItem, SongUsageItem.usagedate <= delete_date)

View File

@ -44,7 +44,8 @@ class SongUsageDetailForm(QtWidgets.QDialog, Ui_SongUsageDetailDialog, RegistryP
""" """
Initialise the form Initialise the form
""" """
super(SongUsageDetailForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint) super(SongUsageDetailForm, self).__init__(parent, QtCore.Qt.WindowSystemMenuHint | QtCore.Qt.WindowTitleHint |
QtCore.Qt.WindowCloseButtonHint)
self.plugin = plugin self.plugin = plugin
self.setupUi(self) self.setupUi(self)

View File

@ -25,17 +25,26 @@ backend for the SongsUsage plugin
""" """
import logging import logging
from sqlalchemy import Column, types from sqlalchemy import Table, Column, types
from openlp.core.lib.db import get_upgrade_op from openlp.core.lib.db import get_upgrade_op
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
__version__ = 1 __version__ = 2
def upgrade_1(session, metadata): def upgrade_1(session, metadata):
""" """
Version 1 upgrade. Version 1 upgrade
Skip due to possible missed update from a 2.4-2.6 upgrade
"""
pass
def upgrade_2(session, metadata):
"""
Version 2 upgrade.
This upgrade adds two new fields to the songusage database This upgrade adds two new fields to the songusage database
@ -43,5 +52,7 @@ def upgrade_1(session, metadata):
:param metadata: SQLAlchemy MetaData object :param metadata: SQLAlchemy MetaData object
""" """
op = get_upgrade_op(session) op = get_upgrade_op(session)
songusage_table = Table('songusage_data', metadata, autoload=True)
if 'plugin_name' not in [col.name for col in songusage_table.c.values()]:
op.add_column('songusage_data', Column('plugin_name', types.Unicode(20), server_default='')) op.add_column('songusage_data', Column('plugin_name', types.Unicode(20), server_default=''))
op.add_column('songusage_data', Column('source', types.Unicode(10), server_default='')) op.add_column('songusage_data', Column('source', types.Unicode(10), server_default=''))

View File

@ -23,6 +23,9 @@
Package to test the openlp.core.lib package. Package to test the openlp.core.lib package.
""" """
import os import os
import shutil
from tempfile import mkdtemp
from unittest import TestCase from unittest import TestCase
from unittest.mock import patch, MagicMock from unittest.mock import patch, MagicMock
@ -30,13 +33,27 @@ from sqlalchemy.pool import NullPool
from sqlalchemy.orm.scoping import ScopedSession from sqlalchemy.orm.scoping import ScopedSession
from sqlalchemy import MetaData from sqlalchemy import MetaData
from openlp.core.lib.db import init_db, get_upgrade_op, delete_database from openlp.core.lib.db import init_db, get_upgrade_op, delete_database, upgrade_db
from openlp.core.lib.projector import upgrade as pjlink_upgrade
class TestDB(TestCase): class TestDB(TestCase):
""" """
A test case for all the tests for the :mod:`~openlp.core.lib.db` module. A test case for all the tests for the :mod:`~openlp.core.lib.db` module.
""" """
def setUp(self):
"""
Set up anything necessary for all tests
"""
self.tmp_folder = mkdtemp(prefix='openlp_')
def tearDown(self):
"""
Clean up
"""
# Ignore errors since windows can have problems with locked files
shutil.rmtree(self.tmp_folder, ignore_errors=True)
def test_init_db_calls_correct_functions(self): def test_init_db_calls_correct_functions(self):
""" """
Test that the init_db function makes the correct function calls Test that the init_db function makes the correct function calls
@ -145,3 +162,17 @@ class TestDB(TestCase):
MockedAppLocation.get_section_data_path.assert_called_with(test_plugin) MockedAppLocation.get_section_data_path.assert_called_with(test_plugin)
mocked_delete_file.assert_called_with(test_location) mocked_delete_file.assert_called_with(test_location)
self.assertFalse(result, 'The result of delete_file should be False (was rigged that way)') self.assertFalse(result, 'The result of delete_file should be False (was rigged that way)')
@patch('tests.functional.openlp_core_lib.test_db.pjlink_upgrade')
def test_skip_db_upgrade_with_no_database(self, mocked_upgrade):
"""
Test the upgrade_db function does not try to update a missing database
"""
# GIVEN: Database URL that does not (yet) exist
url = 'sqlite:///{tmp}/test_db.sqlite'.format(tmp=self.tmp_folder)
# WHEN: We attempt to upgrade a non-existant database
upgrade_db(url, pjlink_upgrade)
# THEN: upgrade should NOT have been called
self.assertFalse(mocked_upgrade.called, 'Database upgrade function should NOT have been called')

View File

@ -29,7 +29,7 @@ class TestProjectorConstants(TestCase):
""" """
Test specific functions in the projector constants module. Test specific functions in the projector constants module.
""" """
def build_pjlink_video_label_test(self): def test_build_pjlink_video_label(self):
""" """
Test building PJLINK_DEFAULT_CODES dictionary Test building PJLINK_DEFAULT_CODES dictionary
""" """

View File

@ -384,21 +384,6 @@ class TestPJLink(TestCase):
self.assertEquals("{test}".format(test=mock_send_command.call_args), self.assertEquals("{test}".format(test=mock_send_command.call_args),
"call(data='{hash}%1CLSS ?\\r')".format(hash=TEST_HASH)) "call(data='{hash}%1CLSS ?\\r')".format(hash=TEST_HASH))
@patch.object(pjlink_test, '_not_implemented')
def not_implemented_test(self, mock_not_implemented):
"""
Test PJLink._not_implemented method being called
"""
# GIVEN: test object
pjlink = pjlink_test
test_cmd = 'TESTMEONLY'
# WHEN: A future command is called that is not implemented yet
pjlink.process_command(test_cmd, "Garbage data for test only")
# THEN: PJLink.__not_implemented should have been called with test_cmd
mock_not_implemented.assert_called_with(test_cmd)
@patch.object(pjlink_test, 'disconnect_from_host') @patch.object(pjlink_test, 'disconnect_from_host')
def socket_abort_test(self, mock_disconnect): def socket_abort_test(self, mock_disconnect):
""" """

View File

@ -27,11 +27,15 @@ PREREQUISITE: add_record() and get_all() functions validated.
""" """
import os import os
import shutil import shutil
from unittest import TestCase from tempfile import mkdtemp
from unittest import TestCase, skip
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
from openlp.core.lib.projector.db import Manufacturer, Model, Projector, ProjectorDB, ProjectorSource, Source from openlp.core.lib.projector import upgrade
from openlp.core.lib.db import upgrade_db
from openlp.core.lib.projector.constants import PJLINK_PORT from openlp.core.lib.projector.constants import PJLINK_PORT
from openlp.core.lib.projector.db import Manufacturer, Model, Projector, ProjectorDB, ProjectorSource, Source
from tests.resources.projector.data import TEST_DB_PJLINK1, TEST_DB, TEST1_DATA, TEST2_DATA, TEST3_DATA from tests.resources.projector.data import TEST_DB_PJLINK1, TEST_DB, TEST1_DATA, TEST2_DATA, TEST3_DATA
from tests.utils.constants import TEST_RESOURCES_PATH from tests.utils.constants import TEST_RESOURCES_PATH
@ -85,6 +89,42 @@ def add_records(projector_db, test):
return added return added
class TestProjectorDBUpdate(TestCase):
"""
Test case for upgrading Projector DB.
NOTE: Separate class so I don't have to look for upgrade tests.
"""
def setUp(self):
"""
Setup for tests
"""
self.tmp_folder = mkdtemp(prefix='openlp_')
def tearDown(self):
"""
Clean up after tests
"""
# Ignore errors since windows can have problems with locked files
shutil.rmtree(self.tmp_folder, ignore_errors=True)
def test_upgrade_old_projector_db(self):
"""
Test that we can upgrade an old song db to the current schema
"""
# GIVEN: An old song db
old_db = os.path.join(TEST_RESOURCES_PATH, "projector", TEST_DB_PJLINK1)
tmp_db = os.path.join(self.tmp_folder, TEST_DB)
shutil.copyfile(old_db, tmp_db)
db_url = 'sqlite:///{db}'.format(db=tmp_db)
# WHEN: upgrading the db
updated_to_version, latest_version = upgrade_db(db_url, upgrade)
# THEN: the song db should have been upgraded to the latest version
self.assertEqual(updated_to_version, latest_version,
'The projector DB should have been upgrade to the latest version')
class TestProjectorDB(TestCase): class TestProjectorDB(TestCase):
""" """
Test case for ProjectorDB Test case for ProjectorDB
@ -94,7 +134,9 @@ class TestProjectorDB(TestCase):
""" """
Set up anything necessary for all tests Set up anything necessary for all tests
""" """
mocked_init_url.return_value = 'sqlite:///{db}'.format(db=TEST_DB) self.tmp_folder = mkdtemp(prefix='openlp_')
tmpdb_url = 'sqlite:///{db}'.format(db=os.path.join(self.tmp_folder, TEST_DB))
mocked_init_url.return_value = tmpdb_url
self.projector = ProjectorDB() self.projector = ProjectorDB()
def tearDown(self): def tearDown(self):
@ -103,15 +145,8 @@ class TestProjectorDB(TestCase):
""" """
self.projector.session.close() self.projector.session.close()
self.projector = None self.projector = None
retries = 0 # Ignore errors since windows can have problems with locked files
while retries < 5: shutil.rmtree(self.tmp_folder, ignore_errors=True)
try:
if os.path.exists(TEST_DB):
os.unlink(TEST_DB)
break
except:
time.sleep(1)
retries += 1
def test_find_record_by_ip(self): def test_find_record_by_ip(self):
""" """
@ -271,10 +306,10 @@ class TestProjectorDB(TestCase):
# THEN: __repr__ should return a proper string # THEN: __repr__ should return a proper string
self.assertEqual(str(projector), self.assertEqual(str(projector),
'< Projector(id="0", ip="127.0.0.1", port="4352", pin="None", name="Test One", ' '< Projector(id="0", ip="127.0.0.1", port="4352", mac_adx="None", pin="None", '
'location="Somewhere over the rainbow", notes="Not again", pjlink_name="TEST", ' 'name="Test One", location="Somewhere over the rainbow", notes="Not again", '
'manufacturer="IN YOUR DREAMS", model="OpenLP", serial_no="None", other="None", ' 'pjlink_name="TEST", manufacturer="IN YOUR DREAMS", model="OpenLP", serial_no="None", '
'sources="None", source_list="[]", model_filter="None", model_lamp="None", ' 'other="None", sources="None", source_list="[]", model_filter="None", model_lamp="None", '
'sw_version="None") >', 'sw_version="None") >',
'Projector.__repr__() should have returned a proper representation string') 'Projector.__repr__() should have returned a proper representation string')

View File

@ -27,12 +27,6 @@ from unittest.mock import MagicMock, patch, call
from openlp.core.ui.formattingtagform import FormattingTagForm from openlp.core.ui.formattingtagform import FormattingTagForm
# TODO: Tests Still TODO
# __init__
# exec
# on_saved_clicked
# _reloadTable
class TestFormattingTagForm(TestCase): class TestFormattingTagForm(TestCase):

View File

@ -29,7 +29,7 @@ from PyQt5 import QtCore
from openlp.core.common import Registry, is_macosx, Settings from openlp.core.common import Registry, is_macosx, Settings
from openlp.core.lib import ScreenList, PluginManager from openlp.core.lib import ScreenList, PluginManager
from openlp.core.ui import MainDisplay from openlp.core.ui import MainDisplay, AudioPlayer
from openlp.core.ui.media import MediaController from openlp.core.ui.media import MediaController
from openlp.core.ui.maindisplay import TRANSPARENT_STYLESHEET, OPAQUE_STYLESHEET from openlp.core.ui.maindisplay import TRANSPARENT_STYLESHEET, OPAQUE_STYLESHEET
@ -283,3 +283,18 @@ class TestMainDisplay(TestCase, TestMixin):
self.assertEquals(main_display.web_view.setHtml.call_count, 1, 'setHTML should be called once') self.assertEquals(main_display.web_view.setHtml.call_count, 1, 'setHTML should be called once')
self.assertEquals(main_display.media_controller.video.call_count, 1, self.assertEquals(main_display.media_controller.video.call_count, 1,
'Media Controller video should have been called once') 'Media Controller video should have been called once')
def test_calling_next_item_in_playlist():
"""
Test the AudioPlayer.next() method
"""
# GIVEN: An instance of AudioPlayer with a mocked out playlist
audio_player = AudioPlayer(None)
# WHEN: next is called.
with patch.object(audio_player, 'playlist') as mocked_playlist:
audio_player.next()
# THEN: playlist.next should had been called once.
mocked_playlist.next.assert_called_once_with()

View File

@ -176,7 +176,7 @@ class TestThemeManager(TestCase):
self.assertTrue(result) self.assertTrue(result)
mocked_qmessagebox_question.assert_called_once_with( mocked_qmessagebox_question.assert_called_once_with(
theme_manager, 'Theme Already Exists', 'Theme Theme Name already exists. Do you want to replace it?', theme_manager, 'Theme Already Exists', 'Theme Theme Name already exists. Do you want to replace it?',
ANY, ANY) defaultButton=ANY)
def test_over_write_message_box_no(self): def test_over_write_message_box_no(self):
""" """
@ -196,7 +196,7 @@ class TestThemeManager(TestCase):
self.assertFalse(result) self.assertFalse(result)
mocked_qmessagebox_question.assert_called_once_with( mocked_qmessagebox_question.assert_called_once_with(
theme_manager, 'Theme Already Exists', 'Theme Theme Name already exists. Do you want to replace it?', theme_manager, 'Theme Already Exists', 'Theme Theme Name already exists. Do you want to replace it?',
ANY, ANY) defaultButton=ANY)
def test_unzip_theme(self): def test_unzip_theme(self):
""" """

View File

@ -29,45 +29,11 @@ from bs4 import BeautifulSoup
from openlp.plugins.bibles.lib.importers.http import BSExtract from openlp.plugins.bibles.lib.importers.http import BSExtract
# TODO: Items left to test
# BGExtract
# __init__
# _remove_elements
# _extract_verse
# _clean_soup
# _extract_verses
# _extract_verses_old
# get_bible_chapter
# get_books_from_http
# _get_application
# CWExtract
# __init__
# get_bible_chapter
# get_books_from_http
# _get_application
# HTTPBible
# __init__
# do_import
# get_verses
# get_chapter
# get_books
# get_chapter_count
# get_verse_count
# _get_application
# get_soup_for_bible_ref
# send_error_message
class TestBSExtract(TestCase): class TestBSExtract(TestCase):
""" """
Test the BSExtractClass Test the BSExtractClass
""" """
# TODO: Items left to test
# BSExtract
# __init__
# get_bible_chapter
# get_books_from_http
# _get_application
def setUp(self): def setUp(self):
self.get_soup_for_bible_ref_patcher = patch('openlp.plugins.bibles.lib.importers.http.get_soup_for_bible_ref') self.get_soup_for_bible_ref_patcher = patch('openlp.plugins.bibles.lib.importers.http.get_soup_for_bible_ref')
self.log_patcher = patch('openlp.plugins.bibles.lib.importers.http.log') self.log_patcher = patch('openlp.plugins.bibles.lib.importers.http.log')

View File

@ -68,7 +68,8 @@ class TestLib(TestCase, TestMixin):
""" """
# GIVEN: Some test data which contains different references to parse, with the expected results. # GIVEN: Some test data which contains different references to parse, with the expected results.
with patch('openlp.plugins.bibles.lib.Settings', return_value=MagicMock(**{'value.return_value': ''})): with patch('openlp.plugins.bibles.lib.Settings', return_value=MagicMock(**{'value.return_value': ''})):
# The following test data tests with 222 variants when using the default 'separators' # The following test data tests with about 240 variants when using the default 'separators'
# The amount is exactly 222 without '1. John 23' and'1. John. 23'
test_data = [ test_data = [
# Input reference, book name, chapter + verse reference # Input reference, book name, chapter + verse reference
('Psalm 23', 'Psalm', '23'), ('Psalm 23', 'Psalm', '23'),
@ -84,6 +85,8 @@ class TestLib(TestCase, TestMixin):
('Psalm 23{_and}24', 'Psalm', '23,24'), ('Psalm 23{_and}24', 'Psalm', '23,24'),
('1 John 23', '1 John', '23'), ('1 John 23', '1 John', '23'),
('1 John. 23', '1 John', '23'), ('1 John. 23', '1 John', '23'),
('1. John 23', '1. John', '23'),
('1. John. 23', '1. John', '23'),
('1 John 23{to}24', '1 John', '23-24'), ('1 John 23{to}24', '1 John', '23-24'),
('1 John 23{verse}1{to}2', '1 John', '23:1-2'), ('1 John 23{verse}1{to}2', '1 John', '23:1-2'),
('1 John 23{verse}1{to}{end}', '1 John', '23:1-end'), ('1 John 23{verse}1{to}{end}', '1 John', '23:1-end'),

View File

@ -199,9 +199,6 @@ class TestMediaItem(TestCase, TestMixin):
self.assertTrue(self.media_item.has_delete_icon, 'Check that the icon is called as True.') self.assertTrue(self.media_item.has_delete_icon, 'Check that the icon is called as True.')
self.assertFalse(self.media_item.add_to_service_item, 'Check that the icon is called as False') self.assertFalse(self.media_item.add_to_service_item, 'Check that the icon is called as False')
# TODO: Test add_end_header_bar
# TODO: Test setupUi
def test_on_focus_search_tab_visible(self): def test_on_focus_search_tab_visible(self):
""" """
Test the correct widget gets focus when the BibleMediaItem receives focus Test the correct widget gets focus when the BibleMediaItem receives focus
@ -480,7 +477,7 @@ class TestMediaItem(TestCase, TestMixin):
# WHEN: Calling update_auto_completer # WHEN: Calling update_auto_completer
self.media_item.update_auto_completer() self.media_item.update_auto_completer()
# THEN: set_case_insensitive_completer should have been called with the names of the books in order # THEN: set_case_insensitive_completer should have been called with the names of the books + space in order
mocked_set_case_insensitive_completer.assert_called_once_with( mocked_set_case_insensitive_completer.assert_called_once_with(
['Book 1 ', 'Book 2 ', 'Book 3 '], mocked_search_edit) ['Book 1 ', 'Book 2 ', 'Book 3 '], mocked_search_edit)
@ -500,11 +497,11 @@ class TestMediaItem(TestCase, TestMixin):
# WHEN: Calling update_auto_completer # WHEN: Calling update_auto_completer
self.media_item.update_auto_completer() self.media_item.update_auto_completer()
# THEN: set_case_insensitive_completer should have been called with the names of the books in order # THEN: set_case_insensitive_completer should have been called with the names of the books + space in order
mocked_set_case_insensitive_completer.assert_called_once_with( mocked_set_case_insensitive_completer.assert_called_once_with(
['Book 1 ', 'Book 2 ', 'Book 3 '], mocked_search_edit) ['Book 1 ', 'Book 2 ', 'Book 3 '], mocked_search_edit)
def test_on_import_click_no_import_wizzard_attr(self): def test_on_import_click_no_import_wizard_attr(self):
""" """
Test on_import_click when media_item does not have the `import_wizard` attribute. And the wizard was canceled. Test on_import_click when media_item does not have the `import_wizard` attribute. And the wizard was canceled.
""" """
@ -521,9 +518,9 @@ class TestMediaItem(TestCase, TestMixin):
self.assertTrue(mocked_bible_import_form.called) self.assertTrue(mocked_bible_import_form.called)
self.assertFalse(mocked_reload_bibles.called) self.assertFalse(mocked_reload_bibles.called)
def test_on_import_click_wizzard_not_canceled(self): def test_on_import_click_wizard_not_canceled(self):
""" """
Test on_import_click when the media item has the import_wizzard attr set and wizard completes sucessfully. Test on_import_click when the media item has the import_wizard attr set and wizard completes sucessfully.
""" """
# GIVEN: An instance of :class:`MediaManagerItem` and a mocked import_wizard # GIVEN: An instance of :class:`MediaManagerItem` and a mocked import_wizard
mocked_import_wizard = MagicMock(**{'exec.return_value': True}) mocked_import_wizard = MagicMock(**{'exec.return_value': True})
@ -1381,8 +1378,6 @@ class TestMediaItem(TestCase, TestMixin):
self.assertTrue(self.mocked_main_window.information_message.called) self.assertTrue(self.mocked_main_window.information_message.called)
mocked_display_results.assert_called_once_with() mocked_display_results.assert_called_once_with()
# TODO: Test text_search
def test_on_search_edit_text_changed_search_while_typing_disabled(self): def test_on_search_edit_text_changed_search_while_typing_disabled(self):
""" """
Test on_search_edit_text_changed when 'search while typing' is disabled Test on_search_edit_text_changed when 'search while typing' is disabled

View File

@ -42,11 +42,6 @@ class TestPptviewController(TestCase, TestMixin):
""" """
Test the PptviewController Class Test the PptviewController Class
""" """
# TODO: Items left to test
# PptviewController
# start_process(self)
# kill
def setUp(self): def setUp(self):
""" """
Set up the patches and mocks need for all tests. Set up the patches and mocks need for all tests.
@ -103,24 +98,6 @@ class TestPptviewDocument(TestCase):
""" """
Test the PptviewDocument Class Test the PptviewDocument Class
""" """
# TODO: Items left to test
# PptviewDocument
# __init__
# create_thumbnails
# close_presentation
# is_loaded
# is_active
# blank_screen
# unblank_screen
# is_blank
# stop_presentation
# start_presentation
# get_slide_number
# get_slide_count
# goto_slide
# next_step
# previous_step
def setUp(self): def setUp(self):
""" """
Set up the patches and mocks need for all tests. Set up the patches and mocks need for all tests.

Some files were not shown because too many files have changed in this diff Show More