forked from openlp/openlp
head
This commit is contained in:
commit
63a5c3bd91
@ -19,40 +19,43 @@
|
||||
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||
###############################################################################
|
||||
"""
|
||||
Provide a work around for a bug in QFileDialog <https://bugs.launchpad.net/openlp/+bug/1209515>
|
||||
"""
|
||||
import logging
|
||||
import os
|
||||
from urllib import parse
|
||||
|
||||
from PyQt5 import QtWidgets
|
||||
|
||||
from openlp.core.common import UiStrings
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
class FileDialog(QtWidgets.QFileDialog):
|
||||
def path_to_str(path):
|
||||
"""
|
||||
Subclass QFileDialog to work round a bug
|
||||
A utility function to convert a Path object or NoneType to a string equivalent.
|
||||
|
||||
:param path: The value to convert to a string
|
||||
:type: pathlib.Path or None
|
||||
|
||||
:return: An empty string if :param:`path` is None, else a string representation of the :param:`path`
|
||||
:rtype: str
|
||||
"""
|
||||
@staticmethod
|
||||
def getOpenFileNames(parent, *args, **kwargs):
|
||||
"""
|
||||
Reimplement getOpenFileNames to fix the way it returns some file names that url encoded when selecting multiple
|
||||
files
|
||||
"""
|
||||
files, filter_used = QtWidgets.QFileDialog.getOpenFileNames(parent, *args, **kwargs)
|
||||
file_list = []
|
||||
for file in files:
|
||||
if not os.path.exists(file):
|
||||
log.info('File not found. Attempting to unquote.')
|
||||
file = parse.unquote(file)
|
||||
if not os.path.exists(file):
|
||||
log.error('File {text} not found.'.format(text=file))
|
||||
QtWidgets.QMessageBox.information(parent, UiStrings().FileNotFound,
|
||||
UiStrings().FileNotFoundMessage.format(name=file))
|
||||
continue
|
||||
file_list.append(file)
|
||||
return file_list
|
||||
if not isinstance(path, Path) and path is not None:
|
||||
raise TypeError('parameter \'path\' must be of type Path or NoneType')
|
||||
if path is None:
|
||||
return ''
|
||||
else:
|
||||
return str(path)
|
||||
|
||||
|
||||
def str_to_path(string):
|
||||
"""
|
||||
A utility function to convert a str object to a Path or NoneType.
|
||||
|
||||
This function is of particular use because initating a Path object with an empty string causes the Path object to
|
||||
point to the current working directory.
|
||||
|
||||
:param string: The string to convert
|
||||
:type string: str
|
||||
|
||||
:return: None if :param:`string` is empty, or a Path object representation of :param:`string`
|
||||
:rtype: pathlib.Path or None
|
||||
"""
|
||||
if not isinstance(string, str):
|
||||
raise TypeError('parameter \'string\' must be of type str')
|
||||
if string == '':
|
||||
return None
|
||||
return Path(string)
|
@ -608,8 +608,42 @@ def create_separated_list(string_list):
|
||||
return list_to_string
|
||||
|
||||
|
||||
def replace_params(args, kwargs, params):
|
||||
"""
|
||||
Apply a transformation function to the specified args or kwargs
|
||||
|
||||
:param args: Positional arguments
|
||||
:type args: (,)
|
||||
|
||||
:param kwargs: Key Word arguments
|
||||
:type kwargs: dict
|
||||
|
||||
:param params: A tuple of tuples with the position and the key word to replace.
|
||||
:type params: ((int, str, path_to_str),)
|
||||
|
||||
:return: The modified positional and keyword arguments
|
||||
:rtype: (tuple, dict)
|
||||
|
||||
|
||||
Usage:
|
||||
Take a method with the following signature, and assume we which to apply the str function to arg2:
|
||||
def method(arg1=None, arg2=None, arg3=None)
|
||||
|
||||
As arg2 can be specified postitionally as the second argument (1 with a zero index) or as a keyword, the we
|
||||
would call this function as follows:
|
||||
|
||||
replace_params(args, kwargs, ((1, 'arg2', str),))
|
||||
"""
|
||||
args = list(args)
|
||||
for position, key_word, transform in params:
|
||||
if len(args) > position:
|
||||
args[position] = transform(args[position])
|
||||
elif key_word in kwargs:
|
||||
kwargs[key_word] = transform(kwargs[key_word])
|
||||
return tuple(args), kwargs
|
||||
|
||||
|
||||
from .exceptions import ValidationError
|
||||
from .filedialog import FileDialog
|
||||
from .screen import ScreenList
|
||||
from .formattingtags import FormattingTags
|
||||
from .plugin import PluginStatus, StringContent, Plugin
|
||||
@ -621,5 +655,5 @@ from .imagemanager import ImageManager
|
||||
from .renderer import Renderer
|
||||
from .mediamanageritem import MediaManagerItem
|
||||
from .projector.db import ProjectorDB, Projector
|
||||
from .projector.pjlink1 import PJLink
|
||||
from .projector.pjlink import PJLink
|
||||
from .projector.constants import PJLINK_PORT, ERROR_MSG, ERROR_STRING
|
||||
|
@ -26,12 +26,14 @@ import logging
|
||||
import os
|
||||
import re
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
from PyQt5 import QtCore, QtWidgets
|
||||
|
||||
from openlp.core.common import Registry, RegistryProperties, Settings, UiStrings, translate
|
||||
from openlp.core.lib import FileDialog, ServiceItem, StringContent, ServiceItemContext
|
||||
from openlp.core.common.path import path_to_str, str_to_path
|
||||
from openlp.core.lib import ServiceItem, StringContent, ServiceItemContext
|
||||
from openlp.core.lib.searchedit import SearchEdit
|
||||
from openlp.core.lib.ui import create_widget_action, critical_error_message_box
|
||||
from openlp.core.ui.lib.filedialog import FileDialog
|
||||
from openlp.core.ui.lib.listwidgetwithdnd import ListWidgetWithDnD
|
||||
from openlp.core.ui.lib.toolbar import OpenLPToolbar
|
||||
|
||||
@ -309,13 +311,14 @@ class MediaManagerItem(QtWidgets.QWidget, RegistryProperties):
|
||||
"""
|
||||
Add a file to the list widget to make it available for showing
|
||||
"""
|
||||
files = FileDialog.getOpenFileNames(self, self.on_new_prompt,
|
||||
Settings().value(self.settings_section + '/last directory'),
|
||||
self.on_new_file_masks)
|
||||
log.info('New files(s) {files}'.format(files=files))
|
||||
if files:
|
||||
file_paths, selected_filter = FileDialog.getOpenFileNames(
|
||||
self, self.on_new_prompt,
|
||||
str_to_path(Settings().value(self.settings_section + '/last directory')),
|
||||
self.on_new_file_masks)
|
||||
log.info('New files(s) {file_paths}'.format(file_paths=file_paths))
|
||||
if file_paths:
|
||||
self.application.set_busy_cursor()
|
||||
self.validate_and_load(files)
|
||||
self.validate_and_load([path_to_str(path) for path in file_paths])
|
||||
self.application.set_normal_cursor()
|
||||
|
||||
def load_file(self, data):
|
||||
|
@ -46,7 +46,7 @@ __all__ = ['S_OK', 'E_GENERAL', 'E_NOT_CONNECTED', 'E_FAN', 'E_LAMP', 'E_TEMP',
|
||||
'S_NOT_CONNECTED', 'S_CONNECTING', 'S_CONNECTED',
|
||||
'S_STATUS', 'S_OFF', 'S_INITIALIZE', 'S_STANDBY', 'S_WARMUP', 'S_ON', 'S_COOLDOWN',
|
||||
'S_INFO', 'S_NETWORK_SENDING', 'S_NETWORK_RECEIVED',
|
||||
'ERROR_STRING', 'CR', 'LF', 'PJLINK_ERST_STATUS', 'PJLINK_POWR_STATUS',
|
||||
'ERROR_STRING', 'CR', 'LF', 'PJLINK_ERST_DATA', 'PJLINK_ERST_STATUS', 'PJLINK_POWR_STATUS',
|
||||
'PJLINK_PORT', 'PJLINK_MAX_PACKET', 'TIMEOUT', 'ERROR_MSG', 'PJLINK_ERRORS',
|
||||
'STATUS_STRING', 'PJLINK_VALID_CMD', 'CONNECTION_ERRORS',
|
||||
'PJLINK_DEFAULT_SOURCES', 'PJLINK_DEFAULT_CODES', 'PJLINK_DEFAULT_ITEMS']
|
||||
@ -154,7 +154,7 @@ PJLINK_VALID_CMD = {
|
||||
},
|
||||
'SRCH': {'version': ['2', ],
|
||||
'description': translate('OpenLP.PJLinkConstants',
|
||||
'UDP broadcast search request for available projectors.')
|
||||
'UDP broadcast search request for available projectors. Reply is ACKN.')
|
||||
},
|
||||
'SVER': {'version': ['2', ],
|
||||
'description': translate('OpenLP.PJLinkConstants',
|
||||
@ -393,11 +393,32 @@ ERROR_MSG = {
|
||||
S_NETWORK_RECEIVED: translate('OpenLP.ProjectorConstants', 'Received data')
|
||||
}
|
||||
|
||||
# Map ERST return code positions to equipment
|
||||
PJLINK_ERST_DATA = {
|
||||
'DATA_LENGTH': 6,
|
||||
0: 'FAN',
|
||||
1: 'LAMP',
|
||||
2: 'TEMP',
|
||||
3: 'COVER',
|
||||
4: 'FILTER',
|
||||
5: 'OTHER',
|
||||
'FAN': 0,
|
||||
'LAMP': 1,
|
||||
'TEMP': 2,
|
||||
'COVER': 3,
|
||||
'FILTER': 4,
|
||||
'OTHER': 5
|
||||
}
|
||||
|
||||
# Map for ERST return codes to string
|
||||
PJLINK_ERST_STATUS = {
|
||||
'0': ERROR_STRING[E_OK],
|
||||
'0': 'OK',
|
||||
'1': ERROR_STRING[E_WARN],
|
||||
'2': ERROR_STRING[E_ERROR]
|
||||
'2': ERROR_STRING[E_ERROR],
|
||||
'OK': '0',
|
||||
E_OK: '0',
|
||||
E_WARN: '1',
|
||||
E_ERROR: '2'
|
||||
}
|
||||
|
||||
# Map for POWR return codes to status code
|
||||
|
@ -20,14 +20,17 @@
|
||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||
###############################################################################
|
||||
"""
|
||||
:mod:`openlp.core.lib.projector.pjlink1` module
|
||||
:mod:`openlp.core.lib.projector.pjlink` module
|
||||
Provides the necessary functions for connecting to a PJLink-capable projector.
|
||||
|
||||
See PJLink Class 1 Specifications for details.
|
||||
http://pjlink.jbmia.or.jp/english/dl.html
|
||||
|
||||
PJLink Class 1 Specifications.
|
||||
http://pjlink.jbmia.or.jp/english/dl_class1.html
|
||||
Section 5-1 PJLink Specifications
|
||||
Section 5-5 Guidelines for Input Terminals
|
||||
|
||||
PJLink Class 2 Specifications.
|
||||
http://pjlink.jbmia.or.jp/english/dl_class2.html
|
||||
Section 5-1 PJLink Specifications
|
||||
Section 5-5 Guidelines for Input Terminals
|
||||
|
||||
NOTE:
|
||||
@ -40,7 +43,7 @@
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
log.debug('pjlink1 loaded')
|
||||
log.debug('pjlink loaded')
|
||||
|
||||
__all__ = ['PJLink']
|
||||
|
||||
@ -51,8 +54,8 @@ from PyQt5 import QtCore, QtNetwork
|
||||
|
||||
from openlp.core.common import translate, qmd5_hash
|
||||
from openlp.core.lib.projector.constants import CONNECTION_ERRORS, CR, ERROR_MSG, ERROR_STRING, \
|
||||
E_AUTHENTICATION, E_CONNECTION_REFUSED, E_GENERAL, E_INVALID_DATA, E_NETWORK, E_NOT_CONNECTED, \
|
||||
E_PARAMETER, E_PROJECTOR, E_SOCKET_TIMEOUT, E_UNAVAILABLE, E_UNDEFINED, PJLINK_ERRORS, \
|
||||
E_AUTHENTICATION, E_CONNECTION_REFUSED, E_GENERAL, E_INVALID_DATA, E_NETWORK, E_NOT_CONNECTED, E_OK, \
|
||||
E_PARAMETER, E_PROJECTOR, E_SOCKET_TIMEOUT, E_UNAVAILABLE, E_UNDEFINED, PJLINK_ERRORS, PJLINK_ERST_DATA, \
|
||||
PJLINK_ERST_STATUS, PJLINK_MAX_PACKET, PJLINK_PORT, PJLINK_POWR_STATUS, PJLINK_VALID_CMD, \
|
||||
STATUS_STRING, S_CONNECTED, S_CONNECTING, S_NETWORK_RECEIVED, S_NETWORK_SENDING, \
|
||||
S_NOT_CONNECTED, S_OFF, S_OK, S_ON, S_STATUS
|
||||
@ -69,88 +72,17 @@ PJLINK_HEADER = '{prefix}{{linkclass}}'.format(prefix=PJLINK_PREFIX)
|
||||
PJLINK_SUFFIX = CR
|
||||
|
||||
|
||||
class PJLink(QtNetwork.QTcpSocket):
|
||||
class PJLinkCommands(object):
|
||||
"""
|
||||
Socket service for connecting to a PJLink-capable projector.
|
||||
Process replies from PJLink projector.
|
||||
"""
|
||||
# Signals sent by this module
|
||||
changeStatus = QtCore.pyqtSignal(str, int, str)
|
||||
projectorNetwork = QtCore.pyqtSignal(int) # Projector network activity
|
||||
projectorStatus = QtCore.pyqtSignal(int) # Status update
|
||||
projectorAuthentication = QtCore.pyqtSignal(str) # Authentication error
|
||||
projectorNoAuthentication = QtCore.pyqtSignal(str) # PIN set and no authentication needed
|
||||
projectorReceivedData = QtCore.pyqtSignal() # Notify when received data finished processing
|
||||
projectorUpdateIcons = QtCore.pyqtSignal() # Update the status icons on toolbar
|
||||
|
||||
# New commands available in PJLink Class 2
|
||||
pjlink_udp_commands = [
|
||||
'ACKN',
|
||||
'ERST', # Class 1 or 2
|
||||
'INPT', # Class 1 or 2
|
||||
'LKUP',
|
||||
'POWR', # Class 1 or 2
|
||||
'SRCH'
|
||||
]
|
||||
|
||||
def __init__(self, name=None, ip=None, port=PJLINK_PORT, pin=None, *args, **kwargs):
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""
|
||||
Setup for instance.
|
||||
|
||||
:param name: Display name
|
||||
:param ip: IP address to connect to
|
||||
:param port: Port to use. Default to PJLINK_PORT
|
||||
:param pin: Access pin (if needed)
|
||||
|
||||
Optional parameters
|
||||
:param dbid: Database ID number
|
||||
:param location: Location where projector is physically located
|
||||
:param notes: Extra notes about the projector
|
||||
:param poll_time: Time (in seconds) to poll connected projector
|
||||
:param socket_timeout: Time (in seconds) to abort the connection if no response
|
||||
Setup for the process commands
|
||||
"""
|
||||
log.debug('PJlink(args={args} kwargs={kwargs})'.format(args=args, kwargs=kwargs))
|
||||
self.name = name
|
||||
self.ip = ip
|
||||
self.port = port
|
||||
self.pin = pin
|
||||
log.debug('PJlinkCommands(args={args} kwargs={kwargs})'.format(args=args, kwargs=kwargs))
|
||||
super().__init__()
|
||||
self.model_lamp = None
|
||||
self.model_filter = None
|
||||
self.mac_adx = kwargs.get('mac_adx')
|
||||
self.serial_no = None
|
||||
self.serial_no_received = None # Used only if saved serial number is different than received serial number
|
||||
self.dbid = None
|
||||
self.db_update = False # Use to check if db needs to be updated prior to exiting
|
||||
self.location = None
|
||||
self.notes = None
|
||||
self.dbid = kwargs.get('dbid')
|
||||
self.location = kwargs.get('location')
|
||||
self.notes = kwargs.get('notes')
|
||||
# Poll time 20 seconds unless called with something else
|
||||
self.poll_time = 20000 if 'poll_time' not in kwargs else kwargs['poll_time'] * 1000
|
||||
# Timeout 5 seconds unless called with something else
|
||||
self.socket_timeout = 5000 if 'socket_timeout' not in kwargs else kwargs['socket_timeout'] * 1000
|
||||
# In case we're called from somewhere that only wants information
|
||||
self.no_poll = 'no_poll' in kwargs
|
||||
self.i_am_running = False
|
||||
self.status_connect = S_NOT_CONNECTED
|
||||
self.last_command = ''
|
||||
self.projector_status = S_NOT_CONNECTED
|
||||
self.error_status = S_OK
|
||||
# Socket information
|
||||
# Add enough space to input buffer for extraneous \n \r
|
||||
self.max_size = PJLINK_MAX_PACKET + 2
|
||||
self.setReadBufferSize(self.max_size)
|
||||
# PJLink information
|
||||
self.pjlink_class = '1' # Default class
|
||||
self.reset_information()
|
||||
# Set from ProjectorManager.add_projector()
|
||||
self.widget = None # QListBox entry
|
||||
self.timer = None # Timer that calls the poll_loop
|
||||
self.send_queue = []
|
||||
self.send_busy = False
|
||||
# Socket timer for some possible brain-dead projectors or network cable pulled
|
||||
self.socket_timer = None
|
||||
# Map command to function
|
||||
self.pjlink_functions = {
|
||||
'AVMT': self.process_avmt,
|
||||
@ -173,29 +105,30 @@ class PJLink(QtNetwork.QTcpSocket):
|
||||
|
||||
def reset_information(self):
|
||||
"""
|
||||
Reset projector-specific information to default
|
||||
Initialize instance variables. Also used to reset projector-specific information to default.
|
||||
"""
|
||||
log.debug('({ip}) reset_information() connect status is {state}'.format(ip=self.ip, state=self.state()))
|
||||
self.send_queue = []
|
||||
self.power = S_OFF
|
||||
self.pjlink_name = None
|
||||
self.manufacturer = None
|
||||
self.model = None
|
||||
self.serial_no = None
|
||||
self.fan = None # ERST
|
||||
self.filter_time = None # FILT
|
||||
self.lamp = None # LAMP
|
||||
self.mac_adx_received = None # ACKN
|
||||
self.manufacturer = None # INF1
|
||||
self.model = None # INF2
|
||||
self.model_filter = None # RFIL
|
||||
self.model_lamp = None # RLMP
|
||||
self.mute = None # AVMT
|
||||
self.other_info = None # INFO
|
||||
self.pjlink_class = PJLINK_CLASS # Default class
|
||||
self.pjlink_name = None # NAME
|
||||
self.power = S_OFF # POWR
|
||||
self.serial_no = None # SNUM
|
||||
self.serial_no_received = None
|
||||
self.sw_version = None
|
||||
self.sw_version = None # SVER
|
||||
self.sw_version_received = None
|
||||
self.mac_adx = None
|
||||
self.shutter = None
|
||||
self.mute = None
|
||||
self.lamp = None
|
||||
self.model_lamp = None
|
||||
self.fan = None
|
||||
self.filter_time = None
|
||||
self.model_filter = None
|
||||
self.source_available = None
|
||||
self.source = None
|
||||
self.other_info = None
|
||||
self.shutter = None # AVMT
|
||||
self.source_available = None # INST
|
||||
self.source = None # INPT
|
||||
# These should be part of PJLink() class, but set here for convenience
|
||||
if hasattr(self, 'timer'):
|
||||
log.debug('({ip}): Calling timer.stop()'.format(ip=self.ip))
|
||||
self.timer.stop()
|
||||
@ -203,6 +136,425 @@ class PJLink(QtNetwork.QTcpSocket):
|
||||
log.debug('({ip}): Calling socket_timer.stop()'.format(ip=self.ip))
|
||||
self.socket_timer.stop()
|
||||
self.send_busy = False
|
||||
self.send_queue = []
|
||||
|
||||
def process_command(self, cmd, data):
|
||||
"""
|
||||
Verifies any return error code. Calls the appropriate command handler.
|
||||
|
||||
:param cmd: Command to process
|
||||
:param data: Data being processed
|
||||
"""
|
||||
log.debug('({ip}) Processing command "{cmd}" with data "{data}"'.format(ip=self.ip,
|
||||
cmd=cmd,
|
||||
data=data))
|
||||
# Check if we have a future command not available yet
|
||||
_cmd = cmd.upper()
|
||||
_data = data.upper()
|
||||
if _cmd not in PJLINK_VALID_CMD:
|
||||
log.error("({ip}) Ignoring command='{cmd}' (Invalid/Unknown)".format(ip=self.ip, cmd=cmd))
|
||||
return
|
||||
elif _data == 'OK':
|
||||
log.debug('({ip}) Command "{cmd}" returned OK'.format(ip=self.ip, cmd=cmd))
|
||||
# A command returned successfully, no further processing needed
|
||||
return
|
||||
elif _cmd not in self.pjlink_functions:
|
||||
log.warn("({ip}) Unable to process command='{cmd}' (Future option)".format(ip=self.ip, cmd=cmd))
|
||||
return
|
||||
elif _data in PJLINK_ERRORS:
|
||||
# Oops - projector error
|
||||
log.error('({ip}) Projector returned error "{data}"'.format(ip=self.ip, data=data))
|
||||
if _data == PJLINK_ERRORS[E_AUTHENTICATION]:
|
||||
# Authentication error
|
||||
self.disconnect_from_host()
|
||||
self.change_status(E_AUTHENTICATION)
|
||||
log.debug('({ip}) emitting projectorAuthentication() signal'.format(ip=self.ip))
|
||||
self.projectorAuthentication.emit(self.name)
|
||||
elif _data == PJLINK_ERRORS[E_UNDEFINED]:
|
||||
# Projector does not recognize command
|
||||
self.change_status(E_UNDEFINED, '{error}: "{data}"'.format(error=ERROR_MSG[E_UNDEFINED],
|
||||
data=cmd))
|
||||
elif _data == PJLINK_ERRORS[E_PARAMETER]:
|
||||
# Invalid parameter
|
||||
self.change_status(E_PARAMETER)
|
||||
elif _data == PJLINK_ERRORS[E_UNAVAILABLE]:
|
||||
# Projector busy
|
||||
self.change_status(E_UNAVAILABLE)
|
||||
elif _data == PJLINK_ERRORS[E_PROJECTOR]:
|
||||
# Projector/display error
|
||||
self.change_status(E_PROJECTOR)
|
||||
self.receive_data_signal()
|
||||
return
|
||||
# Command checks already passed
|
||||
log.debug('({ip}) Calling function for {cmd}'.format(ip=self.ip, cmd=cmd))
|
||||
self.receive_data_signal()
|
||||
self.pjlink_functions[_cmd](data)
|
||||
|
||||
def process_avmt(self, data):
|
||||
"""
|
||||
Process shutter and speaker status. See PJLink specification for format.
|
||||
Update self.mute (audio) and self.shutter (video shutter).
|
||||
11 = Shutter closed, audio unchanged
|
||||
21 = Shutter unchanged, Audio muted
|
||||
30 = Shutter closed, audio muted
|
||||
31 = Shutter open, audio normal
|
||||
|
||||
:param data: Shutter and audio status
|
||||
"""
|
||||
settings = {'11': {'shutter': True, 'mute': self.mute},
|
||||
'21': {'shutter': self.shutter, 'mute': True},
|
||||
'30': {'shutter': False, 'mute': False},
|
||||
'31': {'shutter': True, 'mute': True}
|
||||
}
|
||||
if data not in settings:
|
||||
log.warning('({ip}) Invalid shutter response: {data}'.format(ip=self.ip, data=data))
|
||||
return
|
||||
shutter = settings[data]['shutter']
|
||||
mute = settings[data]['mute']
|
||||
# Check if we need to update the icons
|
||||
update_icons = (shutter != self.shutter) or (mute != self.mute)
|
||||
self.shutter = shutter
|
||||
self.mute = mute
|
||||
if update_icons:
|
||||
self.projectorUpdateIcons.emit()
|
||||
return
|
||||
|
||||
def process_clss(self, data):
|
||||
"""
|
||||
PJLink class that this projector supports. See PJLink specification for format.
|
||||
Updates self.class.
|
||||
|
||||
:param data: Class that projector supports.
|
||||
"""
|
||||
# bug 1550891: Projector returns non-standard class response:
|
||||
# : Expected: '%1CLSS=1'
|
||||
# : Received: '%1CLSS=Class 1' (Optoma)
|
||||
# : Received: '%1CLSS=Version1' (BenQ)
|
||||
if len(data) > 1:
|
||||
log.warn("({ip}) Non-standard CLSS reply: '{data}'".format(ip=self.ip, data=data))
|
||||
# Due to stupid projectors not following standards (Optoma, BenQ comes to mind),
|
||||
# AND the different responses that can be received, the semi-permanent way to
|
||||
# fix the class reply is to just remove all non-digit characters.
|
||||
try:
|
||||
clss = re.findall('\d', data)[0] # Should only be the first match
|
||||
except IndexError:
|
||||
log.error("({ip}) No numbers found in class version reply '{data}' - "
|
||||
"defaulting to class '1'".format(ip=self.ip, data=data))
|
||||
clss = '1'
|
||||
elif not data.isdigit():
|
||||
log.error("({ip}) NAN clss version reply '{data}' - "
|
||||
"defaulting to class '1'".format(ip=self.ip, data=data))
|
||||
clss = '1'
|
||||
else:
|
||||
clss = data
|
||||
self.pjlink_class = clss
|
||||
log.debug('({ip}) Setting pjlink_class for this projector to "{data}"'.format(ip=self.ip,
|
||||
data=self.pjlink_class))
|
||||
return
|
||||
|
||||
def process_erst(self, data):
|
||||
"""
|
||||
Error status. See PJLink Specifications for format.
|
||||
Updates self.projector_errors
|
||||
|
||||
:param data: Error status
|
||||
"""
|
||||
if len(data) != PJLINK_ERST_DATA['DATA_LENGTH']:
|
||||
count = PJLINK_ERST_DATA['DATA_LENGTH']
|
||||
log.warn("{ip}) Invalid error status response '{data}': length != {count}".format(ip=self.ip,
|
||||
data=data,
|
||||
count=count))
|
||||
return
|
||||
try:
|
||||
datacheck = int(data)
|
||||
except ValueError:
|
||||
# Bad data - ignore
|
||||
log.warn("({ip}) Invalid error status response '{data}'".format(ip=self.ip, data=data))
|
||||
return
|
||||
if datacheck == 0:
|
||||
self.projector_errors = None
|
||||
# No errors
|
||||
return
|
||||
# We have some sort of status error, so check out what it/they are
|
||||
self.projector_errors = {}
|
||||
fan, lamp, temp, cover, filt, other = (data[PJLINK_ERST_DATA['FAN']],
|
||||
data[PJLINK_ERST_DATA['LAMP']],
|
||||
data[PJLINK_ERST_DATA['TEMP']],
|
||||
data[PJLINK_ERST_DATA['COVER']],
|
||||
data[PJLINK_ERST_DATA['FILTER']],
|
||||
data[PJLINK_ERST_DATA['OTHER']])
|
||||
if fan != PJLINK_ERST_STATUS[E_OK]:
|
||||
self.projector_errors[translate('OpenLP.ProjectorPJLink', 'Fan')] = \
|
||||
PJLINK_ERST_STATUS[fan]
|
||||
if lamp != PJLINK_ERST_STATUS[E_OK]:
|
||||
self.projector_errors[translate('OpenLP.ProjectorPJLink', 'Lamp')] = \
|
||||
PJLINK_ERST_STATUS[lamp]
|
||||
if temp != PJLINK_ERST_STATUS[E_OK]:
|
||||
self.projector_errors[translate('OpenLP.ProjectorPJLink', 'Temperature')] = \
|
||||
PJLINK_ERST_STATUS[temp]
|
||||
if cover != PJLINK_ERST_STATUS[E_OK]:
|
||||
self.projector_errors[translate('OpenLP.ProjectorPJLink', 'Cover')] = \
|
||||
PJLINK_ERST_STATUS[cover]
|
||||
if filt != PJLINK_ERST_STATUS[E_OK]:
|
||||
self.projector_errors[translate('OpenLP.ProjectorPJLink', 'Filter')] = \
|
||||
PJLINK_ERST_STATUS[filt]
|
||||
if other != PJLINK_ERST_STATUS[E_OK]:
|
||||
self.projector_errors[translate('OpenLP.ProjectorPJLink', 'Other')] = \
|
||||
PJLINK_ERST_STATUS[other]
|
||||
return
|
||||
|
||||
def process_inf1(self, data):
|
||||
"""
|
||||
Manufacturer name set in projector.
|
||||
Updates self.manufacturer
|
||||
|
||||
:param data: Projector manufacturer
|
||||
"""
|
||||
self.manufacturer = data
|
||||
log.debug('({ip}) Setting projector manufacturer data to "{data}"'.format(ip=self.ip, data=self.manufacturer))
|
||||
return
|
||||
|
||||
def process_inf2(self, data):
|
||||
"""
|
||||
Projector Model set in projector.
|
||||
Updates self.model.
|
||||
|
||||
:param data: Model name
|
||||
"""
|
||||
self.model = data
|
||||
log.debug('({ip}) Setting projector model to "{data}"'.format(ip=self.ip, data=self.model))
|
||||
return
|
||||
|
||||
def process_info(self, data):
|
||||
"""
|
||||
Any extra info set in projector.
|
||||
Updates self.other_info.
|
||||
|
||||
:param data: Projector other info
|
||||
"""
|
||||
self.other_info = data
|
||||
log.debug('({ip}) Setting projector other_info to "{data}"'.format(ip=self.ip, data=self.other_info))
|
||||
return
|
||||
|
||||
def process_inpt(self, data):
|
||||
"""
|
||||
Current source input selected. See PJLink specification for format.
|
||||
Update self.source
|
||||
|
||||
:param data: Currently selected source
|
||||
"""
|
||||
self.source = data
|
||||
log.info('({ip}) Setting data source to "{data}"'.format(ip=self.ip, data=self.source))
|
||||
return
|
||||
|
||||
def process_inst(self, data):
|
||||
"""
|
||||
Available source inputs. See PJLink specification for format.
|
||||
Updates self.source_available
|
||||
|
||||
:param data: Sources list
|
||||
"""
|
||||
sources = []
|
||||
check = data.split()
|
||||
for source in check:
|
||||
sources.append(source)
|
||||
sources.sort()
|
||||
self.source_available = sources
|
||||
self.projectorUpdateIcons.emit()
|
||||
log.debug('({ip}) Setting projector sources_available to "{data}"'.format(ip=self.ip,
|
||||
data=self.source_available))
|
||||
return
|
||||
|
||||
def process_lamp(self, data):
|
||||
"""
|
||||
Lamp(s) status. See PJLink Specifications for format.
|
||||
Data may have more than 1 lamp to process.
|
||||
Update self.lamp dictionary with lamp status.
|
||||
|
||||
:param data: Lamp(s) status.
|
||||
"""
|
||||
lamps = []
|
||||
data_dict = data.split()
|
||||
while data_dict:
|
||||
try:
|
||||
fill = {'Hours': int(data_dict[0]), 'On': False if data_dict[1] == '0' else True}
|
||||
except ValueError:
|
||||
# In case of invalid entry
|
||||
log.warning('({ip}) process_lamp(): Invalid data "{data}"'.format(ip=self.ip, data=data))
|
||||
return
|
||||
lamps.append(fill)
|
||||
data_dict.pop(0) # Remove lamp hours
|
||||
data_dict.pop(0) # Remove lamp on/off
|
||||
self.lamp = lamps
|
||||
return
|
||||
|
||||
def process_name(self, data):
|
||||
"""
|
||||
Projector name set in projector.
|
||||
Updates self.pjlink_name
|
||||
|
||||
:param data: Projector name
|
||||
"""
|
||||
self.pjlink_name = data
|
||||
log.debug('({ip}) Setting projector PJLink name to "{data}"'.format(ip=self.ip, data=self.pjlink_name))
|
||||
return
|
||||
|
||||
def process_powr(self, data):
|
||||
"""
|
||||
Power status. See PJLink specification for format.
|
||||
Update self.power with status. Update icons if change from previous setting.
|
||||
|
||||
:param data: Power status
|
||||
"""
|
||||
log.debug('({ip}: Processing POWR command'.format(ip=self.ip))
|
||||
if data in PJLINK_POWR_STATUS:
|
||||
power = PJLINK_POWR_STATUS[data]
|
||||
update_icons = self.power != power
|
||||
self.power = power
|
||||
self.change_status(PJLINK_POWR_STATUS[data])
|
||||
if update_icons:
|
||||
self.projectorUpdateIcons.emit()
|
||||
# Update the input sources available
|
||||
if power == S_ON:
|
||||
self.send_command('INST')
|
||||
else:
|
||||
# Log unknown status response
|
||||
log.warning('({ip}) Unknown power response: {data}'.format(ip=self.ip, data=data))
|
||||
return
|
||||
|
||||
def process_rfil(self, data):
|
||||
"""
|
||||
Process replacement filter type
|
||||
"""
|
||||
if self.model_filter is None:
|
||||
self.model_filter = data
|
||||
else:
|
||||
log.warn("({ip}) Filter model already set".format(ip=self.ip))
|
||||
log.warn("({ip}) Saved model: '{old}'".format(ip=self.ip, old=self.model_filter))
|
||||
log.warn("({ip}) New model: '{new}'".format(ip=self.ip, new=data))
|
||||
|
||||
def process_rlmp(self, data):
|
||||
"""
|
||||
Process replacement lamp type
|
||||
"""
|
||||
if self.model_lamp is None:
|
||||
self.model_lamp = data
|
||||
else:
|
||||
log.warn("({ip}) Lamp model already set".format(ip=self.ip))
|
||||
log.warn("({ip}) Saved lamp: '{old}'".format(ip=self.ip, old=self.model_lamp))
|
||||
log.warn("({ip}) New lamp: '{new}'".format(ip=self.ip, new=data))
|
||||
|
||||
def process_snum(self, data):
|
||||
"""
|
||||
Serial number of projector.
|
||||
|
||||
:param data: Serial number from projector.
|
||||
"""
|
||||
if self.serial_no is None:
|
||||
log.debug("({ip}) Setting projector serial number to '{data}'".format(ip=self.ip, data=data))
|
||||
self.serial_no = data
|
||||
self.db_update = False
|
||||
else:
|
||||
# Compare serial numbers and see if we got the same projector
|
||||
if self.serial_no != data:
|
||||
log.warn("({ip}) Projector serial number does not match saved serial number".format(ip=self.ip))
|
||||
log.warn("({ip}) Saved: '{old}'".format(ip=self.ip, old=self.serial_no))
|
||||
log.warn("({ip}) Received: '{new}'".format(ip=self.ip, new=data))
|
||||
log.warn("({ip}) NOT saving serial number".format(ip=self.ip))
|
||||
self.serial_no_received = data
|
||||
|
||||
def process_sver(self, data):
|
||||
"""
|
||||
Software version of projector
|
||||
"""
|
||||
if self.sw_version is None:
|
||||
log.debug("({ip}) Setting projector software version to '{data}'".format(ip=self.ip, data=data))
|
||||
self.sw_version = data
|
||||
self.db_update = True
|
||||
else:
|
||||
# Compare software version and see if we got the same projector
|
||||
if self.serial_no != data:
|
||||
log.warn("({ip}) Projector software version does not match saved software version".format(ip=self.ip))
|
||||
log.warn("({ip}) Saved: '{old}'".format(ip=self.ip, old=self.sw_version))
|
||||
log.warn("({ip}) Received: '{new}'".format(ip=self.ip, new=data))
|
||||
log.warn("({ip}) NOT saving serial number".format(ip=self.ip))
|
||||
self.sw_version_received = data
|
||||
|
||||
|
||||
class PJLink(PJLinkCommands, QtNetwork.QTcpSocket):
|
||||
"""
|
||||
Socket service for connecting to a PJLink-capable projector.
|
||||
"""
|
||||
# Signals sent by this module
|
||||
changeStatus = QtCore.pyqtSignal(str, int, str)
|
||||
projectorNetwork = QtCore.pyqtSignal(int) # Projector network activity
|
||||
projectorStatus = QtCore.pyqtSignal(int) # Status update
|
||||
projectorAuthentication = QtCore.pyqtSignal(str) # Authentication error
|
||||
projectorNoAuthentication = QtCore.pyqtSignal(str) # PIN set and no authentication needed
|
||||
projectorReceivedData = QtCore.pyqtSignal() # Notify when received data finished processing
|
||||
projectorUpdateIcons = QtCore.pyqtSignal() # Update the status icons on toolbar
|
||||
|
||||
# New commands available in PJLink Class 2
|
||||
pjlink_udp_commands = [
|
||||
'ACKN', # Class 2
|
||||
'ERST', # Class 1 or 2
|
||||
'INPT', # Class 1 or 2
|
||||
'LKUP', # Class 2
|
||||
'POWR', # Class 1 or 2
|
||||
'SRCH' # Class 2
|
||||
]
|
||||
|
||||
def __init__(self, port=PJLINK_PORT, *args, **kwargs):
|
||||
"""
|
||||
Setup for instance.
|
||||
Options should be in kwargs except for port which does have a default.
|
||||
|
||||
:param name: Display name
|
||||
:param ip: IP address to connect to
|
||||
:param port: Port to use. Default to PJLINK_PORT
|
||||
:param pin: Access pin (if needed)
|
||||
|
||||
Optional parameters
|
||||
:param dbid: Database ID number
|
||||
:param location: Location where projector is physically located
|
||||
:param notes: Extra notes about the projector
|
||||
:param poll_time: Time (in seconds) to poll connected projector
|
||||
:param socket_timeout: Time (in seconds) to abort the connection if no response
|
||||
"""
|
||||
log.debug('PJlink(args={args} kwargs={kwargs})'.format(args=args, kwargs=kwargs))
|
||||
super().__init__()
|
||||
self.dbid = kwargs.get('dbid')
|
||||
self.ip = kwargs.get('ip')
|
||||
self.location = kwargs.get('location')
|
||||
self.mac_adx = kwargs.get('mac_adx')
|
||||
self.name = kwargs.get('name')
|
||||
self.notes = kwargs.get('notes')
|
||||
self.pin = kwargs.get('pin')
|
||||
self.port = port
|
||||
self.db_update = False # Use to check if db needs to be updated prior to exiting
|
||||
# Poll time 20 seconds unless called with something else
|
||||
self.poll_time = 20000 if 'poll_time' not in kwargs else kwargs['poll_time'] * 1000
|
||||
# Timeout 5 seconds unless called with something else
|
||||
self.socket_timeout = 5000 if 'socket_timeout' not in kwargs else kwargs['socket_timeout'] * 1000
|
||||
# In case we're called from somewhere that only wants information
|
||||
self.no_poll = 'no_poll' in kwargs
|
||||
self.i_am_running = False
|
||||
self.status_connect = S_NOT_CONNECTED
|
||||
self.last_command = ''
|
||||
self.projector_status = S_NOT_CONNECTED
|
||||
self.error_status = S_OK
|
||||
# Socket information
|
||||
# Add enough space to input buffer for extraneous \n \r
|
||||
self.max_size = PJLINK_MAX_PACKET + 2
|
||||
self.setReadBufferSize(self.max_size)
|
||||
self.reset_information()
|
||||
# Set from ProjectorManager.add_projector()
|
||||
self.widget = None # QListBox entry
|
||||
self.timer = None # Timer that calls the poll_loop
|
||||
self.send_queue = []
|
||||
self.send_busy = False
|
||||
# Socket timer for some possible brain-dead projectors or network cable pulled
|
||||
self.socket_timer = None
|
||||
|
||||
def thread_started(self):
|
||||
"""
|
||||
@ -290,28 +642,6 @@ class PJLink(QtNetwork.QTcpSocket):
|
||||
if self.model_lamp is None:
|
||||
self.send_command('RLMP', queue=True)
|
||||
|
||||
def process_rfil(self, data):
|
||||
"""
|
||||
Process replacement filter type
|
||||
"""
|
||||
if self.model_filter is None:
|
||||
self.model_filter = data
|
||||
else:
|
||||
log.warn("({ip}) Filter model already set".format(ip=self.ip))
|
||||
log.warn("({ip}) Saved model: '{old}'".format(ip=self.ip, old=self.model_filter))
|
||||
log.warn("({ip}) New model: '{new}'".format(ip=self.ip, new=data))
|
||||
|
||||
def process_rlmp(self, data):
|
||||
"""
|
||||
Process replacement lamp type
|
||||
"""
|
||||
if self.model_lamp is None:
|
||||
self.model_lamp = data
|
||||
else:
|
||||
log.warn("({ip}) Lamp model already set".format(ip=self.ip))
|
||||
log.warn("({ip}) Saved lamp: '{old}'".format(ip=self.ip, old=self.model_lamp))
|
||||
log.warn("({ip}) New lamp: '{new}'".format(ip=self.ip, new=data))
|
||||
|
||||
def _get_status(self, status):
|
||||
"""
|
||||
Helper to retrieve status/error codes and convert to strings.
|
||||
@ -474,6 +804,7 @@ class PJLink(QtNetwork.QTcpSocket):
|
||||
self.send_busy = False
|
||||
return
|
||||
read = self.readLine(self.max_size)
|
||||
log.debug("({ip}) get_data(): '{buff}'".format(ip=self.ip, buff=read))
|
||||
if read == -1:
|
||||
# No data available
|
||||
log.debug('({ip}) get_data(): No data available (-1)'.format(ip=self.ip))
|
||||
@ -626,317 +957,6 @@ class PJLink(QtNetwork.QTcpSocket):
|
||||
self.change_status(E_NETWORK,
|
||||
translate('OpenLP.PJLink', 'Error while sending data to projector'))
|
||||
|
||||
def process_command(self, cmd, data):
|
||||
"""
|
||||
Verifies any return error code. Calls the appropriate command handler.
|
||||
|
||||
:param cmd: Command to process
|
||||
:param data: Data being processed
|
||||
"""
|
||||
log.debug('({ip}) Processing command "{cmd}" with data "{data}"'.format(ip=self.ip,
|
||||
cmd=cmd,
|
||||
data=data))
|
||||
# Check if we have a future command not available yet
|
||||
if cmd not in PJLINK_VALID_CMD:
|
||||
log.error('({ip}) Unknown command received - ignoring'.format(ip=self.ip))
|
||||
return
|
||||
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
|
||||
log.error('({ip}) Projector returned error "{data}"'.format(ip=self.ip, data=data))
|
||||
if data.upper() == 'ERRA':
|
||||
# Authentication error
|
||||
self.disconnect_from_host()
|
||||
self.change_status(E_AUTHENTICATION)
|
||||
log.debug('({ip}) emitting projectorAuthentication() signal'.format(ip=self.ip))
|
||||
self.projectorAuthentication.emit(self.name)
|
||||
elif data.upper() == 'ERR1':
|
||||
# Undefined command
|
||||
self.change_status(E_UNDEFINED, '{error}: "{data}"'.format(error=ERROR_MSG[E_UNDEFINED],
|
||||
data=cmd))
|
||||
elif data.upper() == 'ERR2':
|
||||
# Invalid parameter
|
||||
self.change_status(E_PARAMETER)
|
||||
elif data.upper() == 'ERR3':
|
||||
# Projector busy
|
||||
self.change_status(E_UNAVAILABLE)
|
||||
elif data.upper() == 'ERR4':
|
||||
# Projector/display error
|
||||
self.change_status(E_PROJECTOR)
|
||||
self.receive_data_signal()
|
||||
return
|
||||
# Command succeeded - no extra information
|
||||
elif data.upper() == 'OK':
|
||||
log.debug('({ip}) Command returned OK'.format(ip=self.ip))
|
||||
# A command returned successfully
|
||||
self.receive_data_signal()
|
||||
return
|
||||
# Command checks already passed
|
||||
log.debug('({ip}) Calling function for {cmd}'.format(ip=self.ip, cmd=cmd))
|
||||
self.receive_data_signal()
|
||||
self.pjlink_functions[cmd](data)
|
||||
|
||||
def process_lamp(self, data):
|
||||
"""
|
||||
Lamp(s) status. See PJLink Specifications for format.
|
||||
Data may have more than 1 lamp to process.
|
||||
Update self.lamp dictionary with lamp status.
|
||||
|
||||
:param data: Lamp(s) status.
|
||||
"""
|
||||
lamps = []
|
||||
data_dict = data.split()
|
||||
while data_dict:
|
||||
try:
|
||||
fill = {'Hours': int(data_dict[0]), 'On': False if data_dict[1] == '0' else True}
|
||||
except ValueError:
|
||||
# In case of invalid entry
|
||||
log.warning('({ip}) process_lamp(): Invalid data "{data}"'.format(ip=self.ip, data=data))
|
||||
return
|
||||
lamps.append(fill)
|
||||
data_dict.pop(0) # Remove lamp hours
|
||||
data_dict.pop(0) # Remove lamp on/off
|
||||
self.lamp = lamps
|
||||
return
|
||||
|
||||
def process_powr(self, data):
|
||||
"""
|
||||
Power status. See PJLink specification for format.
|
||||
Update self.power with status. Update icons if change from previous setting.
|
||||
|
||||
:param data: Power status
|
||||
"""
|
||||
log.debug('({ip}: Processing POWR command'.format(ip=self.ip))
|
||||
if data in PJLINK_POWR_STATUS:
|
||||
power = PJLINK_POWR_STATUS[data]
|
||||
update_icons = self.power != power
|
||||
self.power = power
|
||||
self.change_status(PJLINK_POWR_STATUS[data])
|
||||
if update_icons:
|
||||
self.projectorUpdateIcons.emit()
|
||||
# Update the input sources available
|
||||
if power == S_ON:
|
||||
self.send_command('INST')
|
||||
else:
|
||||
# Log unknown status response
|
||||
log.warning('({ip}) Unknown power response: {data}'.format(ip=self.ip, data=data))
|
||||
return
|
||||
|
||||
def process_avmt(self, data):
|
||||
"""
|
||||
Process shutter and speaker status. See PJLink specification for format.
|
||||
Update self.mute (audio) and self.shutter (video shutter).
|
||||
|
||||
:param data: Shutter and audio status
|
||||
"""
|
||||
shutter = self.shutter
|
||||
mute = self.mute
|
||||
if data == '11':
|
||||
shutter = True
|
||||
mute = False
|
||||
elif data == '21':
|
||||
shutter = False
|
||||
mute = True
|
||||
elif data == '30':
|
||||
shutter = False
|
||||
mute = False
|
||||
elif data == '31':
|
||||
shutter = True
|
||||
mute = True
|
||||
else:
|
||||
log.warning('({ip}) Unknown shutter response: {data}'.format(ip=self.ip, data=data))
|
||||
update_icons = shutter != self.shutter
|
||||
update_icons = update_icons or mute != self.mute
|
||||
self.shutter = shutter
|
||||
self.mute = mute
|
||||
if update_icons:
|
||||
self.projectorUpdateIcons.emit()
|
||||
return
|
||||
|
||||
def process_inpt(self, data):
|
||||
"""
|
||||
Current source input selected. See PJLink specification for format.
|
||||
Update self.source
|
||||
|
||||
:param data: Currently selected source
|
||||
"""
|
||||
self.source = data
|
||||
log.info('({ip}) Setting data source to "{data}"'.format(ip=self.ip, data=self.source))
|
||||
return
|
||||
|
||||
def process_clss(self, data):
|
||||
"""
|
||||
PJLink class that this projector supports. See PJLink specification for format.
|
||||
Updates self.class.
|
||||
|
||||
:param data: Class that projector supports.
|
||||
"""
|
||||
# bug 1550891: Projector returns non-standard class response:
|
||||
# : Expected: '%1CLSS=1'
|
||||
# : Received: '%1CLSS=Class 1' (Optoma)
|
||||
# : Received: '%1CLSS=Version1' (BenQ)
|
||||
if len(data) > 1:
|
||||
log.warn("({ip}) Non-standard CLSS reply: '{data}'".format(ip=self.ip, data=data))
|
||||
# Due to stupid projectors not following standards (Optoma, BenQ comes to mind),
|
||||
# AND the different responses that can be received, the semi-permanent way to
|
||||
# fix the class reply is to just remove all non-digit characters.
|
||||
try:
|
||||
clss = re.findall('\d', data)[0] # Should only be the first match
|
||||
except IndexError:
|
||||
log.error("({ip}) No numbers found in class version reply - defaulting to class '1'".format(ip=self.ip))
|
||||
clss = '1'
|
||||
elif not data.isdigit():
|
||||
log.error("({ip}) NAN class version reply - defaulting to class '1'".format(ip=self.ip))
|
||||
clss = '1'
|
||||
else:
|
||||
clss = data
|
||||
self.pjlink_class = clss
|
||||
log.debug('({ip}) Setting pjlink_class for this projector to "{data}"'.format(ip=self.ip,
|
||||
data=self.pjlink_class))
|
||||
return
|
||||
|
||||
def process_name(self, data):
|
||||
"""
|
||||
Projector name set in projector.
|
||||
Updates self.pjlink_name
|
||||
|
||||
:param data: Projector name
|
||||
"""
|
||||
self.pjlink_name = data
|
||||
log.debug('({ip}) Setting projector PJLink name to "{data}"'.format(ip=self.ip, data=self.pjlink_name))
|
||||
return
|
||||
|
||||
def process_inf1(self, data):
|
||||
"""
|
||||
Manufacturer name set in projector.
|
||||
Updates self.manufacturer
|
||||
|
||||
:param data: Projector manufacturer
|
||||
"""
|
||||
self.manufacturer = data
|
||||
log.debug('({ip}) Setting projector manufacturer data to "{data}"'.format(ip=self.ip, data=self.manufacturer))
|
||||
return
|
||||
|
||||
def process_inf2(self, data):
|
||||
"""
|
||||
Projector Model set in projector.
|
||||
Updates self.model.
|
||||
|
||||
:param data: Model name
|
||||
"""
|
||||
self.model = data
|
||||
log.debug('({ip}) Setting projector model to "{data}"'.format(ip=self.ip, data=self.model))
|
||||
return
|
||||
|
||||
def process_info(self, data):
|
||||
"""
|
||||
Any extra info set in projector.
|
||||
Updates self.other_info.
|
||||
|
||||
:param data: Projector other info
|
||||
"""
|
||||
self.other_info = data
|
||||
log.debug('({ip}) Setting projector other_info to "{data}"'.format(ip=self.ip, data=self.other_info))
|
||||
return
|
||||
|
||||
def process_inst(self, data):
|
||||
"""
|
||||
Available source inputs. See PJLink specification for format.
|
||||
Updates self.source_available
|
||||
|
||||
:param data: Sources list
|
||||
"""
|
||||
sources = []
|
||||
check = data.split()
|
||||
for source in check:
|
||||
sources.append(source)
|
||||
sources.sort()
|
||||
self.source_available = sources
|
||||
self.projectorUpdateIcons.emit()
|
||||
log.debug('({ip}) Setting projector sources_available to "{data}"'.format(ip=self.ip,
|
||||
data=self.source_available))
|
||||
return
|
||||
|
||||
def process_erst(self, data):
|
||||
"""
|
||||
Error status. See PJLink Specifications for format.
|
||||
Updates self.projector_errors
|
||||
|
||||
:param data: Error status
|
||||
"""
|
||||
try:
|
||||
datacheck = int(data)
|
||||
except ValueError:
|
||||
# Bad data - ignore
|
||||
return
|
||||
if datacheck == 0:
|
||||
self.projector_errors = None
|
||||
else:
|
||||
self.projector_errors = {}
|
||||
# Fan
|
||||
if data[0] != '0':
|
||||
self.projector_errors[translate('OpenLP.ProjectorPJLink', 'Fan')] = \
|
||||
PJLINK_ERST_STATUS[data[0]]
|
||||
# Lamp
|
||||
if data[1] != '0':
|
||||
self.projector_errors[translate('OpenLP.ProjectorPJLink', 'Lamp')] = \
|
||||
PJLINK_ERST_STATUS[data[1]]
|
||||
# Temp
|
||||
if data[2] != '0':
|
||||
self.projector_errors[translate('OpenLP.ProjectorPJLink', 'Temperature')] = \
|
||||
PJLINK_ERST_STATUS[data[2]]
|
||||
# Cover
|
||||
if data[3] != '0':
|
||||
self.projector_errors[translate('OpenLP.ProjectorPJLink', 'Cover')] = \
|
||||
PJLINK_ERST_STATUS[data[3]]
|
||||
# Filter
|
||||
if data[4] != '0':
|
||||
self.projector_errors[translate('OpenLP.ProjectorPJLink', 'Filter')] = \
|
||||
PJLINK_ERST_STATUS[data[4]]
|
||||
# Other
|
||||
if data[5] != '0':
|
||||
self.projector_errors[translate('OpenLP.ProjectorPJLink', 'Other')] = \
|
||||
PJLINK_ERST_STATUS[data[5]]
|
||||
return
|
||||
|
||||
def process_snum(self, data):
|
||||
"""
|
||||
Serial number of projector.
|
||||
|
||||
:param data: Serial number from projector.
|
||||
"""
|
||||
if self.serial_no is None:
|
||||
log.debug("({ip}) Setting projector serial number to '{data}'".format(ip=self.ip, data=data))
|
||||
self.serial_no = data
|
||||
self.db_update = False
|
||||
else:
|
||||
# Compare serial numbers and see if we got the same projector
|
||||
if self.serial_no != data:
|
||||
log.warn("({ip}) Projector serial number does not match saved serial number".format(ip=self.ip))
|
||||
log.warn("({ip}) Saved: '{old}'".format(ip=self.ip, old=self.serial_no))
|
||||
log.warn("({ip}) Received: '{new}'".format(ip=self.ip, new=data))
|
||||
log.warn("({ip}) NOT saving serial number".format(ip=self.ip))
|
||||
self.serial_no_received = data
|
||||
|
||||
def process_sver(self, data):
|
||||
"""
|
||||
Software version of projector
|
||||
"""
|
||||
if self.sw_version is None:
|
||||
log.debug("({ip}) Setting projector software version to '{data}'".format(ip=self.ip, data=data))
|
||||
self.sw_version = data
|
||||
self.db_update = True
|
||||
else:
|
||||
# Compare software version and see if we got the same projector
|
||||
if self.serial_no != data:
|
||||
log.warn("({ip}) Projector software version does not match saved software version".format(ip=self.ip))
|
||||
log.warn("({ip}) Saved: '{old}'".format(ip=self.ip, old=self.sw_version))
|
||||
log.warn("({ip}) Received: '{new}'".format(ip=self.ip, new=data))
|
||||
log.warn("({ip}) NOT saving serial number".format(ip=self.ip))
|
||||
self.sw_version_received = data
|
||||
|
||||
def connect_to_host(self):
|
||||
"""
|
||||
Initiate connection to projector.
|
||||
@ -1098,11 +1118,3 @@ class PJLink(QtNetwork.QTcpSocket):
|
||||
self.send_busy = False
|
||||
self.projectorReceivedData.emit()
|
||||
return
|
||||
|
||||
def _not_implemented(self, cmd):
|
||||
"""
|
||||
Log when a future PJLink command has not been implemented yet.
|
||||
"""
|
||||
log.warn("({ip}) Future command '{cmd}' has not been implemented yet".format(ip=self.ip,
|
||||
cmd=cmd))
|
||||
return
|
@ -42,7 +42,7 @@ 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')
|
||||
log.debug('Skipping projector DB upgrade to version 1 - not used')
|
||||
|
||||
|
||||
def upgrade_2(session, metadata):
|
||||
@ -60,14 +60,14 @@ def upgrade_2(session, metadata):
|
||||
:param session: DB session instance
|
||||
:param metadata: Metadata of current DB
|
||||
"""
|
||||
log.debug('Checking projector DB upgrade to version 2')
|
||||
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'")
|
||||
upgrade_db = 'mac_adx' not in [col.name for col in projector_table.c.values()]
|
||||
if upgrade_db:
|
||||
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")
|
||||
log.debug('{status} projector DB upgrade to version 2'.format(status='Updated' if upgrade_db else 'Skipping'))
|
||||
|
@ -30,6 +30,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
from openlp.core.common import AppLocation, Settings, SlideLimits, UiStrings, translate
|
||||
from openlp.core.common.languagemanager import format_time
|
||||
from openlp.core.common.path import path_to_str
|
||||
from openlp.core.lib import SettingsTab, build_icon
|
||||
from openlp.core.ui.lib import PathEdit, PathType
|
||||
|
||||
@ -156,7 +157,7 @@ class AdvancedTab(SettingsTab):
|
||||
self.data_directory_new_label = QtWidgets.QLabel(self.data_directory_group_box)
|
||||
self.data_directory_new_label.setObjectName('data_directory_current_label')
|
||||
self.data_directory_path_edit = PathEdit(self.data_directory_group_box, path_type=PathType.Directories,
|
||||
default_path=str(AppLocation.get_directory(AppLocation.DataDir)))
|
||||
default_path=AppLocation.get_directory(AppLocation.DataDir))
|
||||
self.data_directory_layout.addRow(self.data_directory_new_label, self.data_directory_path_edit)
|
||||
self.new_data_directory_has_files_label = QtWidgets.QLabel(self.data_directory_group_box)
|
||||
self.new_data_directory_has_files_label.setObjectName('new_data_directory_has_files_label')
|
||||
@ -373,7 +374,7 @@ class AdvancedTab(SettingsTab):
|
||||
self.new_data_directory_has_files_label.hide()
|
||||
self.data_directory_cancel_button.hide()
|
||||
# Since data location can be changed, make sure the path is present.
|
||||
self.data_directory_path_edit.path = str(AppLocation.get_data_path())
|
||||
self.data_directory_path_edit.path = AppLocation.get_data_path()
|
||||
# Don't allow data directory move if running portable.
|
||||
if settings.value('advanced/is portable'):
|
||||
self.data_directory_group_box.hide()
|
||||
@ -497,12 +498,12 @@ class AdvancedTab(SettingsTab):
|
||||
'closed.').format(path=new_data_path),
|
||||
defaultButton=QtWidgets.QMessageBox.No)
|
||||
if answer != QtWidgets.QMessageBox.Yes:
|
||||
self.data_directory_path_edit.path = str(AppLocation.get_data_path())
|
||||
self.data_directory_path_edit.path = AppLocation.get_data_path()
|
||||
return
|
||||
# Check if data already exists here.
|
||||
self.check_data_overwrite(new_data_path)
|
||||
self.check_data_overwrite(path_to_str(new_data_path))
|
||||
# Save the new location.
|
||||
self.main_window.set_new_data_path(new_data_path)
|
||||
self.main_window.set_new_data_path(path_to_str(new_data_path))
|
||||
self.data_directory_cancel_button.show()
|
||||
|
||||
def on_data_directory_copy_check_box_toggled(self):
|
||||
@ -550,7 +551,7 @@ class AdvancedTab(SettingsTab):
|
||||
"""
|
||||
Cancel the data directory location change
|
||||
"""
|
||||
self.data_directory_path_edit.path = str(AppLocation.get_data_path())
|
||||
self.data_directory_path_edit.path = AppLocation.get_data_path()
|
||||
self.data_directory_copy_check_box.setChecked(False)
|
||||
self.main_window.set_new_data_path(None)
|
||||
self.main_window.set_copy_data(False)
|
||||
|
@ -23,10 +23,12 @@
|
||||
The general tab of the configuration dialog.
|
||||
"""
|
||||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
from openlp.core.common import Registry, Settings, UiStrings, translate, get_images_filter
|
||||
from openlp.core.common.path import path_to_str, str_to_path
|
||||
from openlp.core.lib import SettingsTab, ScreenList
|
||||
from openlp.core.ui.lib import ColorButton, PathEdit
|
||||
|
||||
@ -172,7 +174,8 @@ class GeneralTab(SettingsTab):
|
||||
self.logo_layout.setObjectName('logo_layout')
|
||||
self.logo_file_label = QtWidgets.QLabel(self.logo_group_box)
|
||||
self.logo_file_label.setObjectName('logo_file_label')
|
||||
self.logo_file_path_edit = PathEdit(self.logo_group_box, default_path=':/graphics/openlp-splash-screen.png')
|
||||
self.logo_file_path_edit = PathEdit(self.logo_group_box,
|
||||
default_path=Path(':/graphics/openlp-splash-screen.png'))
|
||||
self.logo_layout.addRow(self.logo_file_label, self.logo_file_path_edit)
|
||||
self.logo_color_label = QtWidgets.QLabel(self.logo_group_box)
|
||||
self.logo_color_label.setObjectName('logo_color_label')
|
||||
@ -266,7 +269,7 @@ class GeneralTab(SettingsTab):
|
||||
self.audio_group_box.setTitle(translate('OpenLP.GeneralTab', 'Background Audio'))
|
||||
self.start_paused_check_box.setText(translate('OpenLP.GeneralTab', 'Start background audio paused'))
|
||||
self.repeat_list_check_box.setText(translate('OpenLP.GeneralTab', 'Repeat track list'))
|
||||
self.logo_file_path_edit.dialog_caption = dialog_caption = translate('OpenLP.AdvancedTab', 'Select Logo File')
|
||||
self.logo_file_path_edit.dialog_caption = translate('OpenLP.AdvancedTab', 'Select Logo File')
|
||||
self.logo_file_path_edit.filters = '{text};;{names} (*)'.format(
|
||||
text=get_images_filter(), names=UiStrings().AllFiles)
|
||||
|
||||
@ -291,7 +294,7 @@ class GeneralTab(SettingsTab):
|
||||
self.auto_open_check_box.setChecked(settings.value('auto open'))
|
||||
self.show_splash_check_box.setChecked(settings.value('show splash'))
|
||||
self.logo_background_color = settings.value('logo background color')
|
||||
self.logo_file_path_edit.path = settings.value('logo file')
|
||||
self.logo_file_path_edit.path = str_to_path(settings.value('logo file'))
|
||||
self.logo_hide_on_startup_check_box.setChecked(settings.value('logo hide on startup'))
|
||||
self.logo_color_button.color = self.logo_background_color
|
||||
self.check_for_updates_check_box.setChecked(settings.value('update check'))
|
||||
@ -325,7 +328,7 @@ class GeneralTab(SettingsTab):
|
||||
settings.setValue('auto open', self.auto_open_check_box.isChecked())
|
||||
settings.setValue('show splash', self.show_splash_check_box.isChecked())
|
||||
settings.setValue('logo background color', self.logo_background_color)
|
||||
settings.setValue('logo file', self.logo_file_path_edit.path)
|
||||
settings.setValue('logo file', path_to_str(self.logo_file_path_edit.path))
|
||||
settings.setValue('logo hide on startup', self.logo_hide_on_startup_check_box.isChecked())
|
||||
settings.setValue('update check', self.check_for_updates_check_box.isChecked())
|
||||
settings.setValue('save prompt', self.save_check_service_check_box.isChecked())
|
||||
|
113
openlp/core/ui/lib/filedialog.py
Executable file
113
openlp/core/ui/lib/filedialog.py
Executable file
@ -0,0 +1,113 @@
|
||||
# -*- 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 #
|
||||
###############################################################################
|
||||
""" Patch the QFileDialog so it accepts and returns Path objects"""
|
||||
from pathlib import Path
|
||||
|
||||
from PyQt5 import QtWidgets
|
||||
|
||||
from openlp.core.common.path import path_to_str, str_to_path
|
||||
from openlp.core.lib import replace_params
|
||||
|
||||
|
||||
class FileDialog(QtWidgets.QFileDialog):
|
||||
@classmethod
|
||||
def getExistingDirectory(cls, *args, **kwargs):
|
||||
"""
|
||||
Wraps `getExistingDirectory` so that it can be called with, and return Path objects
|
||||
|
||||
:type parent: QtWidgets.QWidget or None
|
||||
:type caption: str
|
||||
:type directory: pathlib.Path
|
||||
:type options: QtWidgets.QFileDialog.Options
|
||||
:rtype: tuple[Path, str]
|
||||
"""
|
||||
args, kwargs = replace_params(args, kwargs, ((2, 'directory', path_to_str),))
|
||||
|
||||
return_value = super().getExistingDirectory(*args, **kwargs)
|
||||
|
||||
# getExistingDirectory returns a str that represents the path. The string is empty if the user cancels the
|
||||
# dialog.
|
||||
return str_to_path(return_value)
|
||||
|
||||
@classmethod
|
||||
def getOpenFileName(cls, *args, **kwargs):
|
||||
"""
|
||||
Wraps `getOpenFileName` so that it can be called with, and return Path objects
|
||||
|
||||
:type parent: QtWidgets.QWidget or None
|
||||
:type caption: str
|
||||
:type directory: pathlib.Path
|
||||
:type filter: str
|
||||
:type initialFilter: str
|
||||
:type options: QtWidgets.QFileDialog.Options
|
||||
:rtype: tuple[Path, str]
|
||||
"""
|
||||
args, kwargs = replace_params(args, kwargs, ((2, 'directory', path_to_str),))
|
||||
|
||||
file_name, selected_filter = super().getOpenFileName(*args, **kwargs)
|
||||
|
||||
# getOpenFileName returns a tuple. The first item is a str that represents the path. The string is empty if
|
||||
# the user cancels the dialog.
|
||||
return str_to_path(file_name), selected_filter
|
||||
|
||||
@classmethod
|
||||
def getOpenFileNames(cls, *args, **kwargs):
|
||||
"""
|
||||
Wraps `getOpenFileNames` so that it can be called with, and return Path objects
|
||||
|
||||
:type parent: QtWidgets.QWidget or None
|
||||
:type caption: str
|
||||
:type directory: pathlib.Path
|
||||
:type filter: str
|
||||
:type initialFilter: str
|
||||
:type options: QtWidgets.QFileDialog.Options
|
||||
:rtype: tuple[list[Path], str]
|
||||
"""
|
||||
args, kwargs = replace_params(args, kwargs, ((2, 'directory', path_to_str),))
|
||||
|
||||
file_names, selected_filter = super().getOpenFileNames(*args, **kwargs)
|
||||
|
||||
# getSaveFileName returns a tuple. The first item is a list of str's that represents the path. The list is
|
||||
# empty if the user cancels the dialog.
|
||||
paths = [str_to_path(path) for path in file_names]
|
||||
return paths, selected_filter
|
||||
|
||||
@classmethod
|
||||
def getSaveFileName(cls, *args, **kwargs):
|
||||
"""
|
||||
Wraps `getSaveFileName` so that it can be called with, and return Path objects
|
||||
|
||||
:type parent: QtWidgets.QWidget or None
|
||||
:type caption: str
|
||||
:type directory: pathlib.Path
|
||||
:type filter: str
|
||||
:type initialFilter: str
|
||||
:type options: QtWidgets.QFileDialog.Options
|
||||
:rtype: tuple[Path or None, str]
|
||||
"""
|
||||
args, kwargs = replace_params(args, kwargs, ((2, 'directory', path_to_str),))
|
||||
|
||||
file_name, selected_filter = super().getSaveFileName(*args, **kwargs)
|
||||
|
||||
# getSaveFileName returns a tuple. The first item represents the path as a str. The string is empty if the user
|
||||
# cancels the dialog.
|
||||
return str_to_path(file_name), selected_filter
|
56
openlp/core/ui/lib/pathedit.py
Executable file → Normal file
56
openlp/core/ui/lib/pathedit.py
Executable file → Normal file
@ -20,12 +20,14 @@
|
||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||
###############################################################################
|
||||
from enum import Enum
|
||||
import os.path
|
||||
from pathlib import Path
|
||||
|
||||
from PyQt5 import QtCore, QtWidgets
|
||||
|
||||
from openlp.core.common import UiStrings, translate
|
||||
from openlp.core.common.path import path_to_str, str_to_path
|
||||
from openlp.core.lib import build_icon
|
||||
from openlp.core.ui.lib.filedialog import FileDialog
|
||||
|
||||
|
||||
class PathType(Enum):
|
||||
@ -38,11 +40,11 @@ class PathEdit(QtWidgets.QWidget):
|
||||
The :class:`~openlp.core.ui.lib.pathedit.PathEdit` class subclasses QWidget to create a custom widget for use when
|
||||
a file or directory needs to be selected.
|
||||
"""
|
||||
pathChanged = QtCore.pyqtSignal(str)
|
||||
pathChanged = QtCore.pyqtSignal(Path)
|
||||
|
||||
def __init__(self, parent=None, path_type=PathType.Files, default_path=None, dialog_caption=None, show_revert=True):
|
||||
"""
|
||||
Initalise the PathEdit widget
|
||||
Initialise the PathEdit widget
|
||||
|
||||
:param parent: The parent of the widget. This is just passed to the super method.
|
||||
:type parent: QWidget or None
|
||||
@ -51,9 +53,9 @@ class PathEdit(QtWidgets.QWidget):
|
||||
:type dialog_caption: str
|
||||
|
||||
: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: pathlib.Path
|
||||
|
||||
:param show_revert: Used to determin if the 'revert button' should be visible.
|
||||
:param show_revert: Used to determine if the 'revert button' should be visible.
|
||||
:type show_revert: bool
|
||||
|
||||
:return: None
|
||||
@ -79,7 +81,6 @@ class PathEdit(QtWidgets.QWidget):
|
||||
widget_layout = QtWidgets.QHBoxLayout()
|
||||
widget_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.line_edit = QtWidgets.QLineEdit(self)
|
||||
self.line_edit.setText(self._path)
|
||||
widget_layout.addWidget(self.line_edit)
|
||||
self.browse_button = QtWidgets.QToolButton(self)
|
||||
self.browse_button.setIcon(build_icon(':/general/general_open.png'))
|
||||
@ -101,7 +102,7 @@ class PathEdit(QtWidgets.QWidget):
|
||||
A property getter method to return the selected path.
|
||||
|
||||
:return: The selected path
|
||||
:rtype: str
|
||||
:rtype: pathlib.Path
|
||||
"""
|
||||
return self._path
|
||||
|
||||
@ -111,11 +112,15 @@ class PathEdit(QtWidgets.QWidget):
|
||||
A Property setter method to set the selected path
|
||||
|
||||
:param path: The path to set the widget to
|
||||
:type path: str
|
||||
:type path: pathlib.Path
|
||||
|
||||
:return: None
|
||||
:rtype: None
|
||||
"""
|
||||
self._path = path
|
||||
self.line_edit.setText(path)
|
||||
self.line_edit.setToolTip(path)
|
||||
text = path_to_str(path)
|
||||
self.line_edit.setText(text)
|
||||
self.line_edit.setToolTip(text)
|
||||
|
||||
@property
|
||||
def path_type(self):
|
||||
@ -124,7 +129,7 @@ class PathEdit(QtWidgets.QWidget):
|
||||
selecting a file or directory.
|
||||
|
||||
:return: The type selected
|
||||
:rtype: Enum of PathEdit
|
||||
:rtype: PathType
|
||||
"""
|
||||
return self._path_type
|
||||
|
||||
@ -133,8 +138,11 @@ class PathEdit(QtWidgets.QWidget):
|
||||
"""
|
||||
A Property setter method to set the path type
|
||||
|
||||
:param path: The type of path to select
|
||||
:type path: Enum of PathEdit
|
||||
:param path_type: The type of path to select
|
||||
:type path_type: PathType
|
||||
|
||||
:return: None
|
||||
:rtype: None
|
||||
"""
|
||||
self._path_type = path_type
|
||||
self.update_button_tool_tips()
|
||||
@ -142,7 +150,9 @@ class PathEdit(QtWidgets.QWidget):
|
||||
def update_button_tool_tips(self):
|
||||
"""
|
||||
Called to update the tooltips on the buttons. This is changing path types, and when the widget is initalised
|
||||
|
||||
:return: None
|
||||
:rtype: None
|
||||
"""
|
||||
if self._path_type == PathType.Directories:
|
||||
self.browse_button.setToolTip(translate('OpenLP.PathEdit', 'Browse for directory.'))
|
||||
@ -156,21 +166,21 @@ class PathEdit(QtWidgets.QWidget):
|
||||
A handler to handle a click on the browse button.
|
||||
|
||||
Show the QFileDialog and process the input from the user
|
||||
|
||||
:return: None
|
||||
:rtype: None
|
||||
"""
|
||||
caption = self.dialog_caption
|
||||
path = ''
|
||||
path = None
|
||||
if self._path_type == PathType.Directories:
|
||||
if not caption:
|
||||
caption = translate('OpenLP.PathEdit', 'Select Directory')
|
||||
path = QtWidgets.QFileDialog.getExistingDirectory(self, caption,
|
||||
self._path, QtWidgets.QFileDialog.ShowDirsOnly)
|
||||
path = FileDialog.getExistingDirectory(self, caption, self._path, FileDialog.ShowDirsOnly)
|
||||
elif self._path_type == PathType.Files:
|
||||
if not caption:
|
||||
caption = self.dialog_caption = translate('OpenLP.PathEdit', 'Select File')
|
||||
path, filter_used = QtWidgets.QFileDialog.getOpenFileName(self, caption, self._path, self.filters)
|
||||
path, filter_used = FileDialog.getOpenFileName(self, caption, self._path, self.filters)
|
||||
if path:
|
||||
path = os.path.normpath(path)
|
||||
self.on_new_path(path)
|
||||
|
||||
def on_revert_button_clicked(self):
|
||||
@ -178,16 +188,21 @@ class PathEdit(QtWidgets.QWidget):
|
||||
A handler to handle a click on the revert button.
|
||||
|
||||
Set the new path to the value of the default_path instance variable.
|
||||
|
||||
:return: None
|
||||
:rtype: None
|
||||
"""
|
||||
self.on_new_path(self.default_path)
|
||||
|
||||
def on_line_edit_editing_finished(self):
|
||||
"""
|
||||
A handler to handle when the line edit has finished being edited.
|
||||
|
||||
:return: None
|
||||
:rtype: None
|
||||
"""
|
||||
self.on_new_path(self.line_edit.text())
|
||||
path = str_to_path(self.line_edit.text())
|
||||
self.on_new_path(path)
|
||||
|
||||
def on_new_path(self, path):
|
||||
"""
|
||||
@ -196,9 +211,10 @@ class PathEdit(QtWidgets.QWidget):
|
||||
Emits the pathChanged Signal
|
||||
|
||||
:param path: The new path
|
||||
:type path: str
|
||||
:type path: pathlib.Path
|
||||
|
||||
:return: None
|
||||
:rtype: None
|
||||
"""
|
||||
if self._path != path:
|
||||
self.path = path
|
||||
|
@ -38,7 +38,7 @@ from openlp.core.lib.projector.constants import ERROR_MSG, ERROR_STRING, E_AUTHE
|
||||
E_NETWORK, E_NOT_CONNECTED, E_UNKNOWN_SOCKET_ERROR, STATUS_STRING, S_CONNECTED, S_CONNECTING, S_COOLDOWN, \
|
||||
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.pjlink1 import PJLink
|
||||
from openlp.core.lib.projector.pjlink import PJLink
|
||||
from openlp.core.lib.projector.pjlink2 import PJLinkUDP
|
||||
from openlp.core.ui.projector.editform import ProjectorEditForm
|
||||
from openlp.core.ui.projector.sourceselectform import SourceSelectTabs, SourceSelectSingle
|
||||
|
@ -28,6 +28,7 @@ import os
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
from openlp.core.common import Registry, RegistryProperties, UiStrings, translate, get_images_filter, is_not_image_file
|
||||
from openlp.core.common.path import path_to_str, str_to_path
|
||||
from openlp.core.lib.theme import BackgroundType, BackgroundGradientType
|
||||
from openlp.core.lib.ui import critical_error_message_box
|
||||
from openlp.core.ui import ThemeLayoutForm
|
||||
@ -316,11 +317,11 @@ class ThemeForm(QtWidgets.QWizard, Ui_ThemeWizard, RegistryProperties):
|
||||
self.setField('background_type', 1)
|
||||
elif self.theme.background_type == BackgroundType.to_string(BackgroundType.Image):
|
||||
self.image_color_button.color = self.theme.background_border_color
|
||||
self.image_path_edit.path = self.theme.background_filename
|
||||
self.image_path_edit.path = str_to_path(self.theme.background_filename)
|
||||
self.setField('background_type', 2)
|
||||
elif self.theme.background_type == BackgroundType.to_string(BackgroundType.Video):
|
||||
self.video_color_button.color = self.theme.background_border_color
|
||||
self.video_path_edit.path = self.theme.background_filename
|
||||
self.video_path_edit.path = str_to_path(self.theme.background_filename)
|
||||
self.setField('background_type', 4)
|
||||
elif self.theme.background_type == BackgroundType.to_string(BackgroundType.Transparent):
|
||||
self.setField('background_type', 3)
|
||||
@ -448,18 +449,18 @@ class ThemeForm(QtWidgets.QWizard, Ui_ThemeWizard, RegistryProperties):
|
||||
"""
|
||||
self.theme.background_end_color = color
|
||||
|
||||
def on_image_path_edit_path_changed(self, filename):
|
||||
def on_image_path_edit_path_changed(self, file_path):
|
||||
"""
|
||||
Background Image button pushed.
|
||||
"""
|
||||
self.theme.background_filename = filename
|
||||
self.theme.background_filename = path_to_str(file_path)
|
||||
self.set_background_page_values()
|
||||
|
||||
def on_video_path_edit_path_changed(self, filename):
|
||||
def on_video_path_edit_path_changed(self, file_path):
|
||||
"""
|
||||
Background video button pushed.
|
||||
"""
|
||||
self.theme.background_filename = filename
|
||||
self.theme.background_filename = path_to_str(file_path)
|
||||
self.set_background_page_values()
|
||||
|
||||
def on_main_color_changed(self, color):
|
||||
|
@ -22,7 +22,6 @@
|
||||
"""
|
||||
The Theme Manager manages adding, deleteing and modifying of themes.
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import zipfile
|
||||
import shutil
|
||||
@ -32,12 +31,14 @@ from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
from openlp.core.common import Registry, RegistryProperties, AppLocation, Settings, OpenLPMixin, RegistryMixin, \
|
||||
UiStrings, check_directory_exists, translate, is_win, get_filesystem_encoding, delete_file
|
||||
from openlp.core.lib import FileDialog, ImageSource, ValidationError, get_text_file_string, build_icon, \
|
||||
from openlp.core.common.path import path_to_str, str_to_path
|
||||
from openlp.core.lib import ImageSource, ValidationError, get_text_file_string, build_icon, \
|
||||
check_item_selected, create_thumb, validate_thumb
|
||||
from openlp.core.lib.theme import Theme, BackgroundType
|
||||
from openlp.core.lib.ui import critical_error_message_box, create_widget_action
|
||||
from openlp.core.ui import FileRenameForm, ThemeForm
|
||||
from openlp.core.ui.lib import OpenLPToolbar
|
||||
from openlp.core.ui.lib.filedialog import FileDialog
|
||||
from openlp.core.common.languagemanager import get_locale_key
|
||||
|
||||
|
||||
@ -424,15 +425,17 @@ class ThemeManager(OpenLPMixin, RegistryMixin, QtWidgets.QWidget, Ui_ThemeManage
|
||||
those files. This process will only load version 2 themes.
|
||||
:param field:
|
||||
"""
|
||||
files = FileDialog.getOpenFileNames(self,
|
||||
translate('OpenLP.ThemeManager', 'Select Theme Import File'),
|
||||
Settings().value(self.settings_section + '/last directory import'),
|
||||
translate('OpenLP.ThemeManager', 'OpenLP Themes (*.otz)'))
|
||||
self.log_info('New Themes {name}'.format(name=str(files)))
|
||||
if not files:
|
||||
file_paths, selected_filter = FileDialog.getOpenFileNames(
|
||||
self,
|
||||
translate('OpenLP.ThemeManager', 'Select Theme Import File'),
|
||||
str_to_path(Settings().value(self.settings_section + '/last directory import')),
|
||||
translate('OpenLP.ThemeManager', 'OpenLP Themes (*.otz)'))
|
||||
self.log_info('New Themes {file_paths}'.format(file_paths=file_paths))
|
||||
if not file_paths:
|
||||
return
|
||||
self.application.set_busy_cursor()
|
||||
for file_name in files:
|
||||
for file_path in file_paths:
|
||||
file_name = path_to_str(file_path)
|
||||
Settings().setValue(self.settings_section + '/last directory import', str(file_name))
|
||||
self.unzip_theme(file_name, self.path)
|
||||
self.load_themes()
|
||||
|
@ -22,6 +22,8 @@
|
||||
"""
|
||||
The Create/Edit theme wizard
|
||||
"""
|
||||
from pathlib import Path
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
from openlp.core.common import UiStrings, translate, is_macosx
|
||||
|
@ -255,7 +255,7 @@ class BGExtract(RegistryProperties):
|
||||
chapter=chapter,
|
||||
version=version)
|
||||
soup = get_soup_for_bible_ref(
|
||||
'http://biblegateway.com/passage/?{url}'.format(url=url_params),
|
||||
'http://www.biblegateway.com/passage/?{url}'.format(url=url_params),
|
||||
pre_parse_regex=r'<meta name.*?/>', pre_parse_substitute='')
|
||||
if not soup:
|
||||
return None
|
||||
@ -284,7 +284,7 @@ class BGExtract(RegistryProperties):
|
||||
"""
|
||||
log.debug('BGExtract.get_books_from_http("{version}")'.format(version=version))
|
||||
url_params = urllib.parse.urlencode({'action': 'getVersionInfo', 'vid': '{version}'.format(version=version)})
|
||||
reference_url = 'http://biblegateway.com/versions/?{url}#books'.format(url=url_params)
|
||||
reference_url = 'http://www.biblegateway.com/versions/?{url}#books'.format(url=url_params)
|
||||
page = get_web_page(reference_url)
|
||||
if not page:
|
||||
send_error_message('download')
|
||||
|
@ -20,10 +20,11 @@
|
||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||
###############################################################################
|
||||
|
||||
from PyQt5 import QtGui, QtWidgets
|
||||
from PyQt5 import QtWidgets
|
||||
|
||||
from openlp.core.common import Settings, UiStrings, translate
|
||||
from openlp.core.lib import SettingsTab, build_icon
|
||||
from openlp.core.common.path import path_to_str, str_to_path
|
||||
from openlp.core.lib import SettingsTab
|
||||
from openlp.core.lib.ui import critical_error_message_box
|
||||
from openlp.core.ui.lib import PathEdit
|
||||
from openlp.plugins.presentations.lib.pdfcontroller import PdfController
|
||||
@ -156,7 +157,7 @@ class PresentationTab(SettingsTab):
|
||||
self.program_path_edit.setEnabled(enable_pdf_program)
|
||||
pdf_program = Settings().value(self.settings_section + '/pdf_program')
|
||||
if pdf_program:
|
||||
self.program_path_edit.path = pdf_program
|
||||
self.program_path_edit.path = str_to_path(pdf_program)
|
||||
|
||||
def save(self):
|
||||
"""
|
||||
@ -192,7 +193,7 @@ class PresentationTab(SettingsTab):
|
||||
Settings().setValue(setting_key, self.ppt_window_check_box.checkState())
|
||||
changed = True
|
||||
# Save pdf-settings
|
||||
pdf_program = self.program_path_edit.path
|
||||
pdf_program = path_to_str(self.program_path_edit.path)
|
||||
enable_pdf_program = self.pdf_program_check_box.checkState()
|
||||
# If the given program is blank disable using the program
|
||||
if pdf_program == '':
|
||||
@ -219,12 +220,13 @@ class PresentationTab(SettingsTab):
|
||||
checkbox.setEnabled(controller.is_available())
|
||||
self.set_controller_text(checkbox, controller)
|
||||
|
||||
def on_program_path_edit_path_changed(self, filename):
|
||||
def on_program_path_edit_path_changed(self, new_path):
|
||||
"""
|
||||
Select the mudraw or ghostscript binary that should be used.
|
||||
"""
|
||||
if filename:
|
||||
if not PdfController.process_check_binary(filename):
|
||||
new_path = path_to_str(new_path)
|
||||
if new_path:
|
||||
if not PdfController.process_check_binary(new_path):
|
||||
critical_error_message_box(UiStrings().Error,
|
||||
translate('PresentationPlugin.PresentationTab',
|
||||
'The program is not ghostscript or mudraw which is required.'))
|
||||
|
@ -28,12 +28,15 @@ import logging
|
||||
import re
|
||||
import os
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
from PyQt5 import QtCore, QtWidgets
|
||||
|
||||
from openlp.core.common import Registry, RegistryProperties, AppLocation, UiStrings, check_directory_exists, translate
|
||||
from openlp.core.lib import FileDialog, PluginStatus, MediaType, create_separated_list
|
||||
from openlp.core.common.path import path_to_str
|
||||
from openlp.core.lib import PluginStatus, MediaType, create_separated_list
|
||||
from openlp.core.lib.ui import set_case_insensitive_completer, critical_error_message_box, find_and_set_in_combo_box
|
||||
from openlp.core.ui.lib.filedialog import FileDialog
|
||||
from openlp.core.common.languagemanager import get_natural_key
|
||||
from openlp.plugins.songs.lib import VerseType, clean_song
|
||||
from openlp.plugins.songs.lib.db import Book, Song, Author, AuthorType, Topic, MediaFile, SongBookEntry
|
||||
@ -925,9 +928,10 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
|
||||
Loads file(s) from the filesystem.
|
||||
"""
|
||||
filters = '{text} (*)'.format(text=UiStrings().AllFiles)
|
||||
file_names = FileDialog.getOpenFileNames(self, translate('SongsPlugin.EditSongForm', 'Open File(s)'), '',
|
||||
filters)
|
||||
for filename in file_names:
|
||||
file_paths, selected_filter = FileDialog.getOpenFileNames(
|
||||
self, translate('SongsPlugin.EditSongForm', 'Open File(s)'), Path(), filters)
|
||||
for file_path in file_paths:
|
||||
filename = path_to_str(file_path)
|
||||
item = QtWidgets.QListWidgetItem(os.path.split(str(filename))[1])
|
||||
item.setData(QtCore.Qt.UserRole, filename)
|
||||
self.audio_list_widget.addItem(item)
|
||||
|
@ -29,8 +29,9 @@ import os
|
||||
from PyQt5 import QtCore, QtWidgets
|
||||
|
||||
from openlp.core.common import RegistryProperties, Settings, UiStrings, translate
|
||||
from openlp.core.lib import FileDialog
|
||||
from openlp.core.common.path import path_to_str, str_to_path
|
||||
from openlp.core.lib.ui import critical_error_message_box
|
||||
from openlp.core.ui.lib.filedialog import FileDialog
|
||||
from openlp.core.ui.lib.wizard import OpenLPWizard, WizardStrings
|
||||
from openlp.plugins.songs.lib.importer import SongFormat, SongFormatSelect
|
||||
|
||||
@ -237,10 +238,11 @@ class SongImportForm(OpenLPWizard, RegistryProperties):
|
||||
if filters:
|
||||
filters += ';;'
|
||||
filters += '{text} (*)'.format(text=UiStrings().AllFiles)
|
||||
file_names = FileDialog.getOpenFileNames(
|
||||
file_paths, selected_filter = FileDialog.getOpenFileNames(
|
||||
self, title,
|
||||
Settings().value(self.plugin.settings_section + '/last directory import'), filters)
|
||||
if file_names:
|
||||
str_to_path(Settings().value(self.plugin.settings_section + '/last directory import')), filters)
|
||||
if file_paths:
|
||||
file_names = [path_to_str(file_path) for file_path in file_paths]
|
||||
listbox.addItems(file_names)
|
||||
Settings().setValue(self.plugin.settings_section + '/last directory import',
|
||||
os.path.split(str(file_names[0]))[0])
|
||||
|
@ -27,6 +27,7 @@ from PyQt5 import QtCore, QtWidgets
|
||||
from sqlalchemy.sql import and_
|
||||
|
||||
from openlp.core.common import RegistryProperties, Settings, check_directory_exists, translate
|
||||
from openlp.core.common.path import path_to_str, str_to_path
|
||||
from openlp.core.lib.ui import critical_error_message_box
|
||||
from openlp.plugins.songusage.lib.db import SongUsageItem
|
||||
from .songusagedetaildialog import Ui_SongUsageDetailDialog
|
||||
@ -55,20 +56,21 @@ class SongUsageDetailForm(QtWidgets.QDialog, Ui_SongUsageDetailDialog, RegistryP
|
||||
"""
|
||||
self.from_date_calendar.setSelectedDate(Settings().value(self.plugin.settings_section + '/from date'))
|
||||
self.to_date_calendar.setSelectedDate(Settings().value(self.plugin.settings_section + '/to date'))
|
||||
self.report_path_edit.path = Settings().value(self.plugin.settings_section + '/last directory export')
|
||||
self.report_path_edit.path = str_to_path(
|
||||
Settings().value(self.plugin.settings_section + '/last directory export'))
|
||||
|
||||
def on_report_path_edit_path_changed(self, file_path):
|
||||
"""
|
||||
Triggered when the Directory selection button is clicked
|
||||
"""
|
||||
Settings().setValue(self.plugin.settings_section + '/last directory export', file_path)
|
||||
Settings().setValue(self.plugin.settings_section + '/last directory export', path_to_str(file_path))
|
||||
|
||||
def accept(self):
|
||||
"""
|
||||
Ok was triggered so lets save the data and run the report
|
||||
"""
|
||||
log.debug('accept')
|
||||
path = self.report_path_edit.path
|
||||
path = path_to_str(self.report_path_edit.path)
|
||||
if not path:
|
||||
self.main_window.error_message(
|
||||
translate('SongUsagePlugin.SongUsageDetailForm', 'Output Path Not Selected'),
|
||||
|
88
tests/functional/openlp_core_common/test_path.py
Normal file
88
tests/functional/openlp_core_common/test_path.py
Normal file
@ -0,0 +1,88 @@
|
||||
# -*- 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 #
|
||||
###############################################################################
|
||||
"""
|
||||
Package to test the openlp.core.common.path package.
|
||||
"""
|
||||
import os
|
||||
from pathlib import Path
|
||||
from unittest import TestCase
|
||||
|
||||
from openlp.core.common.path import path_to_str, str_to_path
|
||||
|
||||
|
||||
class TestPath(TestCase):
|
||||
"""
|
||||
Tests for the :mod:`openlp.core.common.path` module
|
||||
"""
|
||||
|
||||
def test_path_to_str_type_error(self):
|
||||
"""
|
||||
Test that `path_to_str` raises a type error when called with an invalid type
|
||||
"""
|
||||
# GIVEN: The `path_to_str` function
|
||||
# WHEN: Calling `path_to_str` with an invalid Type
|
||||
# THEN: A TypeError should have been raised
|
||||
with self.assertRaises(TypeError):
|
||||
path_to_str(str())
|
||||
|
||||
def test_path_to_str_none(self):
|
||||
"""
|
||||
Test that `path_to_str` correctly converts the path parameter when passed with None
|
||||
"""
|
||||
# GIVEN: The `path_to_str` function
|
||||
# WHEN: Calling the `path_to_str` function with None
|
||||
result = path_to_str(None)
|
||||
|
||||
# THEN: `path_to_str` should return an empty string
|
||||
self.assertEqual(result, '')
|
||||
|
||||
def test_path_to_str_path_object(self):
|
||||
"""
|
||||
Test that `path_to_str` correctly converts the path parameter when passed a Path object
|
||||
"""
|
||||
# GIVEN: The `path_to_str` function
|
||||
# WHEN: Calling the `path_to_str` function with a Path object
|
||||
result = path_to_str(Path('test/path'))
|
||||
|
||||
# THEN: `path_to_str` should return a string representation of the Path object
|
||||
self.assertEqual(result, os.path.join('test', 'path'))
|
||||
|
||||
def test_str_to_path_type_error(self):
|
||||
"""
|
||||
Test that `str_to_path` raises a type error when called with an invalid type
|
||||
"""
|
||||
# GIVEN: The `str_to_path` function
|
||||
# WHEN: Calling `str_to_path` with an invalid Type
|
||||
# THEN: A TypeError should have been raised
|
||||
with self.assertRaises(TypeError):
|
||||
str_to_path(Path())
|
||||
|
||||
def test_str_to_path_empty_str(self):
|
||||
"""
|
||||
Test that `str_to_path` correctly converts the string parameter when passed with and empty string
|
||||
"""
|
||||
# GIVEN: The `str_to_path` function
|
||||
# WHEN: Calling the `str_to_path` function with None
|
||||
result = str_to_path('')
|
||||
|
||||
# THEN: `path_to_str` should return None
|
||||
self.assertEqual(result, None)
|
@ -20,12 +20,10 @@
|
||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||
###############################################################################
|
||||
"""
|
||||
Package to test the openlp.core.lib.filedialog package.
|
||||
Package to test the openlp.core.ui.lib.filedialog package.
|
||||
"""
|
||||
from unittest import TestCase
|
||||
from unittest.mock import MagicMock, call, patch
|
||||
|
||||
from openlp.core.lib.filedialog import FileDialog
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
|
||||
class TestFileDialog(TestCase):
|
||||
@ -33,9 +31,9 @@ class TestFileDialog(TestCase):
|
||||
Test the functions in the :mod:`filedialog` module.
|
||||
"""
|
||||
def setUp(self):
|
||||
self.os_patcher = patch('openlp.core.lib.filedialog.os')
|
||||
self.qt_gui_patcher = patch('openlp.core.lib.filedialog.QtWidgets')
|
||||
self.ui_strings_patcher = patch('openlp.core.lib.filedialog.UiStrings')
|
||||
self.os_patcher = patch('openlp.core.ui.lib.filedialog.os')
|
||||
self.qt_gui_patcher = patch('openlp.core.ui.lib.filedialog.QtWidgets')
|
||||
self.ui_strings_patcher = patch('openlp.core.ui.lib.filedialog.UiStrings')
|
||||
self.mocked_os = self.os_patcher.start()
|
||||
self.mocked_qt_gui = self.qt_gui_patcher.start()
|
||||
self.mocked_ui_strings = self.ui_strings_patcher.start()
|
||||
@ -45,52 +43,3 @@ class TestFileDialog(TestCase):
|
||||
self.os_patcher.stop()
|
||||
self.qt_gui_patcher.stop()
|
||||
self.ui_strings_patcher.stop()
|
||||
|
||||
def test_get_open_file_names_canceled(self):
|
||||
"""
|
||||
Test that FileDialog.getOpenFileNames() returns and empty QStringList when QFileDialog is canceled
|
||||
(returns an empty QStringList)
|
||||
"""
|
||||
self.mocked_os.reset_mock()
|
||||
|
||||
# GIVEN: An empty QStringList as a return value from QFileDialog.getOpenFileNames
|
||||
self.mocked_qt_gui.QFileDialog.getOpenFileNames.return_value = ([], [])
|
||||
|
||||
# WHEN: FileDialog.getOpenFileNames is called
|
||||
result = FileDialog.getOpenFileNames(self.mocked_parent)
|
||||
|
||||
# THEN: The returned value should be an empty QStringList and os.path.exists should not have been called
|
||||
assert not self.mocked_os.path.exists.called
|
||||
self.assertEqual(result, [],
|
||||
'FileDialog.getOpenFileNames should return and empty list when QFileDialog.getOpenFileNames '
|
||||
'is canceled')
|
||||
|
||||
def test_returned_file_list(self):
|
||||
"""
|
||||
Test that FileDialog.getOpenFileNames handles a list of files properly when QFileList.getOpenFileNames
|
||||
returns a good file name, a url encoded file name and a non-existing file
|
||||
"""
|
||||
self.mocked_os.rest_mock()
|
||||
self.mocked_qt_gui.reset_mock()
|
||||
|
||||
# GIVEN: A List of known values as a return value from QFileDialog.getOpenFileNames and a list of valid file
|
||||
# names.
|
||||
self.mocked_qt_gui.QFileDialog.getOpenFileNames.return_value = ([
|
||||
'/Valid File', '/url%20encoded%20file%20%231', '/non-existing'], [])
|
||||
self.mocked_os.path.exists.side_effect = lambda file_name: file_name in [
|
||||
'/Valid File', '/url encoded file #1']
|
||||
self.mocked_ui_strings().FileNotFound = 'File Not Found'
|
||||
self.mocked_ui_strings().FileNotFoundMessage = 'File {name} not found.\nPlease try selecting it individually.'
|
||||
|
||||
# WHEN: FileDialog.getOpenFileNames is called
|
||||
result = FileDialog.getOpenFileNames(self.mocked_parent)
|
||||
|
||||
# THEN: os.path.exists should have been called with known args. QmessageBox.information should have been
|
||||
# called. The returned result should correlate with the input.
|
||||
call_list = [call('/Valid File'), call('/url%20encoded%20file%20%231'), call('/url encoded file #1'),
|
||||
call('/non-existing'), call('/non-existing')]
|
||||
self.mocked_os.path.exists.assert_has_calls(call_list)
|
||||
self.mocked_qt_gui.QMessageBox.information.assert_called_with(
|
||||
self.mocked_parent, 'File Not Found',
|
||||
'File /non-existing not found.\nPlease try selecting it individually.')
|
||||
self.assertEqual(result, ['/Valid File', '/url encoded file #1'], 'The returned file list is incorrect')
|
||||
|
@ -29,10 +29,9 @@ from unittest.mock import MagicMock, patch
|
||||
|
||||
from PyQt5 import QtCore, QtGui
|
||||
|
||||
from openlp.core.lib import FormattingTags, expand_chords_for_printing
|
||||
from openlp.core.lib import build_icon, check_item_selected, clean_tags, create_thumb, create_separated_list, \
|
||||
expand_tags, get_text_file_string, image_to_byte, resize_image, str_to_bool, validate_thumb, expand_chords, \
|
||||
compare_chord_lyric, find_formatting_tags
|
||||
from openlp.core.lib import FormattingTags, build_icon, check_item_selected, clean_tags, compare_chord_lyric, \
|
||||
create_separated_list, create_thumb, expand_chords, expand_chords_for_printing, expand_tags, find_formatting_tags, \
|
||||
get_text_file_string, image_to_byte, replace_params, resize_image, str_to_bool, validate_thumb
|
||||
|
||||
TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'resources'))
|
||||
|
||||
@ -652,6 +651,38 @@ class TestLib(TestCase):
|
||||
mocked_os.stat.assert_any_call(thumb_path)
|
||||
assert result is False, 'The result should be False'
|
||||
|
||||
def test_replace_params_no_params(self):
|
||||
"""
|
||||
Test replace_params when called with and empty tuple instead of parameters to replace
|
||||
"""
|
||||
# GIVEN: Some test data
|
||||
test_args = (1, 2)
|
||||
test_kwargs = {'arg3': 3, 'arg4': 4}
|
||||
test_params = tuple()
|
||||
|
||||
# WHEN: Calling replace_params
|
||||
result_args, result_kwargs = replace_params(test_args, test_kwargs, test_params)
|
||||
|
||||
# THEN: The positional and keyword args should not have changed
|
||||
self.assertEqual(test_args, result_args)
|
||||
self.assertEqual(test_kwargs, result_kwargs)
|
||||
|
||||
def test_replace_params_params(self):
|
||||
"""
|
||||
Test replace_params when given a positional and a keyword argument to change
|
||||
"""
|
||||
# GIVEN: Some test data
|
||||
test_args = (1, 2)
|
||||
test_kwargs = {'arg3': 3, 'arg4': 4}
|
||||
test_params = ((1, 'arg2', str), (2, 'arg3', str))
|
||||
|
||||
# WHEN: Calling replace_params
|
||||
result_args, result_kwargs = replace_params(test_args, test_kwargs, test_params)
|
||||
|
||||
# THEN: The positional and keyword args should have have changed
|
||||
self.assertEqual(result_args, (1, '2'))
|
||||
self.assertEqual(result_kwargs, {'arg3': '3', 'arg4': 4})
|
||||
|
||||
def test_resize_thumb(self):
|
||||
"""
|
||||
Test the resize_thumb() function
|
||||
|
@ -22,7 +22,7 @@
|
||||
"""
|
||||
Package to test the openlp.core.lib.projector.constants package.
|
||||
"""
|
||||
from unittest import TestCase, skip
|
||||
from unittest import TestCase
|
||||
|
||||
|
||||
class TestProjectorConstants(TestCase):
|
||||
@ -40,4 +40,4 @@ class TestProjectorConstants(TestCase):
|
||||
from openlp.core.lib.projector.constants import PJLINK_DEFAULT_CODES
|
||||
|
||||
# THEN: Verify dictionary was build correctly
|
||||
self.assertEquals(PJLINK_DEFAULT_CODES, TEST_VIDEO_CODES, 'PJLink video strings should match')
|
||||
self.assertEqual(PJLINK_DEFAULT_CODES, TEST_VIDEO_CODES, 'PJLink video strings should match')
|
||||
|
@ -29,8 +29,8 @@ import os
|
||||
import shutil
|
||||
from tempfile import mkdtemp
|
||||
|
||||
from unittest import TestCase, skip
|
||||
from unittest.mock import MagicMock, patch
|
||||
from unittest import TestCase
|
||||
from unittest.mock import patch
|
||||
|
||||
from openlp.core.lib.projector import upgrade
|
||||
from openlp.core.lib.db import upgrade_db
|
||||
@ -413,7 +413,7 @@ class TestProjectorDB(TestCase):
|
||||
Test add_projector() fail
|
||||
"""
|
||||
# GIVEN: Test entry in the database
|
||||
ignore_result = self.projector.add_projector(Projector(**TEST1_DATA))
|
||||
self.projector.add_projector(Projector(**TEST1_DATA))
|
||||
|
||||
# WHEN: Attempt to add same projector entry
|
||||
results = self.projector.add_projector(Projector(**TEST1_DATA))
|
||||
@ -439,7 +439,7 @@ class TestProjectorDB(TestCase):
|
||||
Test update_projector() when entry not in database
|
||||
"""
|
||||
# GIVEN: Projector entry in database
|
||||
ignore_result = self.projector.add_projector(Projector(**TEST1_DATA))
|
||||
self.projector.add_projector(Projector(**TEST1_DATA))
|
||||
projector = Projector(**TEST2_DATA)
|
||||
|
||||
# WHEN: Attempt to update data with a different ID
|
||||
|
208
tests/functional/openlp_core_lib/test_projector_pjlink_base.py
Normal file
208
tests/functional/openlp_core_lib/test_projector_pjlink_base.py
Normal file
@ -0,0 +1,208 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||
|
||||
###############################################################################
|
||||
# OpenLP - Open Source Lyrics Projection #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Copyright (c) 2008-2015 OpenLP Developers #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# This program is free software; you can redistribute it and/or modify it #
|
||||
# under the terms of the GNU General Public License as published by the Free #
|
||||
# Software Foundation; version 2 of the License. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT #
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
|
||||
# more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License along #
|
||||
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||
###############################################################################
|
||||
"""
|
||||
Package to test the openlp.core.lib.projector.pjlink base package.
|
||||
"""
|
||||
from unittest import TestCase
|
||||
from unittest.mock import call, patch, MagicMock
|
||||
|
||||
from openlp.core.lib.projector.pjlink import PJLink
|
||||
from openlp.core.lib.projector.constants import E_PARAMETER, ERROR_STRING, S_ON, S_CONNECTED
|
||||
|
||||
from tests.resources.projector.data import TEST_PIN, TEST_SALT, TEST_CONNECT_AUTHENTICATE, TEST_HASH
|
||||
|
||||
pjlink_test = PJLink(name='test', ip='127.0.0.1', pin=TEST_PIN, no_poll=True)
|
||||
|
||||
|
||||
class TestPJLinkBase(TestCase):
|
||||
"""
|
||||
Tests for the PJLink module
|
||||
"""
|
||||
@patch.object(pjlink_test, 'readyRead')
|
||||
@patch.object(pjlink_test, 'send_command')
|
||||
@patch.object(pjlink_test, 'waitForReadyRead')
|
||||
@patch('openlp.core.common.qmd5_hash')
|
||||
def test_authenticated_connection_call(self,
|
||||
mock_qmd5_hash,
|
||||
mock_waitForReadyRead,
|
||||
mock_send_command,
|
||||
mock_readyRead):
|
||||
"""
|
||||
Ticket 92187: Fix for projector connect with PJLink authentication exception.
|
||||
"""
|
||||
# GIVEN: Test object
|
||||
pjlink = pjlink_test
|
||||
|
||||
# WHEN: Calling check_login with authentication request:
|
||||
pjlink.check_login(data=TEST_CONNECT_AUTHENTICATE)
|
||||
|
||||
# THEN: Should have called qmd5_hash
|
||||
self.assertTrue(mock_qmd5_hash.called_with(TEST_SALT,
|
||||
"Connection request should have been called with TEST_SALT"))
|
||||
self.assertTrue(mock_qmd5_hash.called_with(TEST_PIN,
|
||||
"Connection request should have been called with TEST_PIN"))
|
||||
|
||||
@patch.object(pjlink_test, 'change_status')
|
||||
def test_status_change(self, mock_change_status):
|
||||
"""
|
||||
Test process_command call with ERR2 (Parameter) status
|
||||
"""
|
||||
# GIVEN: Test object
|
||||
pjlink = pjlink_test
|
||||
|
||||
# WHEN: process_command is called with "ERR2" status from projector
|
||||
pjlink.process_command('POWR', 'ERR2')
|
||||
|
||||
# THEN: change_status should have called change_status with E_UNDEFINED
|
||||
# as first parameter
|
||||
mock_change_status.called_with(E_PARAMETER,
|
||||
'change_status should have been called with "{}"'.format(
|
||||
ERROR_STRING[E_PARAMETER]))
|
||||
|
||||
@patch.object(pjlink_test, 'send_command')
|
||||
@patch.object(pjlink_test, 'waitForReadyRead')
|
||||
@patch.object(pjlink_test, 'projectorAuthentication')
|
||||
@patch.object(pjlink_test, 'timer')
|
||||
@patch.object(pjlink_test, 'socket_timer')
|
||||
def test_bug_1593882_no_pin_authenticated_connection(self,
|
||||
mock_socket_timer,
|
||||
mock_timer,
|
||||
mock_authentication,
|
||||
mock_ready_read,
|
||||
mock_send_command):
|
||||
"""
|
||||
Test bug 1593882 no pin and authenticated request exception
|
||||
"""
|
||||
# GIVEN: Test object and mocks
|
||||
pjlink = pjlink_test
|
||||
pjlink.pin = None
|
||||
mock_ready_read.return_value = True
|
||||
|
||||
# WHEN: call with authentication request and pin not set
|
||||
pjlink.check_login(data=TEST_CONNECT_AUTHENTICATE)
|
||||
|
||||
# THEN: 'No Authentication' signal should have been sent
|
||||
mock_authentication.emit.assert_called_with(pjlink.ip)
|
||||
|
||||
@patch.object(pjlink_test, 'waitForReadyRead')
|
||||
@patch.object(pjlink_test, 'state')
|
||||
@patch.object(pjlink_test, '_send_command')
|
||||
@patch.object(pjlink_test, 'timer')
|
||||
@patch.object(pjlink_test, 'socket_timer')
|
||||
def test_bug_1593883_pjlink_authentication(self,
|
||||
mock_socket_timer,
|
||||
mock_timer,
|
||||
mock_send_command,
|
||||
mock_state,
|
||||
mock_waitForReadyRead):
|
||||
"""
|
||||
Test bugfix 1593883 pjlink authentication
|
||||
"""
|
||||
# GIVEN: Test object and data
|
||||
pjlink = pjlink_test
|
||||
pjlink.pin = TEST_PIN
|
||||
mock_state.return_value = pjlink.ConnectedState
|
||||
mock_waitForReadyRead.return_value = True
|
||||
|
||||
# WHEN: Athenticated connection is called
|
||||
pjlink.check_login(data=TEST_CONNECT_AUTHENTICATE)
|
||||
|
||||
# THEN: send_command should have the proper authentication
|
||||
self.assertEqual("{test}".format(test=mock_send_command.call_args),
|
||||
"call(data='{hash}%1CLSS ?\\r')".format(hash=TEST_HASH))
|
||||
|
||||
@patch.object(pjlink_test, 'disconnect_from_host')
|
||||
def test_socket_abort(self, mock_disconnect):
|
||||
"""
|
||||
Test PJLink.socket_abort calls disconnect_from_host
|
||||
"""
|
||||
# GIVEN: Test object
|
||||
pjlink = pjlink_test
|
||||
|
||||
# WHEN: Calling socket_abort
|
||||
pjlink.socket_abort()
|
||||
|
||||
# THEN: disconnect_from_host should be called
|
||||
self.assertTrue(mock_disconnect.called, 'Should have called disconnect_from_host')
|
||||
|
||||
def test_poll_loop_not_connected(self):
|
||||
"""
|
||||
Test PJLink.poll_loop not connected return
|
||||
"""
|
||||
# GIVEN: Test object and mocks
|
||||
pjlink = pjlink_test
|
||||
pjlink.state = MagicMock()
|
||||
pjlink.timer = MagicMock()
|
||||
pjlink.state.return_value = False
|
||||
pjlink.ConnectedState = True
|
||||
|
||||
# WHEN: PJLink.poll_loop called
|
||||
pjlink.poll_loop()
|
||||
|
||||
# THEN: poll_loop should exit without calling any other method
|
||||
self.assertFalse(pjlink.timer.called, 'Should have returned without calling any other method')
|
||||
|
||||
@patch.object(pjlink_test, 'send_command')
|
||||
def test_poll_loop_start(self, mock_send_command):
|
||||
"""
|
||||
Test PJLink.poll_loop makes correct calls
|
||||
"""
|
||||
# GIVEN: test object and test data
|
||||
pjlink = pjlink_test
|
||||
pjlink.state = MagicMock()
|
||||
pjlink.timer = MagicMock()
|
||||
pjlink.timer.interval = MagicMock()
|
||||
pjlink.timer.setInterval = MagicMock()
|
||||
pjlink.timer.start = MagicMock()
|
||||
pjlink.poll_time = 20
|
||||
pjlink.power = S_ON
|
||||
pjlink.source_available = None
|
||||
pjlink.other_info = None
|
||||
pjlink.manufacturer = None
|
||||
pjlink.model = None
|
||||
pjlink.pjlink_name = None
|
||||
pjlink.ConnectedState = S_CONNECTED
|
||||
pjlink.timer.interval.return_value = 10
|
||||
pjlink.state.return_value = S_CONNECTED
|
||||
call_list = [
|
||||
call('POWR', queue=True),
|
||||
call('ERST', queue=True),
|
||||
call('LAMP', queue=True),
|
||||
call('AVMT', queue=True),
|
||||
call('INPT', queue=True),
|
||||
call('INST', queue=True),
|
||||
call('INFO', queue=True),
|
||||
call('INF1', queue=True),
|
||||
call('INF2', queue=True),
|
||||
call('NAME', queue=True),
|
||||
]
|
||||
|
||||
# WHEN: PJLink.poll_loop is called
|
||||
pjlink.poll_loop()
|
||||
|
||||
# THEN: proper calls were made to retrieve projector data
|
||||
# First, call to update the timer with the next interval
|
||||
self.assertTrue(pjlink.timer.setInterval.called, 'Should have updated the timer')
|
||||
# Next, should have called the timer to start
|
||||
self.assertTrue(pjlink.timer.start.called, 'Should have started the timer')
|
||||
# Finally, should have called send_command with a list of projetctor status checks
|
||||
mock_send_command.assert_has_calls(call_list, 'Should have queued projector queries')
|
@ -0,0 +1,222 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
|
||||
|
||||
###############################################################################
|
||||
# OpenLP - Open Source Lyrics Projection #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Copyright (c) 2008-2015 OpenLP Developers #
|
||||
# --------------------------------------------------------------------------- #
|
||||
# This program is free software; you can redistribute it and/or modify it #
|
||||
# under the terms of the GNU General Public License as published by the Free #
|
||||
# Software Foundation; version 2 of the License. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT #
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
|
||||
# more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License along #
|
||||
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||
###############################################################################
|
||||
"""
|
||||
Package to test the openlp.core.lib.projector.pjlink class command routing.
|
||||
"""
|
||||
|
||||
from unittest import TestCase
|
||||
from unittest.mock import patch, MagicMock
|
||||
|
||||
import openlp.core.lib.projector.pjlink
|
||||
from openlp.core.lib.projector.pjlink import PJLink
|
||||
from openlp.core.lib.projector.constants import PJLINK_ERRORS, \
|
||||
E_AUTHENTICATION, E_PARAMETER, E_PROJECTOR, E_UNAVAILABLE, E_UNDEFINED
|
||||
|
||||
'''
|
||||
from openlp.core.lib.projector.constants import ERROR_STRING, PJLINK_ERST_DATA, PJLINK_ERST_STATUS, \
|
||||
PJLINK_POWR_STATUS, PJLINK_VALID_CMD, E_WARN, E_ERROR, S_OFF, S_STANDBY, S_ON
|
||||
'''
|
||||
from tests.resources.projector.data import TEST_PIN
|
||||
|
||||
pjlink_test = PJLink(name='test', ip='127.0.0.1', pin=TEST_PIN, no_poll=True)
|
||||
|
||||
|
||||
class TestPJLinkRouting(TestCase):
|
||||
"""
|
||||
Tests for the PJLink module command routing
|
||||
"""
|
||||
@patch.object(openlp.core.lib.projector.pjlink, 'log')
|
||||
def test_process_command_call_clss(self, mock_log):
|
||||
"""
|
||||
Test process_command calls proper function
|
||||
"""
|
||||
# GIVEN: Test object
|
||||
pjlink = pjlink_test
|
||||
log_text = '(127.0.0.1) Calling function for CLSS'
|
||||
mock_log.reset_mock()
|
||||
mock_process_clss = MagicMock()
|
||||
pjlink.pjlink_functions['CLSS'] = mock_process_clss
|
||||
|
||||
# WHEN: process_command is called with valid function and data
|
||||
pjlink.process_command(cmd='CLSS', data='1')
|
||||
|
||||
# THEN: Process method should have been called properly
|
||||
mock_log.debug.assert_called_with(log_text)
|
||||
mock_process_clss.assert_called_with('1')
|
||||
|
||||
@patch.object(pjlink_test, 'change_status')
|
||||
@patch.object(openlp.core.lib.projector.pjlink, 'log')
|
||||
def test_process_command_err1(self, mock_log, mock_change_status):
|
||||
"""
|
||||
Test ERR1 - Undefined projector function
|
||||
"""
|
||||
# GIVEN: Test object
|
||||
pjlink = pjlink_test
|
||||
log_text = '(127.0.0.1) Projector returned error "ERR1"'
|
||||
mock_change_status.reset_mock()
|
||||
mock_log.reset_mock()
|
||||
|
||||
# WHEN: process_command called with ERR1
|
||||
pjlink.process_command(cmd='CLSS', data=PJLINK_ERRORS[E_UNDEFINED])
|
||||
|
||||
# THEN: Error should be logged and status_change should be called
|
||||
mock_change_status.assert_called_once_with(E_UNDEFINED, 'Undefined Command: "CLSS"')
|
||||
mock_log.error.assert_called_with(log_text)
|
||||
|
||||
@patch.object(pjlink_test, 'change_status')
|
||||
@patch.object(openlp.core.lib.projector.pjlink, 'log')
|
||||
def test_process_command_err2(self, mock_log, mock_change_status):
|
||||
"""
|
||||
Test ERR2 - Parameter Error
|
||||
"""
|
||||
# GIVEN: Test object
|
||||
pjlink = pjlink_test
|
||||
log_text = '(127.0.0.1) Projector returned error "ERR2"'
|
||||
mock_change_status.reset_mock()
|
||||
mock_log.reset_mock()
|
||||
|
||||
# WHEN: process_command called with ERR2
|
||||
pjlink.process_command(cmd='CLSS', data=PJLINK_ERRORS[E_PARAMETER])
|
||||
|
||||
# THEN: Error should be logged and status_change should be called
|
||||
mock_change_status.assert_called_once_with(E_PARAMETER)
|
||||
mock_log.error.assert_called_with(log_text)
|
||||
|
||||
@patch.object(pjlink_test, 'change_status')
|
||||
@patch.object(openlp.core.lib.projector.pjlink, 'log')
|
||||
def test_process_command_err3(self, mock_log, mock_change_status):
|
||||
"""
|
||||
Test ERR3 - Unavailable error
|
||||
"""
|
||||
# GIVEN: Test object
|
||||
pjlink = pjlink_test
|
||||
log_text = '(127.0.0.1) Projector returned error "ERR3"'
|
||||
mock_change_status.reset_mock()
|
||||
mock_log.reset_mock()
|
||||
|
||||
# WHEN: process_command called with ERR3
|
||||
pjlink.process_command(cmd='CLSS', data=PJLINK_ERRORS[E_UNAVAILABLE])
|
||||
|
||||
# THEN: Error should be logged and status_change should be called
|
||||
mock_change_status.assert_called_once_with(E_UNAVAILABLE)
|
||||
mock_log.error.assert_called_with(log_text)
|
||||
|
||||
@patch.object(pjlink_test, 'change_status')
|
||||
@patch.object(openlp.core.lib.projector.pjlink, 'log')
|
||||
def test_process_command_err4(self, mock_log, mock_change_status):
|
||||
"""
|
||||
Test ERR3 - Unavailable error
|
||||
"""
|
||||
# GIVEN: Test object
|
||||
pjlink = pjlink_test
|
||||
log_text = '(127.0.0.1) Projector returned error "ERR4"'
|
||||
mock_change_status.reset_mock()
|
||||
mock_log.reset_mock()
|
||||
|
||||
# WHEN: process_command called with ERR3
|
||||
pjlink.process_command(cmd='CLSS', data=PJLINK_ERRORS[E_PROJECTOR])
|
||||
|
||||
# THEN: Error should be logged and status_change should be called
|
||||
mock_change_status.assert_called_once_with(E_PROJECTOR)
|
||||
mock_log.error.assert_called_with(log_text)
|
||||
|
||||
@patch.object(pjlink_test, 'projectorAuthentication')
|
||||
@patch.object(pjlink_test, 'change_status')
|
||||
@patch.object(pjlink_test, 'disconnect_from_host')
|
||||
@patch.object(openlp.core.lib.projector.pjlink, 'log')
|
||||
def test_process_command_erra(self, mock_log, mock_disconnect, mock_change_status, mock_err_authenticate):
|
||||
"""
|
||||
Test ERRA - Authentication Error
|
||||
"""
|
||||
# GIVEN: Test object
|
||||
pjlink = pjlink_test
|
||||
log_text = '(127.0.0.1) Projector returned error "ERRA"'
|
||||
mock_change_status.reset_mock()
|
||||
mock_log.reset_mock()
|
||||
|
||||
# WHEN: process_command called with ERRA
|
||||
pjlink.process_command(cmd='CLSS', data=PJLINK_ERRORS[E_AUTHENTICATION])
|
||||
|
||||
# THEN: Error should be logged and several methods should be called
|
||||
self.assertTrue(mock_disconnect.called, 'disconnect_from_host should have been called')
|
||||
mock_change_status.assert_called_once_with(E_AUTHENTICATION)
|
||||
mock_log.error.assert_called_with(log_text)
|
||||
|
||||
@patch.object(openlp.core.lib.projector.pjlink, 'log')
|
||||
def test_process_command_future(self, mock_log):
|
||||
"""
|
||||
Test command valid but no method to process yet
|
||||
"""
|
||||
# GIVEN: Test object
|
||||
pjlink = pjlink_test
|
||||
log_text = "(127.0.0.1) Unable to process command='CLSS' (Future option)"
|
||||
mock_log.reset_mock()
|
||||
# Remove a valid command so we can test valid command but not available yet
|
||||
pjlink.pjlink_functions.pop('CLSS')
|
||||
|
||||
# WHEN: process_command called with an unknown command
|
||||
with patch.object(pjlink, 'pjlink_functions') as mock_functions:
|
||||
pjlink.process_command(cmd='CLSS', data='DONT CARE')
|
||||
|
||||
# THEN: Error should be logged and no command called
|
||||
self.assertFalse(mock_functions.called, 'Should not have gotten to the end of the method')
|
||||
mock_log.warn.assert_called_once_with(log_text)
|
||||
|
||||
@patch.object(pjlink_test, 'pjlink_functions')
|
||||
@patch.object(openlp.core.lib.projector.pjlink, 'log')
|
||||
def test_process_command_invalid(self, mock_log, mock_functions):
|
||||
"""
|
||||
Test not a valid command
|
||||
"""
|
||||
# GIVEN: Test object
|
||||
pjlink = pjlink_test
|
||||
mock_functions.reset_mock()
|
||||
mock_log.reset_mock()
|
||||
|
||||
# WHEN: process_command called with an unknown command
|
||||
pjlink.process_command(cmd='Unknown', data='Dont Care')
|
||||
log_text = "(127.0.0.1) Ignoring command='Unknown' (Invalid/Unknown)"
|
||||
|
||||
# THEN: Error should be logged and no command called
|
||||
self.assertFalse(mock_functions.called, 'Should not have gotten to the end of the method')
|
||||
mock_log.error.assert_called_once_with(log_text)
|
||||
|
||||
@patch.object(pjlink_test, 'pjlink_functions')
|
||||
@patch.object(openlp.core.lib.projector.pjlink, 'log')
|
||||
def test_process_command_ok(self, mock_log, mock_functions):
|
||||
"""
|
||||
Test command returned success
|
||||
"""
|
||||
# GIVEN: Test object
|
||||
pjlink = pjlink_test
|
||||
mock_functions.reset_mock()
|
||||
mock_log.reset_mock()
|
||||
|
||||
# WHEN: process_command called with an unknown command
|
||||
pjlink.process_command(cmd='CLSS', data='OK')
|
||||
log_text = '(127.0.0.1) Command "CLSS" returned OK'
|
||||
|
||||
# THEN: Error should be logged and no command called
|
||||
self.assertFalse(mock_functions.called, 'Should not have gotten to the end of the method')
|
||||
self.assertEqual(mock_log.debug.call_count, 2, 'log.debug() should have been called twice')
|
||||
# Although we called it twice, only the last log entry is saved
|
||||
mock_log.debug.assert_called_with(log_text)
|
@ -20,44 +20,476 @@
|
||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||
###############################################################################
|
||||
"""
|
||||
Package to test the openlp.core.lib.projector.pjlink1 package.
|
||||
Package to test the openlp.core.lib.projector.pjlink commands package.
|
||||
"""
|
||||
from unittest import TestCase
|
||||
from unittest.mock import call, patch, MagicMock
|
||||
from unittest.mock import patch, MagicMock
|
||||
|
||||
from openlp.core.lib.projector.pjlink1 import PJLink
|
||||
from openlp.core.lib.projector.constants import E_PARAMETER, ERROR_STRING, S_OFF, S_STANDBY, S_ON, \
|
||||
PJLINK_POWR_STATUS, S_CONNECTED
|
||||
import openlp.core.lib.projector.pjlink
|
||||
from openlp.core.lib.projector.pjlink import PJLink
|
||||
from openlp.core.lib.projector.constants import ERROR_STRING, PJLINK_ERST_DATA, PJLINK_ERST_STATUS, \
|
||||
PJLINK_POWR_STATUS, E_WARN, E_ERROR, S_OFF, S_STANDBY, S_ON
|
||||
|
||||
from tests.resources.projector.data import TEST_PIN, TEST_SALT, TEST_CONNECT_AUTHENTICATE, TEST_HASH
|
||||
from tests.resources.projector.data import TEST_PIN
|
||||
|
||||
pjlink_test = PJLink(name='test', ip='127.0.0.1', pin=TEST_PIN, no_poll=True)
|
||||
|
||||
# Create a list of ERST positional data so we don't have to redo the same buildup multiple times
|
||||
PJLINK_ERST_POSITIONS = []
|
||||
for pos in range(0, len(PJLINK_ERST_DATA)):
|
||||
if pos in PJLINK_ERST_DATA:
|
||||
PJLINK_ERST_POSITIONS.append(PJLINK_ERST_DATA[pos])
|
||||
|
||||
class TestPJLink(TestCase):
|
||||
|
||||
class TestPJLinkCommands(TestCase):
|
||||
"""
|
||||
Tests for the PJLink module
|
||||
"""
|
||||
@patch.object(pjlink_test, 'readyRead')
|
||||
@patch.object(pjlink_test, 'send_command')
|
||||
@patch.object(pjlink_test, 'waitForReadyRead')
|
||||
@patch('openlp.core.common.qmd5_hash')
|
||||
def test_authenticated_connection_call(self, mock_qmd5_hash, mock_waitForReadyRead, mock_send_command,
|
||||
mock_readyRead):
|
||||
def test_projector_reset_information(self):
|
||||
"""
|
||||
Ticket 92187: Fix for projector connect with PJLink authentication exception.
|
||||
Test reset_information() resets all information and stops timers
|
||||
"""
|
||||
# GIVEN: Test object and test data
|
||||
pjlink = pjlink_test
|
||||
pjlink.power = S_ON
|
||||
pjlink.pjlink_name = 'OPENLPTEST'
|
||||
pjlink.manufacturer = 'PJLINK'
|
||||
pjlink.model = '1'
|
||||
pjlink.shutter = True
|
||||
pjlink.mute = True
|
||||
pjlink.lamp = True
|
||||
pjlink.fan = True
|
||||
pjlink.source_available = True
|
||||
pjlink.other_info = 'ANOTHER TEST'
|
||||
pjlink.send_queue = True
|
||||
pjlink.send_busy = True
|
||||
pjlink.timer = MagicMock()
|
||||
pjlink.socket_timer = MagicMock()
|
||||
|
||||
# WHEN: reset_information() is called
|
||||
with patch.object(pjlink.timer, 'stop') as mock_timer:
|
||||
with patch.object(pjlink.socket_timer, 'stop') as mock_socket_timer:
|
||||
pjlink.reset_information()
|
||||
|
||||
# THEN: All information should be reset and timers stopped
|
||||
self.assertEqual(pjlink.power, S_OFF, 'Projector power should be OFF')
|
||||
self.assertIsNone(pjlink.pjlink_name, 'Projector pjlink_name should be None')
|
||||
self.assertIsNone(pjlink.manufacturer, 'Projector manufacturer should be None')
|
||||
self.assertIsNone(pjlink.model, 'Projector model should be None')
|
||||
self.assertIsNone(pjlink.shutter, 'Projector shutter should be None')
|
||||
self.assertIsNone(pjlink.mute, 'Projector shuttter should be None')
|
||||
self.assertIsNone(pjlink.lamp, 'Projector lamp should be None')
|
||||
self.assertIsNone(pjlink.fan, 'Projector fan should be None')
|
||||
self.assertIsNone(pjlink.source_available, 'Projector source_available should be None')
|
||||
self.assertIsNone(pjlink.source, 'Projector source should be None')
|
||||
self.assertIsNone(pjlink.other_info, 'Projector other_info should be None')
|
||||
self.assertEqual(pjlink.send_queue, [], 'Projector send_queue should be an empty list')
|
||||
self.assertFalse(pjlink.send_busy, 'Projector send_busy should be False')
|
||||
self.assertTrue(mock_timer.called, 'Projector timer.stop() should have been called')
|
||||
self.assertTrue(mock_socket_timer.called, 'Projector socket_timer.stop() should have been called')
|
||||
|
||||
@patch.object(pjlink_test, 'projectorUpdateIcons')
|
||||
def test_projector_process_avmt_bad_data(self, mock_UpdateIcons):
|
||||
"""
|
||||
Test avmt bad data fail
|
||||
"""
|
||||
# GIVEN: Test object
|
||||
pjlink = pjlink_test
|
||||
pjlink.shutter = True
|
||||
pjlink.mute = True
|
||||
|
||||
# WHEN: Called with an invalid setting
|
||||
pjlink.process_avmt('36')
|
||||
|
||||
# THEN: Shutter should be closed and mute should be True
|
||||
self.assertTrue(pjlink.shutter, 'Shutter should changed')
|
||||
self.assertTrue(pjlink.mute, 'Audio should not have changed')
|
||||
self.assertFalse(mock_UpdateIcons.emit.called, 'Update icons should NOT have been called')
|
||||
|
||||
@patch.object(pjlink_test, 'projectorUpdateIcons')
|
||||
def test_projector_process_avmt_closed_muted(self, mock_UpdateIcons):
|
||||
"""
|
||||
Test avmt status shutter closed and mute off
|
||||
"""
|
||||
# GIVEN: Test object
|
||||
pjlink = pjlink_test
|
||||
pjlink.shutter = False
|
||||
pjlink.mute = False
|
||||
|
||||
# WHEN: Called with setting shutter to closed and mute on
|
||||
pjlink.process_avmt('31')
|
||||
|
||||
# THEN: Shutter should be closed and mute should be True
|
||||
self.assertTrue(pjlink.shutter, 'Shutter should have been set to closed')
|
||||
self.assertTrue(pjlink.mute, 'Audio should be muted')
|
||||
self.assertTrue(mock_UpdateIcons.emit.called, 'Update icons should have been called')
|
||||
|
||||
@patch.object(pjlink_test, 'projectorUpdateIcons')
|
||||
def test_projector_process_avmt_shutter_closed(self, mock_UpdateIcons):
|
||||
"""
|
||||
Test avmt status shutter closed and audio unchanged
|
||||
"""
|
||||
# GIVEN: Test object
|
||||
pjlink = pjlink_test
|
||||
pjlink.shutter = False
|
||||
pjlink.mute = True
|
||||
|
||||
# WHEN: Called with setting shutter closed and mute off
|
||||
pjlink.process_avmt('11')
|
||||
|
||||
# THEN: Shutter should be True and mute should be False
|
||||
self.assertTrue(pjlink.shutter, 'Shutter should have been set to closed')
|
||||
self.assertTrue(pjlink.mute, 'Audio should not have changed')
|
||||
self.assertTrue(mock_UpdateIcons.emit.called, 'Update icons should have been called')
|
||||
|
||||
@patch.object(pjlink_test, 'projectorUpdateIcons')
|
||||
def test_projector_process_avmt_audio_muted(self, mock_UpdateIcons):
|
||||
"""
|
||||
Test avmt status shutter unchanged and mute on
|
||||
"""
|
||||
# GIVEN: Test object
|
||||
pjlink = pjlink_test
|
||||
pjlink.shutter = True
|
||||
pjlink.mute = False
|
||||
|
||||
# WHEN: Called with setting shutter closed and mute on
|
||||
pjlink.process_avmt('21')
|
||||
|
||||
# THEN: Shutter should be closed and mute should be True
|
||||
self.assertTrue(pjlink.shutter, 'Shutter should not have changed')
|
||||
self.assertTrue(pjlink.mute, 'Audio should be off')
|
||||
self.assertTrue(mock_UpdateIcons.emit.called, 'Update icons should have been called')
|
||||
|
||||
@patch.object(pjlink_test, 'projectorUpdateIcons')
|
||||
def test_projector_process_avmt_open_unmuted(self, mock_UpdateIcons):
|
||||
"""
|
||||
Test avmt status shutter open and mute off
|
||||
"""
|
||||
# GIVEN: Test object
|
||||
pjlink = pjlink_test
|
||||
pjlink.shutter = True
|
||||
pjlink.mute = True
|
||||
|
||||
# WHEN: Called with setting shutter to closed and mute on
|
||||
pjlink.process_avmt('30')
|
||||
|
||||
# THEN: Shutter should be closed and mute should be True
|
||||
self.assertFalse(pjlink.shutter, 'Shutter should have been set to open')
|
||||
self.assertFalse(pjlink.mute, 'Audio should be on')
|
||||
self.assertTrue(mock_UpdateIcons.emit.called, 'Update icons should have been called')
|
||||
|
||||
def test_projector_process_clss_one(self):
|
||||
"""
|
||||
Test class 1 sent from projector
|
||||
"""
|
||||
# GIVEN: Test object
|
||||
pjlink = pjlink_test
|
||||
|
||||
# WHEN: Calling check_login with authentication request:
|
||||
pjlink.check_login(data=TEST_CONNECT_AUTHENTICATE)
|
||||
# WHEN: Process class response
|
||||
pjlink.process_clss('1')
|
||||
|
||||
# THEN: Should have called qmd5_hash
|
||||
self.assertTrue(mock_qmd5_hash.called_with(TEST_SALT,
|
||||
"Connection request should have been called with TEST_SALT"))
|
||||
self.assertTrue(mock_qmd5_hash.called_with(TEST_PIN,
|
||||
"Connection request should have been called with TEST_PIN"))
|
||||
# THEN: Projector class should be set to 1
|
||||
self.assertEqual(pjlink.pjlink_class, '1',
|
||||
'Projector should have set class=1')
|
||||
|
||||
def test_projector_process_clss_two(self):
|
||||
"""
|
||||
Test class 2 sent from projector
|
||||
"""
|
||||
# GIVEN: Test object
|
||||
pjlink = pjlink_test
|
||||
|
||||
# WHEN: Process class response
|
||||
pjlink.process_clss('2')
|
||||
|
||||
# THEN: Projector class should be set to 1
|
||||
self.assertEqual(pjlink.pjlink_class, '2',
|
||||
'Projector should have set class=2')
|
||||
|
||||
def test_projector_process_clss_nonstandard_reply_optoma(self):
|
||||
"""
|
||||
Bugfix 1550891: CLSS request returns non-standard reply with Optoma projector
|
||||
"""
|
||||
# GIVEN: Test object
|
||||
pjlink = pjlink_test
|
||||
|
||||
# WHEN: Process non-standard reply
|
||||
pjlink.process_clss('Class 1')
|
||||
|
||||
# THEN: Projector class should be set with proper value
|
||||
self.assertEqual(pjlink.pjlink_class, '1',
|
||||
'Non-standard class reply should have set class=1')
|
||||
|
||||
def test_projector_process_clss_nonstandard_reply_benq(self):
|
||||
"""
|
||||
Bugfix 1550891: CLSS request returns non-standard reply with BenQ projector
|
||||
"""
|
||||
# GIVEN: Test object
|
||||
pjlink = pjlink_test
|
||||
|
||||
# WHEN: Process non-standard reply
|
||||
pjlink.process_clss('Version2')
|
||||
|
||||
# THEN: Projector class should be set with proper value
|
||||
# NOTE: At this time BenQ is Class 1, but we're trying a different value to verify
|
||||
self.assertEqual(pjlink.pjlink_class, '2',
|
||||
'Non-standard class reply should have set class=2')
|
||||
|
||||
@patch.object(openlp.core.lib.projector.pjlink, 'log')
|
||||
def test_projector_process_clss_invalid_nan(self, mock_log):
|
||||
"""
|
||||
Test CLSS reply has no class number
|
||||
"""
|
||||
# GIVEN: Test object
|
||||
pjlink = pjlink_test
|
||||
|
||||
# WHEN: Process invalid reply
|
||||
pjlink.process_clss('Z')
|
||||
log_warn_text = "(127.0.0.1) NAN clss version reply 'Z' - defaulting to class '1'"
|
||||
|
||||
# THEN: Projector class should be set with default value
|
||||
self.assertEqual(pjlink.pjlink_class, '1',
|
||||
'Non-standard class reply should have set class=1')
|
||||
mock_log.error.assert_called_once_with(log_warn_text)
|
||||
|
||||
@patch.object(openlp.core.lib.projector.pjlink, 'log')
|
||||
def test_projector_process_clss_invalid_no_version(self, mock_log):
|
||||
"""
|
||||
Test CLSS reply has no class number
|
||||
"""
|
||||
# GIVEN: Test object
|
||||
pjlink = pjlink_test
|
||||
|
||||
# WHEN: Process invalid reply
|
||||
pjlink.process_clss('Invalid')
|
||||
log_warn_text = "(127.0.0.1) No numbers found in class version reply 'Invalid' - defaulting to class '1'"
|
||||
|
||||
# THEN: Projector class should be set with default value
|
||||
self.assertEqual(pjlink.pjlink_class, '1',
|
||||
'Non-standard class reply should have set class=1')
|
||||
mock_log.error.assert_called_once_with(log_warn_text)
|
||||
|
||||
def test_projector_process_erst_all_ok(self):
|
||||
"""
|
||||
Test test_projector_process_erst_all_ok
|
||||
"""
|
||||
# GIVEN: Test object
|
||||
pjlink = pjlink_test
|
||||
chk_test = PJLINK_ERST_STATUS['OK']
|
||||
chk_param = chk_test * len(PJLINK_ERST_POSITIONS)
|
||||
|
||||
# WHEN: process_erst with no errors
|
||||
pjlink.process_erst(chk_param)
|
||||
|
||||
# THEN: PJLink instance errors should be None
|
||||
self.assertIsNone(pjlink.projector_errors, 'projector_errors should have been set to None')
|
||||
|
||||
@patch.object(openlp.core.lib.projector.pjlink, 'log')
|
||||
def test_projector_process_erst_data_invalid_length(self, mock_log):
|
||||
"""
|
||||
Test test_projector_process_erst_data_invalid_length
|
||||
"""
|
||||
# GIVEN: Test object
|
||||
pjlink = pjlink_test
|
||||
pjlink.projector_errors = None
|
||||
log_warn_text = "127.0.0.1) Invalid error status response '11111111': length != 6"
|
||||
|
||||
# WHEN: process_erst called with invalid data (too many values
|
||||
pjlink.process_erst('11111111')
|
||||
|
||||
# THEN: pjlink.projector_errors should be empty and warning logged
|
||||
self.assertIsNone(pjlink.projector_errors, 'There should be no errors')
|
||||
self.assertTrue(mock_log.warn.called, 'Warning should have been logged')
|
||||
mock_log.warn.assert_called_once_with(log_warn_text)
|
||||
|
||||
@patch.object(openlp.core.lib.projector.pjlink, 'log')
|
||||
def test_projector_process_erst_data_invalid_nan(self, mock_log):
|
||||
"""
|
||||
Test test_projector_process_erst_data_invalid_nan
|
||||
"""
|
||||
# GIVEN: Test object
|
||||
pjlink = pjlink_test
|
||||
pjlink.projector_errors = None
|
||||
log_warn_text = "(127.0.0.1) Invalid error status response '1111Z1'"
|
||||
|
||||
# WHEN: process_erst called with invalid data (too many values
|
||||
pjlink.process_erst('1111Z1')
|
||||
|
||||
# THEN: pjlink.projector_errors should be empty and warning logged
|
||||
self.assertIsNone(pjlink.projector_errors, 'There should be no errors')
|
||||
self.assertTrue(mock_log.warn.called, 'Warning should have been logged')
|
||||
mock_log.warn.assert_called_once_with(log_warn_text)
|
||||
|
||||
def test_projector_process_erst_all_warn(self):
|
||||
"""
|
||||
Test test_projector_process_erst_all_warn
|
||||
"""
|
||||
# GIVEN: Test object
|
||||
pjlink = pjlink_test
|
||||
chk_test = PJLINK_ERST_STATUS[E_WARN]
|
||||
chk_string = ERROR_STRING[E_WARN]
|
||||
chk_param = chk_test * len(PJLINK_ERST_POSITIONS)
|
||||
|
||||
# WHEN: process_erst with status set to WARN
|
||||
pjlink.process_erst(chk_param)
|
||||
|
||||
# THEN: PJLink instance errors should match chk_value
|
||||
for chk in pjlink.projector_errors:
|
||||
self.assertEqual(pjlink.projector_errors[chk], chk_string,
|
||||
"projector_errors['{chk}'] should have been set to {err}".format(chk=chk,
|
||||
err=chk_string))
|
||||
|
||||
def test_projector_process_erst_all_error(self):
|
||||
"""
|
||||
Test test_projector_process_erst_all_error
|
||||
"""
|
||||
# GIVEN: Test object
|
||||
pjlink = pjlink_test
|
||||
chk_test = PJLINK_ERST_STATUS[E_ERROR]
|
||||
chk_string = ERROR_STRING[E_ERROR]
|
||||
chk_param = chk_test * len(PJLINK_ERST_POSITIONS)
|
||||
|
||||
# WHEN: process_erst with status set to WARN
|
||||
pjlink.process_erst(chk_param)
|
||||
|
||||
# THEN: PJLink instance errors should match chk_value
|
||||
for chk in pjlink.projector_errors:
|
||||
self.assertEqual(pjlink.projector_errors[chk], chk_string,
|
||||
"projector_errors['{chk}'] should have been set to {err}".format(chk=chk,
|
||||
err=chk_string))
|
||||
|
||||
def test_projector_process_erst_warn_cover_only(self):
|
||||
"""
|
||||
Test test_projector_process_erst_warn_cover_only
|
||||
"""
|
||||
# GIVEN: Test object
|
||||
pjlink = pjlink_test
|
||||
chk_test = PJLINK_ERST_STATUS[E_WARN]
|
||||
chk_string = ERROR_STRING[E_WARN]
|
||||
pos = PJLINK_ERST_DATA['COVER']
|
||||
build_chk = []
|
||||
for check in range(0, len(PJLINK_ERST_POSITIONS)):
|
||||
if check == pos:
|
||||
build_chk.append(chk_test)
|
||||
else:
|
||||
build_chk.append(PJLINK_ERST_STATUS['OK'])
|
||||
chk_param = ''.join(build_chk)
|
||||
|
||||
# WHEN: process_erst with cover only set to WARN and all others set to OK
|
||||
pjlink.process_erst(chk_param)
|
||||
|
||||
# THEN: Only COVER should have an error
|
||||
self.assertEqual(len(pjlink.projector_errors), 1, 'projector_errors should only have 1 error')
|
||||
self.assertTrue(('Cover' in pjlink.projector_errors), 'projector_errors should have an error for "Cover"')
|
||||
self.assertEqual(pjlink.projector_errors['Cover'],
|
||||
chk_string,
|
||||
'projector_errors["Cover"] should have error "{err}"'.format(err=chk_string))
|
||||
|
||||
def test_projector_process_inpt(self):
|
||||
"""
|
||||
Test input source status shows current input
|
||||
"""
|
||||
# GIVEN: Test object
|
||||
pjlink = pjlink_test
|
||||
pjlink.source = '0'
|
||||
|
||||
# WHEN: Called with input source
|
||||
pjlink.process_inpt('1')
|
||||
|
||||
# THEN: Input selected should reflect current input
|
||||
self.assertEqual(pjlink.source, '1', 'Input source should be set to "1"')
|
||||
|
||||
@patch.object(pjlink_test, 'projectorReceivedData')
|
||||
def test_projector_process_lamp_single(self, mock_projectorReceivedData):
|
||||
"""
|
||||
Test status lamp on/off and hours
|
||||
"""
|
||||
# GIVEN: Test object
|
||||
pjlink = pjlink_test
|
||||
|
||||
# WHEN: Call process_command with lamp data
|
||||
pjlink.process_command('LAMP', '22222 1')
|
||||
|
||||
# THEN: Lamp should have been set with status=ON and hours=22222
|
||||
self.assertEqual(pjlink.lamp[0]['On'], True,
|
||||
'Lamp power status should have been set to TRUE')
|
||||
self.assertEqual(pjlink.lamp[0]['Hours'], 22222,
|
||||
'Lamp hours should have been set to 22222')
|
||||
|
||||
@patch.object(pjlink_test, 'projectorReceivedData')
|
||||
def test_projector_process_lamp_multiple(self, mock_projectorReceivedData):
|
||||
"""
|
||||
Test status multiple lamp on/off and hours
|
||||
"""
|
||||
# GIVEN: Test object
|
||||
pjlink = pjlink_test
|
||||
|
||||
# WHEN: Call process_command with lamp data
|
||||
pjlink.process_command('LAMP', '11111 1 22222 0 33333 1')
|
||||
|
||||
# THEN: Lamp should have been set with proper lamp status
|
||||
self.assertEqual(len(pjlink.lamp), 3,
|
||||
'Projector should have 3 lamps specified')
|
||||
self.assertEqual(pjlink.lamp[0]['On'], True,
|
||||
'Lamp 1 power status should have been set to TRUE')
|
||||
self.assertEqual(pjlink.lamp[0]['Hours'], 11111,
|
||||
'Lamp 1 hours should have been set to 11111')
|
||||
self.assertEqual(pjlink.lamp[1]['On'], False,
|
||||
'Lamp 2 power status should have been set to FALSE')
|
||||
self.assertEqual(pjlink.lamp[1]['Hours'], 22222,
|
||||
'Lamp 2 hours should have been set to 22222')
|
||||
self.assertEqual(pjlink.lamp[2]['On'], True,
|
||||
'Lamp 3 power status should have been set to TRUE')
|
||||
self.assertEqual(pjlink.lamp[2]['Hours'], 33333,
|
||||
'Lamp 3 hours should have been set to 33333')
|
||||
|
||||
@patch.object(pjlink_test, 'projectorReceivedData')
|
||||
@patch.object(pjlink_test, 'projectorUpdateIcons')
|
||||
@patch.object(pjlink_test, 'send_command')
|
||||
@patch.object(pjlink_test, 'change_status')
|
||||
def test_projector_process_powr_on(self,
|
||||
mock_change_status,
|
||||
mock_send_command,
|
||||
mock_UpdateIcons,
|
||||
mock_ReceivedData):
|
||||
"""
|
||||
Test status power to ON
|
||||
"""
|
||||
# GIVEN: Test object and preset
|
||||
pjlink = pjlink_test
|
||||
pjlink.power = S_STANDBY
|
||||
|
||||
# WHEN: Call process_command with turn power on command
|
||||
pjlink.process_command('POWR', PJLINK_POWR_STATUS[S_ON])
|
||||
|
||||
# THEN: Power should be set to ON
|
||||
self.assertEqual(pjlink.power, S_ON, 'Power should have been set to ON')
|
||||
mock_send_command.assert_called_once_with('INST')
|
||||
self.assertEqual(mock_UpdateIcons.emit.called, True, 'projectorUpdateIcons should have been called')
|
||||
|
||||
@patch.object(pjlink_test, 'projectorReceivedData')
|
||||
@patch.object(pjlink_test, 'projectorUpdateIcons')
|
||||
@patch.object(pjlink_test, 'send_command')
|
||||
@patch.object(pjlink_test, 'change_status')
|
||||
def test_projector_process_powr_off(self,
|
||||
mock_change_status,
|
||||
mock_send_command,
|
||||
mock_UpdateIcons,
|
||||
mock_ReceivedData):
|
||||
"""
|
||||
Test status power to STANDBY
|
||||
"""
|
||||
# GIVEN: Test object and preset
|
||||
pjlink = pjlink_test
|
||||
pjlink.power = S_ON
|
||||
|
||||
# WHEN: Call process_command with turn power on command
|
||||
pjlink.process_command('POWR', PJLINK_POWR_STATUS[S_STANDBY])
|
||||
|
||||
# THEN: Power should be set to STANDBY
|
||||
self.assertEqual(pjlink.power, S_STANDBY, 'Power should have been set to STANDBY')
|
||||
self.assertEqual(mock_send_command.called, False, 'send_command should not have been called')
|
||||
self.assertEqual(mock_UpdateIcons.emit.called, True, 'projectorUpdateIcons should have been called')
|
||||
|
||||
def test_projector_process_rfil_save(self):
|
||||
"""
|
||||
@ -150,419 +582,3 @@ class TestPJLink(TestCase):
|
||||
# THEN: Serial number should be set
|
||||
self.assertNotEquals(pjlink.serial_no, test_number,
|
||||
'Projector serial number should NOT have been set')
|
||||
|
||||
def test_projector_clss_one(self):
|
||||
"""
|
||||
Test class 1 sent from projector
|
||||
"""
|
||||
# GIVEN: Test object
|
||||
pjlink = pjlink_test
|
||||
|
||||
# WHEN: Process class response
|
||||
pjlink.process_clss('1')
|
||||
|
||||
# THEN: Projector class should be set to 1
|
||||
self.assertEqual(pjlink.pjlink_class, '1',
|
||||
'Projector should have returned class=1')
|
||||
|
||||
def test_projector_clss_two(self):
|
||||
"""
|
||||
Test class 2 sent from projector
|
||||
"""
|
||||
# GIVEN: Test object
|
||||
pjlink = pjlink_test
|
||||
|
||||
# WHEN: Process class response
|
||||
pjlink.process_clss('2')
|
||||
|
||||
# THEN: Projector class should be set to 1
|
||||
self.assertEqual(pjlink.pjlink_class, '2',
|
||||
'Projector should have returned class=2')
|
||||
|
||||
def test_bug_1550891_non_standard_class_reply(self):
|
||||
"""
|
||||
Bugfix 1550891: CLSS request returns non-standard reply
|
||||
"""
|
||||
# GIVEN: Test object
|
||||
pjlink = pjlink_test
|
||||
|
||||
# WHEN: Process non-standard reply
|
||||
pjlink.process_clss('Class 1')
|
||||
|
||||
# THEN: Projector class should be set with proper value
|
||||
self.assertEqual(pjlink.pjlink_class, '1',
|
||||
'Non-standard class reply should have set class=1')
|
||||
|
||||
@patch.object(pjlink_test, 'change_status')
|
||||
def test_status_change(self, mock_change_status):
|
||||
"""
|
||||
Test process_command call with ERR2 (Parameter) status
|
||||
"""
|
||||
# GIVEN: Test object
|
||||
pjlink = pjlink_test
|
||||
|
||||
# WHEN: process_command is called with "ERR2" status from projector
|
||||
pjlink.process_command('POWR', 'ERR2')
|
||||
|
||||
# THEN: change_status should have called change_status with E_UNDEFINED
|
||||
# as first parameter
|
||||
mock_change_status.called_with(E_PARAMETER,
|
||||
'change_status should have been called with "{}"'.format(
|
||||
ERROR_STRING[E_PARAMETER]))
|
||||
|
||||
@patch.object(pjlink_test, 'process_inpt')
|
||||
def test_projector_return_ok(self, mock_process_inpt):
|
||||
"""
|
||||
Test projector calls process_inpt command when process_command is called with INPT option
|
||||
"""
|
||||
# GIVEN: Test object
|
||||
pjlink = pjlink_test
|
||||
|
||||
# WHEN: process_command is called with INST command and 31 input:
|
||||
pjlink.process_command('INPT', '31')
|
||||
|
||||
# THEN: process_inpt method should have been called with 31
|
||||
mock_process_inpt.called_with('31',
|
||||
"process_inpt should have been called with 31")
|
||||
|
||||
@patch.object(pjlink_test, 'projectorReceivedData')
|
||||
def test_projector_process_lamp(self, mock_projectorReceivedData):
|
||||
"""
|
||||
Test status lamp on/off and hours
|
||||
"""
|
||||
# GIVEN: Test object
|
||||
pjlink = pjlink_test
|
||||
|
||||
# WHEN: Call process_command with lamp data
|
||||
pjlink.process_command('LAMP', '22222 1')
|
||||
|
||||
# THEN: Lamp should have been set with status=ON and hours=22222
|
||||
self.assertEqual(pjlink.lamp[0]['On'], True,
|
||||
'Lamp power status should have been set to TRUE')
|
||||
self.assertEqual(pjlink.lamp[0]['Hours'], 22222,
|
||||
'Lamp hours should have been set to 22222')
|
||||
|
||||
@patch.object(pjlink_test, 'projectorReceivedData')
|
||||
def test_projector_process_multiple_lamp(self, mock_projectorReceivedData):
|
||||
"""
|
||||
Test status multiple lamp on/off and hours
|
||||
"""
|
||||
# GIVEN: Test object
|
||||
pjlink = pjlink_test
|
||||
|
||||
# WHEN: Call process_command with lamp data
|
||||
pjlink.process_command('LAMP', '11111 1 22222 0 33333 1')
|
||||
|
||||
# THEN: Lamp should have been set with proper lamp status
|
||||
self.assertEqual(len(pjlink.lamp), 3,
|
||||
'Projector should have 3 lamps specified')
|
||||
self.assertEqual(pjlink.lamp[0]['On'], True,
|
||||
'Lamp 1 power status should have been set to TRUE')
|
||||
self.assertEqual(pjlink.lamp[0]['Hours'], 11111,
|
||||
'Lamp 1 hours should have been set to 11111')
|
||||
self.assertEqual(pjlink.lamp[1]['On'], False,
|
||||
'Lamp 2 power status should have been set to FALSE')
|
||||
self.assertEqual(pjlink.lamp[1]['Hours'], 22222,
|
||||
'Lamp 2 hours should have been set to 22222')
|
||||
self.assertEqual(pjlink.lamp[2]['On'], True,
|
||||
'Lamp 3 power status should have been set to TRUE')
|
||||
self.assertEqual(pjlink.lamp[2]['Hours'], 33333,
|
||||
'Lamp 3 hours should have been set to 33333')
|
||||
|
||||
@patch.object(pjlink_test, 'projectorReceivedData')
|
||||
@patch.object(pjlink_test, 'projectorUpdateIcons')
|
||||
@patch.object(pjlink_test, 'send_command')
|
||||
@patch.object(pjlink_test, 'change_status')
|
||||
def test_projector_process_power_on(self, mock_change_status,
|
||||
mock_send_command,
|
||||
mock_UpdateIcons,
|
||||
mock_ReceivedData):
|
||||
"""
|
||||
Test status power to ON
|
||||
"""
|
||||
# GIVEN: Test object and preset
|
||||
pjlink = pjlink_test
|
||||
pjlink.power = S_STANDBY
|
||||
|
||||
# WHEN: Call process_command with turn power on command
|
||||
pjlink.process_command('POWR', PJLINK_POWR_STATUS[S_ON])
|
||||
|
||||
# THEN: Power should be set to ON
|
||||
self.assertEqual(pjlink.power, S_ON, 'Power should have been set to ON')
|
||||
mock_send_command.assert_called_once_with('INST')
|
||||
self.assertEqual(mock_UpdateIcons.emit.called, True, 'projectorUpdateIcons should have been called')
|
||||
|
||||
@patch.object(pjlink_test, 'projectorReceivedData')
|
||||
@patch.object(pjlink_test, 'projectorUpdateIcons')
|
||||
@patch.object(pjlink_test, 'send_command')
|
||||
@patch.object(pjlink_test, 'change_status')
|
||||
def test_projector_process_power_off(self, mock_change_status,
|
||||
mock_send_command,
|
||||
mock_UpdateIcons,
|
||||
mock_ReceivedData):
|
||||
"""
|
||||
Test status power to STANDBY
|
||||
"""
|
||||
# GIVEN: Test object and preset
|
||||
pjlink = pjlink_test
|
||||
pjlink.power = S_ON
|
||||
|
||||
# WHEN: Call process_command with turn power on command
|
||||
pjlink.process_command('POWR', PJLINK_POWR_STATUS[S_STANDBY])
|
||||
|
||||
# THEN: Power should be set to STANDBY
|
||||
self.assertEqual(pjlink.power, S_STANDBY, 'Power should have been set to STANDBY')
|
||||
self.assertEqual(mock_send_command.called, False, 'send_command should not have been called')
|
||||
self.assertEqual(mock_UpdateIcons.emit.called, True, 'projectorUpdateIcons should have been called')
|
||||
|
||||
@patch.object(pjlink_test, 'projectorUpdateIcons')
|
||||
def test_projector_process_avmt_closed_unmuted(self, mock_projectorReceivedData):
|
||||
"""
|
||||
Test avmt status shutter closed and audio muted
|
||||
"""
|
||||
# GIVEN: Test object
|
||||
pjlink = pjlink_test
|
||||
pjlink.shutter = False
|
||||
pjlink.mute = True
|
||||
|
||||
# WHEN: Called with setting shutter closed and mute off
|
||||
pjlink.process_avmt('11')
|
||||
|
||||
# THEN: Shutter should be True and mute should be False
|
||||
self.assertTrue(pjlink.shutter, 'Shutter should have been set to closed')
|
||||
self.assertFalse(pjlink.mute, 'Audio should be off')
|
||||
|
||||
@patch.object(pjlink_test, 'projectorUpdateIcons')
|
||||
def test_projector_process_avmt_open_muted(self, mock_projectorReceivedData):
|
||||
"""
|
||||
Test avmt status shutter open and mute on
|
||||
"""
|
||||
# GIVEN: Test object
|
||||
pjlink = pjlink_test
|
||||
pjlink.shutter = True
|
||||
pjlink.mute = False
|
||||
|
||||
# WHEN: Called with setting shutter closed and mute on
|
||||
pjlink.process_avmt('21')
|
||||
|
||||
# THEN: Shutter should be closed and mute should be True
|
||||
self.assertFalse(pjlink.shutter, 'Shutter should have been set to closed')
|
||||
self.assertTrue(pjlink.mute, 'Audio should be off')
|
||||
|
||||
@patch.object(pjlink_test, 'projectorUpdateIcons')
|
||||
def test_projector_process_avmt_open_unmuted(self, mock_projectorReceivedData):
|
||||
"""
|
||||
Test avmt status shutter open and mute off off
|
||||
"""
|
||||
# GIVEN: Test object
|
||||
pjlink = pjlink_test
|
||||
pjlink.shutter = True
|
||||
pjlink.mute = True
|
||||
|
||||
# WHEN: Called with setting shutter to closed and mute on
|
||||
pjlink.process_avmt('30')
|
||||
|
||||
# THEN: Shutter should be closed and mute should be True
|
||||
self.assertFalse(pjlink.shutter, 'Shutter should have been set to open')
|
||||
self.assertFalse(pjlink.mute, 'Audio should be on')
|
||||
|
||||
@patch.object(pjlink_test, 'projectorUpdateIcons')
|
||||
def test_projector_process_avmt_closed_muted(self, mock_projectorReceivedData):
|
||||
"""
|
||||
Test avmt status shutter closed and mute off
|
||||
"""
|
||||
# GIVEN: Test object
|
||||
pjlink = pjlink_test
|
||||
pjlink.shutter = False
|
||||
pjlink.mute = False
|
||||
|
||||
# WHEN: Called with setting shutter to closed and mute on
|
||||
pjlink.process_avmt('31')
|
||||
|
||||
# THEN: Shutter should be closed and mute should be True
|
||||
self.assertTrue(pjlink.shutter, 'Shutter should have been set to closed')
|
||||
self.assertTrue(pjlink.mute, 'Audio should be on')
|
||||
|
||||
def test_projector_process_input(self):
|
||||
"""
|
||||
Test input source status shows current input
|
||||
"""
|
||||
# GIVEN: Test object
|
||||
pjlink = pjlink_test
|
||||
pjlink.source = '0'
|
||||
|
||||
# WHEN: Called with input source
|
||||
pjlink.process_inpt('1')
|
||||
|
||||
# THEN: Input selected should reflect current input
|
||||
self.assertEqual(pjlink.source, '1', 'Input source should be set to "1"')
|
||||
|
||||
def test_projector_reset_information(self):
|
||||
"""
|
||||
Test reset_information() resets all information and stops timers
|
||||
"""
|
||||
# GIVEN: Test object and test data
|
||||
pjlink = pjlink_test
|
||||
pjlink.power = S_ON
|
||||
pjlink.pjlink_name = 'OPENLPTEST'
|
||||
pjlink.manufacturer = 'PJLINK'
|
||||
pjlink.model = '1'
|
||||
pjlink.shutter = True
|
||||
pjlink.mute = True
|
||||
pjlink.lamp = True
|
||||
pjlink.fan = True
|
||||
pjlink.source_available = True
|
||||
pjlink.other_info = 'ANOTHER TEST'
|
||||
pjlink.send_queue = True
|
||||
pjlink.send_busy = True
|
||||
pjlink.timer = MagicMock()
|
||||
pjlink.socket_timer = MagicMock()
|
||||
|
||||
# WHEN: reset_information() is called
|
||||
with patch.object(pjlink.timer, 'stop') as mock_timer:
|
||||
with patch.object(pjlink.socket_timer, 'stop') as mock_socket_timer:
|
||||
pjlink.reset_information()
|
||||
|
||||
# THEN: All information should be reset and timers stopped
|
||||
self.assertEqual(pjlink.power, S_OFF, 'Projector power should be OFF')
|
||||
self.assertIsNone(pjlink.pjlink_name, 'Projector pjlink_name should be None')
|
||||
self.assertIsNone(pjlink.manufacturer, 'Projector manufacturer should be None')
|
||||
self.assertIsNone(pjlink.model, 'Projector model should be None')
|
||||
self.assertIsNone(pjlink.shutter, 'Projector shutter should be None')
|
||||
self.assertIsNone(pjlink.mute, 'Projector shuttter should be None')
|
||||
self.assertIsNone(pjlink.lamp, 'Projector lamp should be None')
|
||||
self.assertIsNone(pjlink.fan, 'Projector fan should be None')
|
||||
self.assertIsNone(pjlink.source_available, 'Projector source_available should be None')
|
||||
self.assertIsNone(pjlink.source, 'Projector source should be None')
|
||||
self.assertIsNone(pjlink.other_info, 'Projector other_info should be None')
|
||||
self.assertEqual(pjlink.send_queue, [], 'Projector send_queue should be an empty list')
|
||||
self.assertFalse(pjlink.send_busy, 'Projector send_busy should be False')
|
||||
self.assertTrue(mock_timer.called, 'Projector timer.stop() should have been called')
|
||||
self.assertTrue(mock_socket_timer.called, 'Projector socket_timer.stop() should have been called')
|
||||
|
||||
@patch.object(pjlink_test, 'send_command')
|
||||
@patch.object(pjlink_test, 'waitForReadyRead')
|
||||
@patch.object(pjlink_test, 'projectorAuthentication')
|
||||
@patch.object(pjlink_test, 'timer')
|
||||
@patch.object(pjlink_test, 'socket_timer')
|
||||
def test_bug_1593882_no_pin_authenticated_connection(self, mock_socket_timer,
|
||||
mock_timer,
|
||||
mock_authentication,
|
||||
mock_ready_read,
|
||||
mock_send_command):
|
||||
"""
|
||||
Test bug 1593882 no pin and authenticated request exception
|
||||
"""
|
||||
# GIVEN: Test object and mocks
|
||||
pjlink = pjlink_test
|
||||
pjlink.pin = None
|
||||
mock_ready_read.return_value = True
|
||||
|
||||
# WHEN: call with authentication request and pin not set
|
||||
pjlink.check_login(data=TEST_CONNECT_AUTHENTICATE)
|
||||
|
||||
# THEN: 'No Authentication' signal should have been sent
|
||||
mock_authentication.emit.assert_called_with(pjlink.ip)
|
||||
|
||||
@patch.object(pjlink_test, 'waitForReadyRead')
|
||||
@patch.object(pjlink_test, 'state')
|
||||
@patch.object(pjlink_test, '_send_command')
|
||||
@patch.object(pjlink_test, 'timer')
|
||||
@patch.object(pjlink_test, 'socket_timer')
|
||||
def test_bug_1593883_pjlink_authentication(self, mock_socket_timer,
|
||||
mock_timer,
|
||||
mock_send_command,
|
||||
mock_state,
|
||||
mock_waitForReadyRead):
|
||||
"""
|
||||
Test bugfix 1593883 pjlink authentication
|
||||
"""
|
||||
# GIVEN: Test object and data
|
||||
pjlink = pjlink_test
|
||||
pjlink.pin = TEST_PIN
|
||||
mock_state.return_value = pjlink.ConnectedState
|
||||
mock_waitForReadyRead.return_value = True
|
||||
|
||||
# WHEN: Athenticated connection is called
|
||||
pjlink.check_login(data=TEST_CONNECT_AUTHENTICATE)
|
||||
|
||||
# THEN: send_command should have the proper authentication
|
||||
self.assertEqual("{test}".format(test=mock_send_command.call_args),
|
||||
"call(data='{hash}%1CLSS ?\\r')".format(hash=TEST_HASH))
|
||||
|
||||
@patch.object(pjlink_test, 'disconnect_from_host')
|
||||
def socket_abort_test(self, mock_disconnect):
|
||||
"""
|
||||
Test PJLink.socket_abort calls disconnect_from_host
|
||||
"""
|
||||
# GIVEN: Test object
|
||||
pjlink = pjlink_test
|
||||
|
||||
# WHEN: Calling socket_abort
|
||||
pjlink.socket_abort()
|
||||
|
||||
# THEN: disconnect_from_host should be called
|
||||
self.assertTrue(mock_disconnect.called, 'Should have called disconnect_from_host')
|
||||
|
||||
def poll_loop_not_connected_test(self):
|
||||
"""
|
||||
Test PJLink.poll_loop not connected return
|
||||
"""
|
||||
# GIVEN: Test object and mocks
|
||||
pjlink = pjlink_test
|
||||
pjlink.state = MagicMock()
|
||||
pjlink.timer = MagicMock()
|
||||
pjlink.state.return_value = False
|
||||
pjlink.ConnectedState = True
|
||||
|
||||
# WHEN: PJLink.poll_loop called
|
||||
pjlink.poll_loop()
|
||||
|
||||
# THEN: poll_loop should exit without calling any other method
|
||||
self.assertFalse(pjlink.timer.called, 'Should have returned without calling any other method')
|
||||
|
||||
@patch.object(pjlink_test, 'send_command')
|
||||
def poll_loop_start_test(self, mock_send_command):
|
||||
"""
|
||||
Test PJLink.poll_loop makes correct calls
|
||||
"""
|
||||
# GIVEN: test object and test data
|
||||
pjlink = pjlink_test
|
||||
pjlink.state = MagicMock()
|
||||
pjlink.timer = MagicMock()
|
||||
pjlink.timer.interval = MagicMock()
|
||||
pjlink.timer.setInterval = MagicMock()
|
||||
pjlink.timer.start = MagicMock()
|
||||
pjlink.poll_time = 20
|
||||
pjlink.power = S_ON
|
||||
pjlink.source_available = None
|
||||
pjlink.other_info = None
|
||||
pjlink.manufacturer = None
|
||||
pjlink.model = None
|
||||
pjlink.pjlink_name = None
|
||||
pjlink.ConnectedState = S_CONNECTED
|
||||
pjlink.timer.interval.return_value = 10
|
||||
pjlink.state.return_value = S_CONNECTED
|
||||
call_list = [
|
||||
call('POWR', queue=True),
|
||||
call('ERST', queue=True),
|
||||
call('LAMP', queue=True),
|
||||
call('AVMT', queue=True),
|
||||
call('INPT', queue=True),
|
||||
call('INST', queue=True),
|
||||
call('INFO', queue=True),
|
||||
call('INF1', queue=True),
|
||||
call('INF2', queue=True),
|
||||
call('NAME', queue=True),
|
||||
]
|
||||
|
||||
# WHEN: PJLink.poll_loop is called
|
||||
pjlink.poll_loop()
|
||||
|
||||
# THEN: proper calls were made to retrieve projector data
|
||||
# First, call to update the timer with the next interval
|
||||
self.assertTrue(pjlink.timer.setInterval.called, 'Should have updated the timer')
|
||||
# Next, should have called the timer to start
|
||||
self.assertTrue(pjlink.timer.start.called, 'Should have started the timer')
|
||||
# Finally, should have called send_command with a list of projetctor status checks
|
||||
mock_send_command.assert_has_calls(call_list, 'Should have queued projector queries')
|
@ -22,6 +22,7 @@
|
||||
"""
|
||||
Package to test the openlp.core.ui.themeform package.
|
||||
"""
|
||||
from pathlib import Path
|
||||
from unittest import TestCase
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
@ -45,7 +46,7 @@ class TestThemeManager(TestCase):
|
||||
self.instance.theme = MagicMock()
|
||||
|
||||
# WHEN: `on_image_path_edit_path_changed` is clicked
|
||||
self.instance.on_image_path_edit_path_changed('/new/pat.h')
|
||||
self.instance.on_image_path_edit_path_changed(Path('/', 'new', 'pat.h'))
|
||||
|
||||
# THEN: The theme background file should be set and `set_background_page_values` should have been called
|
||||
self.assertEqual(self.instance.theme.background_filename, '/new/pat.h')
|
||||
|
188
tests/functional/openlp_core_ui_lib/test_filedialog.py
Executable file
188
tests/functional/openlp_core_ui_lib/test_filedialog.py
Executable file
@ -0,0 +1,188 @@
|
||||
import os
|
||||
from unittest import TestCase
|
||||
from unittest.mock import patch
|
||||
from pathlib import Path
|
||||
|
||||
from PyQt5 import QtWidgets
|
||||
|
||||
from openlp.core.ui.lib.filedialog import FileDialog
|
||||
|
||||
|
||||
class TestFileDialogPatches(TestCase):
|
||||
"""
|
||||
Tests for the :mod:`openlp.core.ui.lib.filedialogpatches` module
|
||||
"""
|
||||
|
||||
def test_file_dialog(self):
|
||||
"""
|
||||
Test that the :class:`FileDialog` instantiates correctly
|
||||
"""
|
||||
# GIVEN: The FileDialog class
|
||||
# WHEN: Creating an instance
|
||||
instance = FileDialog()
|
||||
|
||||
# THEN: The instance should be an instance of QFileDialog
|
||||
self.assertIsInstance(instance, QtWidgets.QFileDialog)
|
||||
|
||||
def test_get_existing_directory_user_abort(self):
|
||||
"""
|
||||
Test that `getExistingDirectory` handles the case when the user cancels the dialog
|
||||
"""
|
||||
# GIVEN: FileDialog with a mocked QDialog.getExistingDirectory method
|
||||
# WHEN: Calling FileDialog.getExistingDirectory and the user cancels the dialog returns a empty string
|
||||
with patch('PyQt5.QtWidgets.QFileDialog.getExistingDirectory', return_value=''):
|
||||
result = FileDialog.getExistingDirectory()
|
||||
|
||||
# THEN: The result should be None
|
||||
self.assertEqual(result, None)
|
||||
|
||||
def test_get_existing_directory_user_accepts(self):
|
||||
"""
|
||||
Test that `getExistingDirectory` handles the case when the user accepts the dialog
|
||||
"""
|
||||
# GIVEN: FileDialog with a mocked QDialog.getExistingDirectory method
|
||||
# WHEN: Calling FileDialog.getExistingDirectory, the user chooses a file and accepts the dialog (it returns a
|
||||
# string pointing to the directory)
|
||||
with patch('PyQt5.QtWidgets.QFileDialog.getExistingDirectory', return_value=os.path.join('test', 'dir')):
|
||||
result = FileDialog.getExistingDirectory()
|
||||
|
||||
# THEN: getExistingDirectory() should return a Path object pointing to the chosen file
|
||||
self.assertEqual(result, Path('test', 'dir'))
|
||||
|
||||
def test_get_existing_directory_param_order(self):
|
||||
"""
|
||||
Test that `getExistingDirectory` passes the parameters to `QFileDialog.getExistingDirectory` in the correct
|
||||
order
|
||||
"""
|
||||
# GIVEN: FileDialog
|
||||
with patch('openlp.core.ui.lib.filedialog.QtWidgets.QFileDialog.getExistingDirectory', return_value='') \
|
||||
as mocked_get_existing_directory:
|
||||
|
||||
# WHEN: Calling the getExistingDirectory method with all parameters set
|
||||
FileDialog.getExistingDirectory('Parent', 'Caption', Path('test', 'dir'), 'Options')
|
||||
|
||||
# THEN: The `QFileDialog.getExistingDirectory` should have been called with the parameters in the correct
|
||||
# order
|
||||
mocked_get_existing_directory.assert_called_once_with('Parent', 'Caption', os.path.join('test', 'dir'),
|
||||
'Options')
|
||||
|
||||
def test_get_open_file_name_user_abort(self):
|
||||
"""
|
||||
Test that `getOpenFileName` handles the case when the user cancels the dialog
|
||||
"""
|
||||
# GIVEN: FileDialog with a mocked QDialog.getOpenFileName method
|
||||
# WHEN: Calling FileDialog.getOpenFileName and the user cancels the dialog (it returns a tuple with the first
|
||||
# value set as an empty string)
|
||||
with patch('PyQt5.QtWidgets.QFileDialog.getOpenFileName', return_value=('', '')):
|
||||
result = FileDialog.getOpenFileName()
|
||||
|
||||
# THEN: First value should be None
|
||||
self.assertEqual(result[0], None)
|
||||
|
||||
def test_get_open_file_name_user_accepts(self):
|
||||
"""
|
||||
Test that `getOpenFileName` handles the case when the user accepts the dialog
|
||||
"""
|
||||
# GIVEN: FileDialog with a mocked QDialog.getOpenFileName method
|
||||
# WHEN: Calling FileDialog.getOpenFileName, the user chooses a file and accepts the dialog (it returns a
|
||||
# tuple with the first value set as an string pointing to the file)
|
||||
with patch('PyQt5.QtWidgets.QFileDialog.getOpenFileName',
|
||||
return_value=(os.path.join('test', 'chosen.file'), '')):
|
||||
result = FileDialog.getOpenFileName()
|
||||
|
||||
# THEN: getOpenFileName() should return a tuple with the first value set to a Path object pointing to the
|
||||
# chosen file
|
||||
self.assertEqual(result[0], Path('test', 'chosen.file'))
|
||||
|
||||
def test_get_open_file_name_selected_filter(self):
|
||||
"""
|
||||
Test that `getOpenFileName` does not modify the selectedFilter as returned by `QFileDialog.getOpenFileName`
|
||||
"""
|
||||
# GIVEN: FileDialog with a mocked QDialog.get_save_file_name method
|
||||
# WHEN: Calling FileDialog.getOpenFileName, and `QFileDialog.getOpenFileName` returns a known `selectedFilter`
|
||||
with patch('PyQt5.QtWidgets.QFileDialog.getOpenFileName', return_value=('', 'selected filter')):
|
||||
result = FileDialog.getOpenFileName()
|
||||
|
||||
# THEN: getOpenFileName() should return a tuple with the second value set to a the selected filter
|
||||
self.assertEqual(result[1], 'selected filter')
|
||||
|
||||
def test_get_open_file_names_user_abort(self):
|
||||
"""
|
||||
Test that `getOpenFileNames` handles the case when the user cancels the dialog
|
||||
"""
|
||||
# GIVEN: FileDialog with a mocked QDialog.getOpenFileNames method
|
||||
# WHEN: Calling FileDialog.getOpenFileNames and the user cancels the dialog (it returns a tuple with the first
|
||||
# value set as an empty list)
|
||||
with patch('PyQt5.QtWidgets.QFileDialog.getOpenFileNames', return_value=([], '')):
|
||||
result = FileDialog.getOpenFileNames()
|
||||
|
||||
# THEN: First value should be an empty list
|
||||
self.assertEqual(result[0], [])
|
||||
|
||||
def test_get_open_file_names_user_accepts(self):
|
||||
"""
|
||||
Test that `getOpenFileNames` handles the case when the user accepts the dialog
|
||||
"""
|
||||
# GIVEN: FileDialog with a mocked QDialog.getOpenFileNames method
|
||||
# WHEN: Calling FileDialog.getOpenFileNames, the user chooses some files and accepts the dialog (it returns a
|
||||
# tuple with the first value set as a list of strings pointing to the file)
|
||||
with patch('PyQt5.QtWidgets.QFileDialog.getOpenFileNames',
|
||||
return_value=([os.path.join('test', 'chosen.file1'), os.path.join('test', 'chosen.file2')], '')):
|
||||
result = FileDialog.getOpenFileNames()
|
||||
|
||||
# THEN: getOpenFileNames() should return a tuple with the first value set to a list of Path objects pointing
|
||||
# to the chosen file
|
||||
self.assertEqual(result[0], [Path('test', 'chosen.file1'), Path('test', 'chosen.file2')])
|
||||
|
||||
def test_get_open_file_names_selected_filter(self):
|
||||
"""
|
||||
Test that `getOpenFileNames` does not modify the selectedFilter as returned by `QFileDialog.getOpenFileNames`
|
||||
"""
|
||||
# GIVEN: FileDialog with a mocked QDialog.getOpenFileNames method
|
||||
# WHEN: Calling FileDialog.getOpenFileNames, and `QFileDialog.getOpenFileNames` returns a known
|
||||
# `selectedFilter`
|
||||
with patch('PyQt5.QtWidgets.QFileDialog.getOpenFileNames', return_value=([], 'selected filter')):
|
||||
result = FileDialog.getOpenFileNames()
|
||||
|
||||
# THEN: getOpenFileNames() should return a tuple with the second value set to a the selected filter
|
||||
self.assertEqual(result[1], 'selected filter')
|
||||
|
||||
def test_get_save_file_name_user_abort(self):
|
||||
"""
|
||||
Test that `getSaveFileName` handles the case when the user cancels the dialog
|
||||
"""
|
||||
# GIVEN: FileDialog with a mocked QDialog.get_save_file_name method
|
||||
# WHEN: Calling FileDialog.getSaveFileName and the user cancels the dialog (it returns a tuple with the first
|
||||
# value set as an empty string)
|
||||
with patch('PyQt5.QtWidgets.QFileDialog.getSaveFileName', return_value=('', '')):
|
||||
result = FileDialog.getSaveFileName()
|
||||
|
||||
# THEN: First value should be None
|
||||
self.assertEqual(result[0], None)
|
||||
|
||||
def test_get_save_file_name_user_accepts(self):
|
||||
"""
|
||||
Test that `getSaveFileName` handles the case when the user accepts the dialog
|
||||
"""
|
||||
# GIVEN: FileDialog with a mocked QDialog.getSaveFileName method
|
||||
# WHEN: Calling FileDialog.getSaveFileName, the user chooses a file and accepts the dialog (it returns a
|
||||
# tuple with the first value set as an string pointing to the file)
|
||||
with patch('PyQt5.QtWidgets.QFileDialog.getSaveFileName',
|
||||
return_value=(os.path.join('test', 'chosen.file'), '')):
|
||||
result = FileDialog.getSaveFileName()
|
||||
|
||||
# THEN: getSaveFileName() should return a tuple with the first value set to a Path object pointing to the
|
||||
# chosen file
|
||||
self.assertEqual(result[0], Path('test', 'chosen.file'))
|
||||
|
||||
def test_get_save_file_name_selected_filter(self):
|
||||
"""
|
||||
Test that `getSaveFileName` does not modify the selectedFilter as returned by `QFileDialog.getSaveFileName`
|
||||
"""
|
||||
# GIVEN: FileDialog with a mocked QDialog.get_save_file_name method
|
||||
# WHEN: Calling FileDialog.getSaveFileName, and `QFileDialog.getSaveFileName` returns a known `selectedFilter`
|
||||
with patch('PyQt5.QtWidgets.QFileDialog.getSaveFileName', return_value=('', 'selected filter')):
|
||||
result = FileDialog.getSaveFileName()
|
||||
|
||||
# THEN: getSaveFileName() should return a tuple with the second value set to a the selected filter
|
||||
self.assertEqual(result[1], 'selected filter')
|
@ -22,12 +22,13 @@
|
||||
"""
|
||||
This module contains tests for the openlp.core.ui.lib.pathedit module
|
||||
"""
|
||||
import os
|
||||
from pathlib import Path
|
||||
from unittest import TestCase
|
||||
|
||||
from PyQt5 import QtWidgets
|
||||
from unittest.mock import MagicMock, PropertyMock, patch
|
||||
|
||||
from openlp.core.ui.lib import PathEdit, PathType
|
||||
from unittest.mock import MagicMock, PropertyMock, patch
|
||||
from openlp.core.ui.lib.filedialog import FileDialog
|
||||
|
||||
|
||||
class TestPathEdit(TestCase):
|
||||
@ -43,11 +44,11 @@ class TestPathEdit(TestCase):
|
||||
Test the `path` property getter.
|
||||
"""
|
||||
# GIVEN: An instance of PathEdit with the `_path` instance variable set
|
||||
self.widget._path = 'getter/test/pat.h'
|
||||
self.widget._path = Path('getter', 'test', 'pat.h')
|
||||
|
||||
# WHEN: Reading the `path` property
|
||||
# THEN: The value that we set should be returned
|
||||
self.assertEqual(self.widget.path, 'getter/test/pat.h')
|
||||
self.assertEqual(self.widget.path, Path('getter', 'test', 'pat.h'))
|
||||
|
||||
def test_path_setter(self):
|
||||
"""
|
||||
@ -57,13 +58,13 @@ class TestPathEdit(TestCase):
|
||||
self.widget.line_edit = MagicMock()
|
||||
|
||||
# WHEN: Writing to the `path` property
|
||||
self.widget.path = 'setter/test/pat.h'
|
||||
self.widget.path = Path('setter', 'test', 'pat.h')
|
||||
|
||||
# THEN: The `_path` instance variable should be set with the test data. The `line_edit` text and tooltip
|
||||
# should have also been set.
|
||||
self.assertEqual(self.widget._path, 'setter/test/pat.h')
|
||||
self.widget.line_edit.setToolTip.assert_called_once_with('setter/test/pat.h')
|
||||
self.widget.line_edit.setText.assert_called_once_with('setter/test/pat.h')
|
||||
self.assertEqual(self.widget._path, Path('setter', 'test', 'pat.h'))
|
||||
self.widget.line_edit.setToolTip.assert_called_once_with(os.path.join('setter', 'test', 'pat.h'))
|
||||
self.widget.line_edit.setText.assert_called_once_with(os.path.join('setter', 'test', 'pat.h'))
|
||||
|
||||
def test_path_type_getter(self):
|
||||
"""
|
||||
@ -125,22 +126,20 @@ class TestPathEdit(TestCase):
|
||||
"""
|
||||
# GIVEN: An instance of PathEdit with the `path_type` set to `Directories` and a mocked
|
||||
# QFileDialog.getExistingDirectory
|
||||
with patch('openlp.core.ui.lib.pathedit.QtWidgets.QFileDialog.getExistingDirectory', return_value='') as \
|
||||
with patch('openlp.core.ui.lib.pathedit.FileDialog.getExistingDirectory', return_value=None) as \
|
||||
mocked_get_existing_directory, \
|
||||
patch('openlp.core.ui.lib.pathedit.QtWidgets.QFileDialog.getOpenFileName') as \
|
||||
mocked_get_open_file_name, \
|
||||
patch('openlp.core.ui.lib.pathedit.os.path.normpath') as mocked_normpath:
|
||||
patch('openlp.core.ui.lib.pathedit.FileDialog.getOpenFileName') as mocked_get_open_file_name:
|
||||
self.widget._path_type = PathType.Directories
|
||||
self.widget._path = 'test/path/'
|
||||
self.widget._path = Path('test', 'path')
|
||||
|
||||
# WHEN: Calling on_browse_button_clicked
|
||||
self.widget.on_browse_button_clicked()
|
||||
|
||||
# THEN: The FileDialog.getExistingDirectory should have been called with the default caption
|
||||
mocked_get_existing_directory.assert_called_once_with(self.widget, 'Select Directory', 'test/path/',
|
||||
QtWidgets.QFileDialog.ShowDirsOnly)
|
||||
mocked_get_existing_directory.assert_called_once_with(self.widget, 'Select Directory',
|
||||
Path('test', 'path'),
|
||||
FileDialog.ShowDirsOnly)
|
||||
self.assertFalse(mocked_get_open_file_name.called)
|
||||
self.assertFalse(mocked_normpath.called)
|
||||
|
||||
def test_on_browse_button_clicked_directory_custom_caption(self):
|
||||
"""
|
||||
@ -149,45 +148,40 @@ class TestPathEdit(TestCase):
|
||||
"""
|
||||
# GIVEN: An instance of PathEdit with the `path_type` set to `Directories` and a mocked
|
||||
# QFileDialog.getExistingDirectory with `default_caption` set.
|
||||
with patch('openlp.core.ui.lib.pathedit.QtWidgets.QFileDialog.getExistingDirectory', return_value='') as \
|
||||
with patch('openlp.core.ui.lib.pathedit.FileDialog.getExistingDirectory', return_value=None) as \
|
||||
mocked_get_existing_directory, \
|
||||
patch('openlp.core.ui.lib.pathedit.QtWidgets.QFileDialog.getOpenFileName') as \
|
||||
mocked_get_open_file_name, \
|
||||
patch('openlp.core.ui.lib.pathedit.os.path.normpath') as mocked_normpath:
|
||||
patch('openlp.core.ui.lib.pathedit.FileDialog.getOpenFileName') as mocked_get_open_file_name:
|
||||
self.widget._path_type = PathType.Directories
|
||||
self.widget._path = 'test/path/'
|
||||
self.widget._path = Path('test', 'path')
|
||||
self.widget.dialog_caption = 'Directory Caption'
|
||||
|
||||
# WHEN: Calling on_browse_button_clicked
|
||||
self.widget.on_browse_button_clicked()
|
||||
|
||||
# THEN: The FileDialog.getExistingDirectory should have been called with the custom caption
|
||||
mocked_get_existing_directory.assert_called_once_with(self.widget, 'Directory Caption', 'test/path/',
|
||||
QtWidgets.QFileDialog.ShowDirsOnly)
|
||||
mocked_get_existing_directory.assert_called_once_with(self.widget, 'Directory Caption',
|
||||
Path('test', 'path'),
|
||||
FileDialog.ShowDirsOnly)
|
||||
self.assertFalse(mocked_get_open_file_name.called)
|
||||
self.assertFalse(mocked_normpath.called)
|
||||
|
||||
def test_on_browse_button_clicked_file(self):
|
||||
"""
|
||||
Test the `browse_button` `clicked` handler on_browse_button_clicked when the `path_type` is set to Files.
|
||||
"""
|
||||
# GIVEN: An instance of PathEdit with the `path_type` set to `Files` and a mocked QFileDialog.getOpenFileName
|
||||
with patch('openlp.core.ui.lib.pathedit.QtWidgets.QFileDialog.getExistingDirectory') as \
|
||||
mocked_get_existing_directory, \
|
||||
patch('openlp.core.ui.lib.pathedit.QtWidgets.QFileDialog.getOpenFileName', return_value=('', '')) as \
|
||||
mocked_get_open_file_name, \
|
||||
patch('openlp.core.ui.lib.pathedit.os.path.normpath') as mocked_normpath:
|
||||
with patch('openlp.core.ui.lib.pathedit.FileDialog.getExistingDirectory') as mocked_get_existing_directory, \
|
||||
patch('openlp.core.ui.lib.pathedit.FileDialog.getOpenFileName', return_value=(None, '')) as \
|
||||
mocked_get_open_file_name:
|
||||
self.widget._path_type = PathType.Files
|
||||
self.widget._path = 'test/pat.h'
|
||||
self.widget._path = Path('test', 'pat.h')
|
||||
|
||||
# WHEN: Calling on_browse_button_clicked
|
||||
self.widget.on_browse_button_clicked()
|
||||
|
||||
# THEN: The FileDialog.getOpenFileName should have been called with the default caption
|
||||
mocked_get_open_file_name.assert_called_once_with(self.widget, 'Select File', 'test/pat.h',
|
||||
mocked_get_open_file_name.assert_called_once_with(self.widget, 'Select File', Path('test', 'pat.h'),
|
||||
self.widget.filters)
|
||||
self.assertFalse(mocked_get_existing_directory.called)
|
||||
self.assertFalse(mocked_normpath.called)
|
||||
|
||||
def test_on_browse_button_clicked_file_custom_caption(self):
|
||||
"""
|
||||
@ -196,23 +190,20 @@ class TestPathEdit(TestCase):
|
||||
"""
|
||||
# GIVEN: An instance of PathEdit with the `path_type` set to `Files` and a mocked QFileDialog.getOpenFileName
|
||||
# with `default_caption` set.
|
||||
with patch('openlp.core.ui.lib.pathedit.QtWidgets.QFileDialog.getExistingDirectory') as \
|
||||
mocked_get_existing_directory, \
|
||||
patch('openlp.core.ui.lib.pathedit.QtWidgets.QFileDialog.getOpenFileName', return_value=('', '')) as \
|
||||
mocked_get_open_file_name, \
|
||||
patch('openlp.core.ui.lib.pathedit.os.path.normpath') as mocked_normpath:
|
||||
with patch('openlp.core.ui.lib.pathedit.FileDialog.getExistingDirectory') as mocked_get_existing_directory, \
|
||||
patch('openlp.core.ui.lib.pathedit.FileDialog.getOpenFileName', return_value=(None, '')) as \
|
||||
mocked_get_open_file_name:
|
||||
self.widget._path_type = PathType.Files
|
||||
self.widget._path = 'test/pat.h'
|
||||
self.widget._path = Path('test', 'pat.h')
|
||||
self.widget.dialog_caption = 'File Caption'
|
||||
|
||||
# WHEN: Calling on_browse_button_clicked
|
||||
self.widget.on_browse_button_clicked()
|
||||
|
||||
# THEN: The FileDialog.getOpenFileName should have been called with the custom caption
|
||||
mocked_get_open_file_name.assert_called_once_with(self.widget, 'File Caption', 'test/pat.h',
|
||||
mocked_get_open_file_name.assert_called_once_with(self.widget, 'File Caption', Path('test', 'pat.h'),
|
||||
self.widget.filters)
|
||||
self.assertFalse(mocked_get_existing_directory.called)
|
||||
self.assertFalse(mocked_normpath.called)
|
||||
|
||||
def test_on_browse_button_clicked_user_cancels(self):
|
||||
"""
|
||||
@ -221,16 +212,14 @@ class TestPathEdit(TestCase):
|
||||
"""
|
||||
# GIVEN: An instance of PathEdit with a mocked QFileDialog.getOpenFileName which returns an empty str for the
|
||||
# file path.
|
||||
with patch('openlp.core.ui.lib.pathedit.QtWidgets.QFileDialog.getOpenFileName', return_value=('', '')) as \
|
||||
mocked_get_open_file_name, \
|
||||
patch('openlp.core.ui.lib.pathedit.os.path.normpath') as mocked_normpath:
|
||||
with patch('openlp.core.ui.lib.pathedit.FileDialog.getOpenFileName', return_value=(None, '')) as \
|
||||
mocked_get_open_file_name:
|
||||
|
||||
# WHEN: Calling on_browse_button_clicked
|
||||
self.widget.on_browse_button_clicked()
|
||||
|
||||
# THEN: normpath should not have been called
|
||||
self.assertTrue(mocked_get_open_file_name.called)
|
||||
self.assertFalse(mocked_normpath.called)
|
||||
|
||||
def test_on_browse_button_clicked_user_accepts(self):
|
||||
"""
|
||||
@ -239,9 +228,8 @@ class TestPathEdit(TestCase):
|
||||
"""
|
||||
# GIVEN: An instance of PathEdit with a mocked QFileDialog.getOpenFileName which returns a str for the file
|
||||
# path.
|
||||
with patch('openlp.core.ui.lib.pathedit.QtWidgets.QFileDialog.getOpenFileName',
|
||||
return_value=('/test/pat.h', '')) as mocked_get_open_file_name, \
|
||||
patch('openlp.core.ui.lib.pathedit.os.path.normpath') as mocked_normpath, \
|
||||
with patch('openlp.core.ui.lib.pathedit.FileDialog.getOpenFileName',
|
||||
return_value=(Path('test', 'pat.h'), '')) as mocked_get_open_file_name, \
|
||||
patch.object(self.widget, 'on_new_path'):
|
||||
|
||||
# WHEN: Calling on_browse_button_clicked
|
||||
@ -249,7 +237,6 @@ class TestPathEdit(TestCase):
|
||||
|
||||
# THEN: normpath and `on_new_path` should have been called
|
||||
self.assertTrue(mocked_get_open_file_name.called)
|
||||
mocked_normpath.assert_called_once_with('/test/pat.h')
|
||||
self.assertTrue(self.widget.on_new_path.called)
|
||||
|
||||
def test_on_revert_button_clicked(self):
|
||||
@ -258,13 +245,13 @@ class TestPathEdit(TestCase):
|
||||
"""
|
||||
# GIVEN: An instance of PathEdit with a mocked `on_new_path`, and the `default_path` set.
|
||||
with patch.object(self.widget, 'on_new_path') as mocked_on_new_path:
|
||||
self.widget.default_path = '/default/pat.h'
|
||||
self.widget.default_path = Path('default', 'pat.h')
|
||||
|
||||
# WHEN: Calling `on_revert_button_clicked`
|
||||
self.widget.on_revert_button_clicked()
|
||||
|
||||
# THEN: on_new_path should have been called with the default path
|
||||
mocked_on_new_path.assert_called_once_with('/default/pat.h')
|
||||
mocked_on_new_path.assert_called_once_with(Path('default', 'pat.h'))
|
||||
|
||||
def test_on_line_edit_editing_finished(self):
|
||||
"""
|
||||
@ -272,13 +259,13 @@ class TestPathEdit(TestCase):
|
||||
"""
|
||||
# GIVEN: An instance of PathEdit with a mocked `line_edit` and `on_new_path`.
|
||||
with patch.object(self.widget, 'on_new_path') as mocked_on_new_path:
|
||||
self.widget.line_edit = MagicMock(**{'text.return_value': '/test/pat.h'})
|
||||
self.widget.line_edit = MagicMock(**{'text.return_value': 'test/pat.h'})
|
||||
|
||||
# WHEN: Calling `on_line_edit_editing_finished`
|
||||
self.widget.on_line_edit_editing_finished()
|
||||
|
||||
# THEN: on_new_path should have been called with the path enetered in `line_edit`
|
||||
mocked_on_new_path.assert_called_once_with('/test/pat.h')
|
||||
mocked_on_new_path.assert_called_once_with(Path('test', 'pat.h'))
|
||||
|
||||
def test_on_new_path_no_change(self):
|
||||
"""
|
||||
@ -286,11 +273,11 @@ class TestPathEdit(TestCase):
|
||||
"""
|
||||
# GIVEN: An instance of PathEdit with a test path and mocked `pathChanged` signal
|
||||
with patch('openlp.core.ui.lib.pathedit.PathEdit.path', new_callable=PropertyMock):
|
||||
self.widget._path = '/old/test/pat.h'
|
||||
self.widget._path = Path('/old', 'test', 'pat.h')
|
||||
self.widget.pathChanged = MagicMock()
|
||||
|
||||
# WHEN: Calling `on_new_path` with the same path as the existing path
|
||||
self.widget.on_new_path('/old/test/pat.h')
|
||||
self.widget.on_new_path(Path('/old', 'test', 'pat.h'))
|
||||
|
||||
# THEN: The `pathChanged` signal should not be emitted
|
||||
self.assertFalse(self.widget.pathChanged.emit.called)
|
||||
@ -301,11 +288,11 @@ class TestPathEdit(TestCase):
|
||||
"""
|
||||
# GIVEN: An instance of PathEdit with a test path and mocked `pathChanged` signal
|
||||
with patch('openlp.core.ui.lib.pathedit.PathEdit.path', new_callable=PropertyMock):
|
||||
self.widget._path = '/old/test/pat.h'
|
||||
self.widget._path = Path('/old', 'test', 'pat.h')
|
||||
self.widget.pathChanged = MagicMock()
|
||||
|
||||
# WHEN: Calling `on_new_path` with the a new path
|
||||
self.widget.on_new_path('/new/test/pat.h')
|
||||
self.widget.on_new_path(Path('/new', 'test', 'pat.h'))
|
||||
|
||||
# THEN: The `pathChanged` signal should be emitted
|
||||
self.widget.pathChanged.emit.assert_called_once_with('/new/test/pat.h')
|
||||
self.widget.pathChanged.emit.assert_called_once_with(Path('/new', 'test', 'pat.h'))
|
Loading…
Reference in New Issue
Block a user