This commit is contained in:
Phill Ridout 2014-10-29 19:11:43 +00:00
commit 3685948ed5
67 changed files with 4678 additions and 44 deletions

View File

@ -33,3 +33,10 @@ tests.kdev4
__pycache__
*.dll
.directory
*.kate-swp
# Git files
.git
.gitignore
# Rejected diff's
*.rej
*.~\?~

View File

@ -30,13 +30,17 @@
The :mod:`common` module contains most of the components and libraries that make
OpenLP work.
"""
import hashlib
import re
import os
import logging
import sys
import traceback
from ipaddress import IPv4Address, IPv6Address, AddressValueError
from codecs import decode, encode
from PyQt4 import QtCore
from PyQt4.QtCore import QCryptographicHash as QHash
log = logging.getLogger(__name__ + '.__init__')
@ -154,6 +158,81 @@ def is_linux():
"""
return sys.platform.startswith('linux')
def verify_ipv4(addr):
"""
Validate an IPv4 address
:param addr: Address to validate
:returns: bool
"""
try:
valid = IPv4Address(addr)
return True
except AddressValueError:
return False
def verify_ipv6(addr):
"""
Validate an IPv6 address
:param addr: Address to validate
:returns: bool
"""
try:
valid = IPv6Address(addr)
return True
except AddressValueError:
return False
def verify_ip_address(addr):
"""
Validate an IP address as either IPv4 or IPv6
:param addr: Address to validate
:returns: bool
"""
return True if verify_ipv4(addr) else verify_ipv6(addr)
def md5_hash(salt, data):
"""
Returns the hashed output of md5sum on salt,data
using Python3 hashlib
:param salt: Initial salt
:param data: Data to hash
:returns: str
"""
log.debug('md5_hash(salt="%s")' % salt)
hash_obj = hashlib.new('md5')
hash_obj.update(salt.encode('ascii'))
hash_obj.update(data.encode('ascii'))
hash_value = hash_obj.hexdigest()
log.debug('md5_hash() returning "%s"' % hash_value)
return hash_value
def qmd5_hash(salt, data):
"""
Returns the hashed output of MD5Sum on salt, data
using PyQt4.QCryptographicHash.
:param salt: Initial salt
:param data: Data to hash
:returns: str
"""
log.debug('qmd5_hash(salt="%s"' % salt)
hash_obj = QHash(QHash.Md5)
hash_obj.addData(salt)
hash_obj.addData(data)
hash_value = hash_obj.result().toHex()
log.debug('qmd5_hash() returning "%s"' % hash_value)
return decode(hash_value.data(), 'ascii')
from .openlpmixin import OpenLPMixin
from .registry import Registry
from .registrymixin import RegistryMixin

View File

@ -148,3 +148,12 @@ class RegistryProperties(object):
if not hasattr(self, '_alerts_manager') or not self._alerts_manager:
self._alerts_manager = Registry().get('alerts_manager')
return self._alerts_manager
@property
def projector_manager(self):
"""
Adds the projector manager to the class dynamically
"""
if not hasattr(self, '_projector_manager') or not self._projector_manager:
self._projector_manager = Registry().get('projector_manager')
return self._projector_manager

View File

@ -276,6 +276,7 @@ class Settings(QtCore.QSettings):
'shortcuts/toolsAddToolItem': [],
'shortcuts/updateThemeImages': [],
'shortcuts/up': [QtGui.QKeySequence(QtCore.Qt.Key_Up)],
'shortcuts/viewProjectorManagerItem': [QtGui.QKeySequence('F6')],
'shortcuts/viewThemeManagerItem': [QtGui.QKeySequence('F10')],
'shortcuts/viewMediaManagerItem': [QtGui.QKeySequence('F8')],
'shortcuts/viewPreviewPanel': [QtGui.QKeySequence('F11')],
@ -296,7 +297,15 @@ class Settings(QtCore.QSettings):
'user interface/main window splitter geometry': QtCore.QByteArray(),
'user interface/main window state': QtCore.QByteArray(),
'user interface/preview panel': True,
'user interface/preview splitter geometry': QtCore.QByteArray()
'user interface/preview splitter geometry': QtCore.QByteArray(),
'projector/db type': 'sqlite',
'projector/enable': True,
'projector/connect on start': False,
'projector/last directory import': '',
'projector/last directory export': '',
'projector/poll time': 20, # PJLink timeout is 30 seconds
'projector/socket timeout': 5, # 5 second socket timeout
'projector/source dialog type': 0 # Source select dialog box type
}
__file_path__ = ''
__obsolete_settings__ = [

View File

@ -99,6 +99,10 @@ class UiStrings(object):
self.LiveBGError = translate('OpenLP.Ui', 'Live Background Error')
self.LiveToolbar = translate('OpenLP.Ui', 'Live Toolbar')
self.Load = translate('OpenLP.Ui', 'Load')
self.Manufacturer = translate('OpenLP.Ui', 'Manufacturer', 'Singular')
self.Manufacturers = translate('OpenLP.Ui', 'Manufacturers', 'Plural')
self.Model = translate('OpenLP.Ui', 'Model', 'Singular')
self.Models = translate('OpenLP.Ui', 'Models', 'Plural')
self.Minutes = translate('OpenLP.Ui', 'm', 'The abbreviated unit for minutes')
self.Middle = translate('OpenLP.Ui', 'Middle')
self.New = translate('OpenLP.Ui', 'New')
@ -118,6 +122,8 @@ class UiStrings(object):
self.PlaySlidesToEnd = translate('OpenLP.Ui', 'Play Slides to End')
self.Preview = translate('OpenLP.Ui', 'Preview')
self.PrintService = translate('OpenLP.Ui', 'Print Service')
self.Projector = translate('OpenLP.Ui', 'Projector', 'Singular')
self.Projectors = translate('OpenLP.Ui', 'Projectors', 'Plural')
self.ReplaceBG = translate('OpenLP.Ui', 'Replace Background')
self.ReplaceLiveBG = translate('OpenLP.Ui', 'Replace live background.')
self.ResetBG = translate('OpenLP.Ui', 'Reset Background')

View File

@ -335,3 +335,6 @@ from .dockwidget import OpenLPDockWidget
from .imagemanager import ImageManager
from .renderer import Renderer
from .mediamanageritem import MediaManagerItem
from .projector.db import ProjectorDB, Projector
from .projector.pjlink1 import PJLink1
from .projector.constants import PJLINK_PORT, ERROR_MSG, ERROR_STRING

View File

@ -48,20 +48,53 @@ from openlp.core.utils import delete_file
log = logging.getLogger(__name__)
def init_db(url, auto_flush=True, auto_commit=False):
def init_db(url, auto_flush=True, auto_commit=False, base=None):
"""
Initialise and return the session and metadata for a database
:param url: The database to initialise connection with
:param auto_flush: Sets the flushing behaviour of the session
:param auto_commit: Sets the commit behaviour of the session
:param base: If using declarative, the base class to bind with
"""
engine = create_engine(url, poolclass=NullPool)
metadata = MetaData(bind=engine)
if base is None:
metadata = MetaData(bind=engine)
else:
base.metadata.bind = engine
metadata = None
session = scoped_session(sessionmaker(autoflush=auto_flush, autocommit=auto_commit, bind=engine))
return session, metadata
def init_url(plugin_name, db_file_name=None):
"""
Return the database URL.
:param plugin_name: The name of the plugin for the database creation.
:param db_file_name: The database file name. Defaults to None resulting in the plugin_name being used.
"""
settings = Settings()
settings.beginGroup(plugin_name)
db_url = ''
db_type = settings.value('db type')
if db_type == 'sqlite':
if db_file_name is None:
db_url = 'sqlite:///%s/%s.sqlite' % (AppLocation.get_section_data_path(plugin_name), plugin_name)
else:
db_url = 'sqlite:///%s/%s' % (AppLocation.get_section_data_path(plugin_name), db_file_name)
else:
db_url = '%s://%s:%s@%s/%s' % (db_type, urlquote(settings.value('db username')),
urlquote(settings.value('db password')),
urlquote(settings.value('db hostname')),
urlquote(settings.value('db database')))
if db_type == 'mysql':
db_encoding = settings.value('db encoding')
db_url += '?charset=%s' % urlquote(db_encoding)
settings.endGroup()
return db_url
def get_upgrade_op(session):
"""
Create a migration context and an operations object for performing upgrades.
@ -159,7 +192,7 @@ class Manager(object):
"""
Provide generic object persistence management
"""
def __init__(self, plugin_name, init_schema, db_file_name=None, upgrade_mod=None):
def __init__(self, plugin_name, init_schema, db_file_name=None, upgrade_mod=None, session=None):
"""
Runs the initialisation process that includes creating the connection to the database and the tables if they do
not exist.
@ -170,26 +203,15 @@ class Manager(object):
:param upgrade_mod: The file name to use for this database. Defaults to None resulting in the plugin_name
being used.
"""
settings = Settings()
settings.beginGroup(plugin_name)
self.db_url = ''
self.is_dirty = False
self.session = None
db_type = settings.value('db type')
if db_type == 'sqlite':
if db_file_name:
self.db_url = 'sqlite:///%s/%s' % (AppLocation.get_section_data_path(plugin_name), db_file_name)
else:
self.db_url = 'sqlite:///%s/%s.sqlite' % (AppLocation.get_section_data_path(plugin_name), plugin_name)
# See if we're using declarative_base with a pre-existing session.
log.debug('Manager: Testing for pre-existing session')
if session is not None:
log.debug('Manager: Using existing session')
else:
self.db_url = '%s://%s:%s@%s/%s' % (db_type, urlquote(settings.value('db username')),
urlquote(settings.value('db password')),
urlquote(settings.value('db hostname')),
urlquote(settings.value('db database')))
if db_type == 'mysql':
db_encoding = settings.value('db encoding')
self.db_url += '?charset=%s' % urlquote(db_encoding)
settings.endGroup()
log.debug('Manager: Creating new session')
self.db_url = init_url(plugin_name, db_file_name)
if upgrade_mod:
try:
db_ver, up_ver = upgrade_db(self.db_url, upgrade_mod)

View File

@ -109,6 +109,6 @@ class ListWidgetWithDnD(QtGui.QListWidget):
listing = os.listdir(local_file)
for file in listing:
files.append(os.path.join(local_file, file))
Registry().execute('%s_dnd' % self.mime_data_text, files)
Registry().execute('%s_dnd' % self.mime_data_text, {'files': files, 'target': self.itemAt(event.pos())})
else:
event.ignore()

View File

@ -0,0 +1,41 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2014 Raoul Snyman #
# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan #
# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
# Christian Richter, Philip Ridout, Ken Roberts, Simon Scudder, #
# Jeffrey Smith, Maikel Stuivenberg, Martin Thompson, Jon Tibble, #
# Dave Warnock, Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
:mod:`openlp.core.ui.projector`
Initialization for the openlp.core.ui.projector modules.
"""
class DialogSourceStyle(object):
"""
An enumeration for projector dialog box type.
"""
Tabbed = 0
Single = 1

View File

@ -0,0 +1,366 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2014 Raoul Snyman #
# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan #
# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
# Christian Richter, Philip Ridout, Ken Roberts, Simon Scudder, #
# Jeffrey Smith, Maikel Stuivenberg, Martin Thompson, Jon Tibble, #
# Dave Warnock, Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
:mod:`openlp.core.lib.projector.constants` module
Provides the constants used for projector errors/status/defaults
"""
import logging
log = logging.getLogger(__name__)
log.debug('projector_constants loaded')
from openlp.core.common import translate
__all__ = ['S_OK', 'E_GENERAL', 'E_NOT_CONNECTED', 'E_FAN', 'E_LAMP', 'E_TEMP',
'E_COVER', 'E_FILTER', 'E_AUTHENTICATION', 'E_NO_AUTHENTICATION',
'E_UNDEFINED', 'E_PARAMETER', 'E_UNAVAILABLE', 'E_PROJECTOR',
'E_INVALID_DATA', 'E_WARN', 'E_ERROR', 'E_CLASS', 'E_PREFIX',
'E_CONNECTION_REFUSED', 'E_REMOTE_HOST_CLOSED_CONNECTION', 'E_HOST_NOT_FOUND',
'E_SOCKET_ACCESS', 'E_SOCKET_RESOURCE', 'E_SOCKET_TIMEOUT', 'E_DATAGRAM_TOO_LARGE',
'E_NETWORK', 'E_ADDRESS_IN_USE', 'E_SOCKET_ADDRESS_NOT_AVAILABLE',
'E_UNSUPPORTED_SOCKET_OPERATION', 'E_PROXY_AUTHENTICATION_REQUIRED',
'E_SLS_HANDSHAKE_FAILED', 'E_UNFINISHED_SOCKET_OPERATION', 'E_PROXY_CONNECTION_REFUSED',
'E_PROXY_CONNECTION_CLOSED', 'E_PROXY_CONNECTION_TIMEOUT', 'E_PROXY_NOT_FOUND',
'E_PROXY_PROTOCOL', 'E_UNKNOWN_SOCKET_ERROR',
'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',
'PJLINK_PORT', 'PJLINK_MAX_PACKET', 'TIMEOUT', 'ERROR_MSG', 'PJLINK_ERRORS',
'STATUS_STRING', 'PJLINK_VALID_CMD', 'CONNECTION_ERRORS']
# Set common constants.
CR = chr(0x0D) # \r
LF = chr(0x0A) # \n
PJLINK_PORT = 4352
TIMEOUT = 30.0
PJLINK_MAX_PACKET = 136
PJLINK_VALID_CMD = {'1': ['PJLINK', # Initial connection
'POWR', # Power option
'INPT', # Video sources option
'AVMT', # Shutter option
'ERST', # Error status option
'LAMP', # Lamp(s) query (Includes fans)
'INST', # Input sources available query
'NAME', # Projector name query
'INF1', # Manufacturer name query
'INF2', # Product name query
'INFO', # Other information query
'CLSS' # PJLink class support query
]}
# Error and status codes
S_OK = E_OK = 0 # E_OK included since I sometimes forget
# Error codes. Start at 200 so we don't duplicate system error codes.
E_GENERAL = 200 # Unknown error
E_NOT_CONNECTED = 201
E_FAN = 202
E_LAMP = 203
E_TEMP = 204
E_COVER = 205
E_FILTER = 206
E_NO_AUTHENTICATION = 207 # PIN set and no authentication set on projector
E_UNDEFINED = 208 # ERR1
E_PARAMETER = 209 # ERR2
E_UNAVAILABLE = 210 # ERR3
E_PROJECTOR = 211 # ERR4
E_INVALID_DATA = 212
E_WARN = 213
E_ERROR = 214
E_AUTHENTICATION = 215 # ERRA
E_CLASS = 216
E_PREFIX = 217
# Remap Qt socket error codes to projector error codes
E_CONNECTION_REFUSED = 230
E_REMOTE_HOST_CLOSED_CONNECTION = 231
E_HOST_NOT_FOUND = 232
E_SOCKET_ACCESS = 233
E_SOCKET_RESOURCE = 234
E_SOCKET_TIMEOUT = 235
E_DATAGRAM_TOO_LARGE = 236
E_NETWORK = 237
E_ADDRESS_IN_USE = 238
E_SOCKET_ADDRESS_NOT_AVAILABLE = 239
E_UNSUPPORTED_SOCKET_OPERATION = 240
E_PROXY_AUTHENTICATION_REQUIRED = 241
E_SLS_HANDSHAKE_FAILED = 242
E_UNFINISHED_SOCKET_OPERATION = 243
E_PROXY_CONNECTION_REFUSED = 244
E_PROXY_CONNECTION_CLOSED = 245
E_PROXY_CONNECTION_TIMEOUT = 246
E_PROXY_NOT_FOUND = 247
E_PROXY_PROTOCOL = 248
E_UNKNOWN_SOCKET_ERROR = -1
# Status codes start at 300
S_NOT_CONNECTED = 300
S_CONNECTING = 301
S_CONNECTED = 302
S_INITIALIZE = 303
S_STATUS = 304
S_OFF = 305
S_STANDBY = 306
S_WARMUP = 307
S_ON = 308
S_COOLDOWN = 309
S_INFO = 310
# Information that does not affect status
S_NETWORK_SENDING = 400
S_NETWORK_RECEIVED = 401
CONNECTION_ERRORS = {E_NOT_CONNECTED, E_NO_AUTHENTICATION, E_AUTHENTICATION, E_CLASS,
E_PREFIX, E_CONNECTION_REFUSED, E_REMOTE_HOST_CLOSED_CONNECTION,
E_HOST_NOT_FOUND, E_SOCKET_ACCESS, E_SOCKET_RESOURCE, E_SOCKET_TIMEOUT,
E_DATAGRAM_TOO_LARGE, E_NETWORK, E_ADDRESS_IN_USE, E_SOCKET_ADDRESS_NOT_AVAILABLE,
E_UNSUPPORTED_SOCKET_OPERATION, E_PROXY_AUTHENTICATION_REQUIRED,
E_SLS_HANDSHAKE_FAILED, E_UNFINISHED_SOCKET_OPERATION, E_PROXY_CONNECTION_REFUSED,
E_PROXY_CONNECTION_CLOSED, E_PROXY_CONNECTION_TIMEOUT, E_PROXY_NOT_FOUND,
E_PROXY_PROTOCOL, E_UNKNOWN_SOCKET_ERROR
}
PJLINK_ERRORS = {'ERRA': E_AUTHENTICATION, # Authentication error
'ERR1': E_UNDEFINED, # Undefined command error
'ERR2': E_PARAMETER, # Invalid parameter error
'ERR3': E_UNAVAILABLE, # Projector busy
'ERR4': E_PROJECTOR, # Projector or display failure
E_AUTHENTICATION: translate('OpenLP.ProjectorConstants', 'ERRA'),
E_UNDEFINED: translate('OpenLP.ProjectorConstants', 'ERR1'),
E_PARAMETER: translate('OpenLP.ProjectorConstants', 'ERR2'),
E_UNAVAILABLE: translate('OpenLP.ProjectorConstants', 'ERR3'),
E_PROJECTOR: translate('OpenLP.ProjectorConstants', 'ERR4')}
# Map error/status codes to string
ERROR_STRING = {0: translate('OpenLP.ProjectorConstants', 'S_OK'),
E_GENERAL: translate('OpenLP.ProjectorConstants', 'E_GENERAL'),
E_NOT_CONNECTED: translate('OpenLP.ProjectorConstants', 'E_NOT_CONNECTED'),
E_FAN: translate('OpenLP.ProjectorConstants', 'E_FAN'),
E_LAMP: translate('OpenLP.ProjectorConstants', 'E_LAMP'),
E_TEMP: translate('OpenLP.ProjectorConstants', 'E_TEMP'),
E_COVER: translate('OpenLP.ProjectorConstants', 'E_COVER'),
E_FILTER: translate('OpenLP.ProjectorConstants', 'E_FILTER'),
E_AUTHENTICATION: translate('OpenLP.ProjectorConstants', 'E_AUTHENTICATION'),
E_NO_AUTHENTICATION: translate('OpenLP.ProjectorConstants', 'E_NO_AUTHENTICATION'),
E_UNDEFINED: translate('OpenLP.ProjectorConstants', 'E_UNDEFINED'),
E_PARAMETER: translate('OpenLP.ProjectorConstants', 'E_PARAMETER'),
E_UNAVAILABLE: translate('OpenLP.ProjectorConstants', 'E_UNAVAILABLE'),
E_PROJECTOR: translate('OpenLP.ProjectorConstants', 'E_PROJECTOR'),
E_INVALID_DATA: translate('OpenLP.ProjectorConstants', 'E_INVALID_DATA'),
E_WARN: translate('OpenLP.ProjectorConstants', 'E_WARN'),
E_ERROR: translate('OpenLP.ProjectorConstants', 'E_ERROR'),
E_CLASS: translate('OpenLP.ProjectorConstants', 'E_CLASS'),
E_PREFIX: translate('OpenLP.ProjectorConstants', 'E_PREFIX'), # Last projector error
E_CONNECTION_REFUSED: translate('OpenLP.ProjectorConstants',
'E_CONNECTION_REFUSED'), # First QtSocket error
E_REMOTE_HOST_CLOSED_CONNECTION: translate('OpenLP.ProjectorConstants',
'E_REMOTE_HOST_CLOSED_CONNECTION'),
E_HOST_NOT_FOUND: translate('OpenLP.ProjectorConstants', 'E_HOST_NOT_FOUND'),
E_SOCKET_ACCESS: translate('OpenLP.ProjectorConstants', 'E_SOCKET_ACCESS'),
E_SOCKET_RESOURCE: translate('OpenLP.ProjectorConstants', 'E_SOCKET_RESOURCE'),
E_SOCKET_TIMEOUT: translate('OpenLP.ProjectorConstants', 'E_SOCKET_TIMEOUT'),
E_DATAGRAM_TOO_LARGE: translate('OpenLP.ProjectorConstants', 'E_DATAGRAM_TOO_LARGE'),
E_NETWORK: translate('OpenLP.ProjectorConstants', 'E_NETWORK'),
E_ADDRESS_IN_USE: translate('OpenLP.ProjectorConstants', 'E_ADDRESS_IN_USE'),
E_SOCKET_ADDRESS_NOT_AVAILABLE: translate('OpenLP.ProjectorConstants',
'E_SOCKET_ADDRESS_NOT_AVAILABLE'),
E_UNSUPPORTED_SOCKET_OPERATION: translate('OpenLP.ProjectorConstants',
'E_UNSUPPORTED_SOCKET_OPERATION'),
E_PROXY_AUTHENTICATION_REQUIRED: translate('OpenLP.ProjectorConstants',
'E_PROXY_AUTHENTICATION_REQUIRED'),
E_SLS_HANDSHAKE_FAILED: translate('OpenLP.ProjectorConstants', 'E_SLS_HANDSHAKE_FAILED'),
E_UNFINISHED_SOCKET_OPERATION: translate('OpenLP.ProjectorConstants',
'E_UNFINISHED_SOCKET_OPERATION'),
E_PROXY_CONNECTION_REFUSED: translate('OpenLP.ProjectorConstants', 'E_PROXY_CONNECTION_REFUSED'),
E_PROXY_CONNECTION_CLOSED: translate('OpenLP.ProjectorConstants', 'E_PROXY_CONNECTION_CLOSED'),
E_PROXY_CONNECTION_TIMEOUT: translate('OpenLP.ProjectorConstants', 'E_PROXY_CONNECTION_TIMEOUT'),
E_PROXY_NOT_FOUND: translate('OpenLP.ProjectorConstants', 'E_PROXY_NOT_FOUND'),
E_PROXY_PROTOCOL: translate('OpenLP.ProjectorConstants', 'E_PROXY_PROTOCOL'),
E_UNKNOWN_SOCKET_ERROR: translate('OpenLP.ProjectorConstants', 'E_UNKNOWN_SOCKET_ERROR')}
STATUS_STRING = {S_NOT_CONNECTED: translate('OpenLP.ProjectorConstants', 'S_NOT_CONNECTED'),
S_CONNECTING: translate('OpenLP.ProjectorConstants', 'S_CONNECTING'),
S_CONNECTED: translate('OpenLP.ProjectorConstants', 'S_CONNECTED'),
S_STATUS: translate('OpenLP.ProjectorConstants', 'S_STATUS'),
S_OFF: translate('OpenLP.ProjectorConstants', 'S_OFF'),
S_INITIALIZE: translate('OpenLP.ProjectorConstants', 'S_INITIALIZE'),
S_STANDBY: translate('OpenLP.ProjectorConstants', 'S_STANDBY'),
S_WARMUP: translate('OpenLP.ProjectorConstants', 'S_WARMUP'),
S_ON: translate('OpenLP.ProjectorConstants', 'S_ON'),
S_COOLDOWN: translate('OpenLP.ProjectorConstants', 'S_COOLDOWN'),
S_INFO: translate('OpenLP.ProjectorConstants', 'S_INFO'),
S_NETWORK_SENDING: translate('OpenLP.ProjectorConstants', 'S_NETWORK_SENDING'),
S_NETWORK_RECEIVED: translate('OpenLP.ProjectorConstants', 'S_NETWORK_RECEIVED')}
# Map error/status codes to message strings
ERROR_MSG = {E_OK: translate('OpenLP.ProjectorConstants', 'OK'), # E_OK | S_OK
E_GENERAL: translate('OpenLP.ProjectorConstants', 'General projector error'),
E_NOT_CONNECTED: translate('OpenLP.ProjectorConstants', 'Not connected error'),
E_LAMP: translate('OpenLP.ProjectorConstants', 'Lamp error'),
E_FAN: translate('OpenLP.ProjectorConstants', 'Fan error'),
E_TEMP: translate('OpenLP.ProjectorConstants', 'High temperature detected'),
E_COVER: translate('OpenLP.ProjectorConstants', 'Cover open detected'),
E_FILTER: translate('OpenLP.ProjectorConstants', 'Check filter'),
E_AUTHENTICATION: translate('OpenLP.ProjectorConstants', 'Authentication Error'),
E_UNDEFINED: translate('OpenLP.ProjectorConstants', 'Undefined Command'),
E_PARAMETER: translate('OpenLP.ProjectorConstants', 'Invalid Parameter'),
E_UNAVAILABLE: translate('OpenLP.ProjectorConstants', 'Projector Busy'),
E_PROJECTOR: translate('OpenLP.ProjectorConstants', 'Projector/Display Error'),
E_INVALID_DATA: translate('OpenLP.ProjectorConstants', 'Invalid packet received'),
E_WARN: translate('OpenLP.ProjectorConstants', 'Warning condition detected'),
E_ERROR: translate('OpenLP.ProjectorConstants', 'Error condition detected'),
E_CLASS: translate('OpenLP.ProjectorConstants', 'PJLink class not supported'),
E_PREFIX: translate('OpenLP.ProjectorConstants', 'Invalid prefix character'),
E_CONNECTION_REFUSED: translate('OpenLP.ProjectorConstants',
'The connection was refused by the peer (or timed out)'),
E_REMOTE_HOST_CLOSED_CONNECTION: translate('OpenLP.ProjectorConstants',
'The remote host closed the connection'),
E_HOST_NOT_FOUND: translate('OpenLP.ProjectorConstants', 'The host address was not found'),
E_SOCKET_ACCESS: translate('OpenLP.ProjectorConstants',
'The socket operation failed because the application '
'lacked the required privileges'),
E_SOCKET_RESOURCE: translate('OpenLP.ProjectorConstants',
'The local system ran out of resources (e.g., too many sockets)'),
E_SOCKET_TIMEOUT: translate('OpenLP.ProjectorConstants',
'The socket operation timed out'),
E_DATAGRAM_TOO_LARGE: translate('OpenLP.ProjectorConstants',
'The datagram was larger than the operating system\'s limit'),
E_NETWORK: translate('OpenLP.ProjectorConstants',
'An error occurred with the network (Possibly someone pulled the plug?)'),
E_ADDRESS_IN_USE: translate('OpenLP.ProjectorConstants',
'The address specified with socket.bind() '
'is already in use and was set to be exclusive'),
E_SOCKET_ADDRESS_NOT_AVAILABLE: translate('OpenLP.ProjectorConstants',
'The address specified to socket.bind() '
'does not belong to the host'),
E_UNSUPPORTED_SOCKET_OPERATION: translate('OpenLP.ProjectorConstants',
'The requested socket operation is not supported by the local '
'operating system (e.g., lack of IPv6 support)'),
E_PROXY_AUTHENTICATION_REQUIRED: translate('OpenLP.ProjectorConstants',
'The socket is using a proxy, '
'and the proxy requires authentication'),
E_SLS_HANDSHAKE_FAILED: translate('OpenLP.ProjectorConstants',
'The SSL/TLS handshake failed'),
E_UNFINISHED_SOCKET_OPERATION: translate('OpenLP.ProjectorConstants',
'The last operation attempted has not finished yet '
'(still in progress in the background)'),
E_PROXY_CONNECTION_REFUSED: translate('OpenLP.ProjectorConstants',
'Could not contact the proxy server because the connection '
'to that server was denied'),
E_PROXY_CONNECTION_CLOSED: translate('OpenLP.ProjectorConstants',
'The connection to the proxy server was closed unexpectedly '
'(before the connection to the final peer was established)'),
E_PROXY_CONNECTION_TIMEOUT: translate('OpenLP.ProjectorConstants',
'The connection to the proxy server timed out or the proxy '
'server stopped responding in the authentication phase.'),
E_PROXY_NOT_FOUND: translate('OpenLP.ProjectorConstants',
'The proxy address set with setProxy() was not found'),
E_PROXY_PROTOCOL: translate('OpenLP.ProjectorConstants',
'The connection negotiation with the proxy server because the response '
'from the proxy server could not be understood'),
E_UNKNOWN_SOCKET_ERROR: translate('OpenLP.ProjectorConstants', 'An unidentified error occurred'),
S_NOT_CONNECTED: translate('OpenLP.ProjectorConstants', 'Not connected'),
S_CONNECTING: translate('OpenLP.ProjectorConstants', 'Connecting'),
S_CONNECTED: translate('OpenLP.ProjectorConstants', 'Connected'),
S_STATUS: translate('OpenLP.ProjectorConstants', 'Getting status'),
S_OFF: translate('OpenLP.ProjectorConstants', 'Off'),
S_INITIALIZE: translate('OpenLP.ProjectorConstants', 'Initialize in progress'),
S_STANDBY: translate('OpenLP.ProjectorConstants', 'Power in standby'),
S_WARMUP: translate('OpenLP.ProjectorConstants', 'Warmup in progress'),
S_ON: translate('OpenLP.ProjectorConstants', 'Power is on'),
S_COOLDOWN: translate('OpenLP.ProjectorConstants', 'Cooldown in progress'),
S_INFO: translate('OpenLP.ProjectorConstants', 'Projector Information available'),
S_NETWORK_SENDING: translate('OpenLP.ProjectorConstants', 'Sending data'),
S_NETWORK_RECEIVED: translate('OpenLP.ProjectorConstants', 'Received data')}
# Map for ERST return codes to string
PJLINK_ERST_STATUS = {'0': ERROR_STRING[E_OK],
'1': ERROR_STRING[E_WARN],
'2': ERROR_STRING[E_ERROR]}
# Map for POWR return codes to status code
PJLINK_POWR_STATUS = {'0': S_STANDBY,
'1': S_ON,
'2': S_COOLDOWN,
'3': S_WARMUP}
PJLINK_DEFAULT_SOURCES = {'1': translate('OpenLP.DB', 'RGB'),
'2': translate('OpenLP.DB', 'Video'),
'3': translate('OpenLP.DB', 'Digital'),
'4': translate('OpenLP.DB', 'Storage'),
'5': translate('OpenLP.DB', 'Network')}
PJLINK_DEFAULT_CODES = {'11': translate('OpenLP.DB', 'RGB 1'),
'12': translate('OpenLP.DB', 'RGB 2'),
'13': translate('OpenLP.DB', 'RGB 3'),
'14': translate('OpenLP.DB', 'RGB 4'),
'15': translate('OpenLP.DB', 'RGB 5'),
'16': translate('OpenLP.DB', 'RGB 6'),
'17': translate('OpenLP.DB', 'RGB 7'),
'18': translate('OpenLP.DB', 'RGB 8'),
'19': translate('OpenLP.DB', 'RGB 9'),
'21': translate('OpenLP.DB', 'Video 1'),
'22': translate('OpenLP.DB', 'Video 2'),
'23': translate('OpenLP.DB', 'Video 3'),
'24': translate('OpenLP.DB', 'Video 4'),
'25': translate('OpenLP.DB', 'Video 5'),
'26': translate('OpenLP.DB', 'Video 6'),
'27': translate('OpenLP.DB', 'Video 7'),
'28': translate('OpenLP.DB', 'Video 8'),
'29': translate('OpenLP.DB', 'Video 9'),
'31': translate('OpenLP.DB', 'Digital 1'),
'32': translate('OpenLP.DB', 'Digital 2'),
'33': translate('OpenLP.DB', 'Digital 3'),
'34': translate('OpenLP.DB', 'Digital 4'),
'35': translate('OpenLP.DB', 'Digital 5'),
'36': translate('OpenLP.DB', 'Digital 6'),
'37': translate('OpenLP.DB', 'Digital 7'),
'38': translate('OpenLP.DB', 'Digital 8'),
'39': translate('OpenLP.DB', 'Digital 9'),
'41': translate('OpenLP.DB', 'Storage 1'),
'42': translate('OpenLP.DB', 'Storage 2'),
'43': translate('OpenLP.DB', 'Storage 3'),
'44': translate('OpenLP.DB', 'Storage 4'),
'45': translate('OpenLP.DB', 'Storage 5'),
'46': translate('OpenLP.DB', 'Storage 6'),
'47': translate('OpenLP.DB', 'Storage 7'),
'48': translate('OpenLP.DB', 'Storage 8'),
'49': translate('OpenLP.DB', 'Storage 9'),
'51': translate('OpenLP.DB', 'Network 1'),
'52': translate('OpenLP.DB', 'Network 2'),
'53': translate('OpenLP.DB', 'Network 3'),
'54': translate('OpenLP.DB', 'Network 4'),
'55': translate('OpenLP.DB', 'Network 5'),
'56': translate('OpenLP.DB', 'Network 6'),
'57': translate('OpenLP.DB', 'Network 7'),
'58': translate('OpenLP.DB', 'Network 8'),
'59': translate('OpenLP.DB', 'Network 9')
}

View File

@ -0,0 +1,436 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2014 Raoul Snyman #
# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan #
# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
# Christian Richter, Philip Ridout, Ken Roberts, Simon Scudder, #
# Jeffrey Smith, Maikel Stuivenberg, Martin Thompson, Jon Tibble, #
# Dave Warnock, Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
:mod:`openlp.core.lib.projector.db` module
Provides the database functions for the Projector module.
The Manufacturer, Model, Source tables keep track of the video source
strings used for display of input sources. The Source table maps
manufacturer-defined or user-defined strings from PJLink default strings
to end-user readable strings; ex: PJLink code 11 would map "RGB 1"
default string to "RGB PC (analog)" string.
(Future feature).
The Projector table keeps track of entries for controlled projectors.
"""
import logging
log = logging.getLogger(__name__)
log.debug('projector.lib.db module loaded')
from sqlalchemy import Column, ForeignKey, Integer, MetaData, String, and_
from sqlalchemy.ext.declarative import declarative_base, declared_attr
from sqlalchemy.orm import backref, relationship
from openlp.core.lib.db import Manager, init_db, init_url
from openlp.core.lib.projector.constants import PJLINK_DEFAULT_CODES
metadata = MetaData()
Base = declarative_base(metadata)
class CommonBase(object):
"""
Base class to automate table name and ID column.
"""
@declared_attr
def __tablename__(cls):
return cls.__name__.lower()
id = Column(Integer, primary_key=True)
class Manufacturer(CommonBase, Base):
"""
Projector manufacturer table.
Manufacturer:
name: Column(String(30))
models: Relationship(Model.id)
Model table is related.
"""
def __repr__(self):
"""
Returns a basic representation of a Manufacturer table entry.
"""
return '<Manufacturer(name="%s")>' % self.name
name = Column(String(30))
models = relationship('Model',
order_by='Model.name',
backref='manufacturer',
cascade='all, delete-orphan',
primaryjoin='Manufacturer.id==Model.manufacturer_id',
lazy='joined')
class Model(CommonBase, Base):
"""
Projector model table.
Model:
name: Column(String(20))
sources: Relationship(Source.id)
manufacturer_id: Foreign_key(Manufacturer.id)
Manufacturer table links here.
Source table is related.
"""
def __repr__(self):
"""
Returns a basic representation of a Model table entry.
"""
return '<Model(name=%s)>' % self.name
manufacturer_id = Column(Integer, ForeignKey('manufacturer.id'))
name = Column(String(20))
sources = relationship('Source',
order_by='Source.pjlink_name',
backref='model',
cascade='all, delete-orphan',
primaryjoin='Model.id==Source.model_id',
lazy='joined')
class Source(CommonBase, Base):
"""
Projector video source table.
Source:
pjlink_name: Column(String(15))
pjlink_code: Column(String(2))
text: Column(String(30))
model_id: Foreign_key(Model.id)
Model table links here.
These entries map PJLink input video source codes to text strings.
"""
def __repr__(self):
"""
Return basic representation of Source table entry.
"""
return '<Source(pjlink_name="%s", pjlink_code="%s", text="%s")>' % \
(self.pjlink_name, self.pjlink_code, self.text)
model_id = Column(Integer, ForeignKey('model.id'))
pjlink_name = Column(String(15))
pjlink_code = Column(String(2))
text = Column(String(30))
class Projector(CommonBase, Base):
"""
Projector table.
Projector:
ip: Column(String(100)) # Allow for IPv6 or FQDN
port: Column(String(8))
pin: Column(String(20)) # Allow for test strings
name: Column(String(20))
location: Column(String(30))
notes: Column(String(200))
pjlink_name: Column(String(128)) # From projector (future)
manufacturer: Column(String(128)) # From projector (future)
model: Column(String(128)) # From projector (future)
other: Column(String(128)) # From projector (future)
sources: Column(String(128)) # From projector (future)
ProjectorSource relates
"""
def __repr__(self):
"""
Return basic representation of Source table entry.
"""
return '< Projector(id="%s", ip="%s", port="%s", pin="%s", name="%s", location="%s",' \
'notes="%s", pjlink_name="%s", manufacturer="%s", model="%s", other="%s",' \
'sources="%s", source_list="%s") >' % (self.id, self.ip, self.port, self.pin, self.name, self.location,
self.notes, self.pjlink_name, self.manufacturer, self.model,
self.other, self.sources, self.source_list)
ip = Column(String(100))
port = Column(String(8))
pin = Column(String(20))
name = Column(String(20))
location = Column(String(30))
notes = Column(String(200))
pjlink_name = Column(String(128))
manufacturer = Column(String(128))
model = Column(String(128))
other = Column(String(128))
sources = Column(String(128))
source_list = relationship('ProjectorSource',
order_by='ProjectorSource.code',
backref='projector',
cascade='all, delete-orphan',
primaryjoin='Projector.id==ProjectorSource.projector_id',
lazy='joined')
class ProjectorSource(CommonBase, Base):
"""
Projector local source table
This table allows mapping specific projector source input to a local
connection; i.e., '11': 'DVD Player'
Projector Source:
projector_id: Foreign_key(Column(Projector.id))
code: Column(String(3)) # PJLink source code
text: Column(String(20)) # Text to display
Projector table links here
"""
def __repr__(self):
"""
Return basic representation of Source table entry.
"""
return '<ProjectorSource(id="%s", code="%s", text="%s", projector_id="%s")>' % (self.id,
self.code,
self.text,
self.projector_id)
code = Column(String(3))
text = Column(String(20))
projector_id = Column(Integer, ForeignKey('projector.id'))
class ProjectorDB(Manager):
"""
Class to access the projector database.
"""
def __init__(self, *args, **kwargs):
log.debug('ProjectorDB().__init__(args="%s", kwargs="%s")' % (args, kwargs))
super().__init__(plugin_name='projector',
init_schema=self.init_schema)
log.debug('ProjectorDB() Initialized using db url %s' % self.db_url)
def init_schema(*args, **kwargs):
"""
Setup the projector database and initialize the schema.
Declarative uses table classes to define schema.
"""
url = init_url('projector')
session, metadata = init_db(url, base=Base)
Base.metadata.create_all(checkfirst=True)
return session
def get_projector_by_id(self, dbid):
"""
Locate a DB record by record ID.
:param dbid: DB record id
:returns: Projector() instance
"""
log.debug('get_projector_by_id(id="%s")' % dbid)
projector = self.get_object_filtered(Projector, Projector.id == dbid)
if projector is None:
# Not found
log.warn('get_projector_by_id() did not find %s' % id)
return None
log.debug('get_projectorby_id() returning 1 entry for "%s" id="%s"' % (dbid, projector.id))
return projector
def get_projector_all(self):
"""
Retrieve all projector entries.
:returns: List with Projector() instances used in Manager() QListWidget.
"""
log.debug('get_all() called')
return_list = []
new_list = self.get_all_objects(Projector)
if new_list is None or new_list.count == 0:
return return_list
for new_projector in new_list:
return_list.append(new_projector)
log.debug('get_all() returning %s item(s)' % len(return_list))
return return_list
def get_projector_by_ip(self, ip):
"""
Locate a projector by host IP/Name.
:param ip: Host IP/Name
:returns: Projector() instance
"""
log.debug('get_projector_by_ip(ip="%s")' % ip)
projector = self.get_object_filtered(Projector, Projector.ip == ip)
if projector is None:
# Not found
log.warn('get_projector_by_ip() did not find %s' % ip)
return None
log.debug('get_projectorby_ip() returning 1 entry for "%s" id="%s"' % (ip, projector.id))
return projector
def get_projector_by_name(self, name):
"""
Locate a projector by name field
:param name: Name of projector
:returns: Projector() instance
"""
log.debug('get_projector_by_name(name="%s")' % name)
projector = self.get_object_filtered(Projector, Projector.name == name)
if projector is None:
# Not found
log.warn('get_projector_by_name() did not find "%s"' % name)
return None
log.debug('get_projector_by_name() returning one entry for "%s" id="%s"' % (name, projector.id))
return projector
def add_projector(self, projector):
"""
Add a new projector entry
:param projector: Projector() instance to add
:returns: bool
True if entry added
False if entry already in DB or db error
"""
old_projector = self.get_object_filtered(Projector, Projector.ip == projector.ip)
if old_projector is not None:
log.warn('add_new() skipping entry ip="%s" (Already saved)' % old_projector.ip)
return False
log.debug('add_new() saving new entry')
log.debug('ip="%s", name="%s", location="%s"' % (projector.ip,
projector.name,
projector.location))
log.debug('notes="%s"' % projector.notes)
return self.save_object(projector)
def update_projector(self, projector=None):
"""
Update projector entry
:param projector: Projector() instance with new information
:returns: bool
True if DB record updated
False if entry not in DB or DB error
"""
if projector is None:
log.error('No Projector() instance to update - cancelled')
return False
old_projector = self.get_object_filtered(Projector, Projector.id == projector.id)
if old_projector is None:
log.error('Edit called on projector instance not in database - cancelled')
return False
log.debug('(%s) Updating projector with dbid=%s' % (projector.ip, projector.id))
old_projector.ip = projector.ip
old_projector.name = projector.name
old_projector.location = projector.location
old_projector.pin = projector.pin
old_projector.port = projector.port
old_projector.pjlink_name = projector.pjlink_name
old_projector.manufacturer = projector.manufacturer
old_projector.model = projector.model
old_projector.other = projector.other
old_projector.sources = projector.sources
return self.save_object(old_projector)
def delete_projector(self, projector):
"""
Delete an entry by record id
:param projector: Projector() instance to delete
:returns: bool
True if record deleted
False if DB error
"""
deleted = self.delete_object(Projector, projector.id)
if deleted:
log.debug('delete_by_id() Removed entry id="%s"' % projector.id)
else:
log.error('delete_by_id() Entry id="%s" not deleted for some reason' % projector.id)
return deleted
def get_source_list(self, projector):
"""
Retrieves the source inputs pjlink code-to-text if available based on
manufacturer and model.
If not available, then returns the PJLink code to default text.
:param projector: Projector instance
:returns: dict
key: (str) PJLink code for source
value: (str) From ProjectorSource, Sources tables or PJLink default code list
"""
source_dict = {}
# Get default list first
for key in projector.source_available:
item = self.get_object_filtered(ProjectorSource,
and_(ProjectorSource.code == key,
ProjectorSource.projector_id == projector.dbid))
if item is None:
source_dict[key] = PJLINK_DEFAULT_CODES[key]
else:
source_dict[key] = item.text
return source_dict
def get_source_by_id(self, source):
"""
Retrieves the ProjectorSource by ProjectorSource.id
:param source: ProjectorSource id
:returns: ProjetorSource instance or None
"""
source_entry = self.get_object_filtered(ProjetorSource, ProjectorSource.id == source)
if source_entry is None:
# Not found
log.warn('get_source_by_id() did not find "%s"' % source)
return None
log.debug('get_source_by_id() returning one entry for "%s""' % (source))
return source_entry
def get_source_by_code(self, code, projector_id):
"""
Retrieves the ProjectorSource by ProjectorSource.id
:param source: PJLink ID
:param projector_id: Projector.id
:returns: ProjetorSource instance or None
"""
source_entry = self.get_object_filtered(ProjectorSource,
and_(ProjectorSource.code == code,
ProjectorSource.projector_id == projector_id))
if source_entry is None:
# Not found
log.warn('get_source_by_id() did not find code="%s" projector_id="%s"' % (code, projector_id))
return None
log.debug('get_source_by_id() returning one entry for code="%s" projector_id="%s"' % (code, projector_id))
return source_entry
def add_source(self, source):
"""
Add a new ProjectorSource record
:param source: ProjectorSource() instance to add
"""
log.debug('Saving ProjectorSource(projector_id="%s" code="%s" text="%s")' % (source.projector_id,
source.code, source.text))
return self.save_object(source)

View File

@ -0,0 +1,913 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2014 Raoul Snyman #
# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan #
# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
# Christian Richter, Philip Ridout, Ken Roberts, Simon Scudder, #
# Jeffrey Smith, Maikel Stuivenberg, Martin Thompson, Jon Tibble, #
# Dave Warnock, Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
:mod:`openlp.core.lib.projector.pjlink1` 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
Section 5-1 PJLink Specifications
Section 5-5 Guidelines for Input Terminals
NOTE:
Function names follow the following syntax:
def process_CCCC(...):
WHERE:
CCCC = PJLink command being processed.
"""
import logging
log = logging.getLogger(__name__)
log.debug('pjlink1 loaded')
__all__ = ['PJLink1']
from codecs import decode
from PyQt4.QtCore import pyqtSignal, pyqtSlot
from PyQt4.QtNetwork import QAbstractSocket, QTcpSocket
from openlp.core.common import translate, qmd5_hash
from openlp.core.lib.projector.constants import *
# Shortcuts
SocketError = QAbstractSocket.SocketError
SocketSTate = QAbstractSocket.SocketState
PJLINK_PREFIX = '%'
PJLINK_CLASS = '1'
PJLINK_HEADER = '%s%s' % (PJLINK_PREFIX, PJLINK_CLASS)
PJLINK_SUFFIX = CR
class PJLink1(QTcpSocket):
"""
Socket service for connecting to a PJLink-capable projector.
"""
# Signals sent by this module
changeStatus = pyqtSignal(str, int, str)
projectorNetwork = pyqtSignal(int) # Projector network activity
projectorStatus = pyqtSignal(int) # Status update
projectorAuthentication = pyqtSignal(str) # Authentication error
projectorNoAuthentication = pyqtSignal(str) # PIN set and no authentication needed
projectorReceivedData = pyqtSignal() # Notify when received data finished processing
projectorUpdateIcons = pyqtSignal() # Update the status icons on toolbar
def __init__(self, name=None, ip=None, port=PJLINK_PORT, pin=None, *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
"""
log.debug('PJlink(args="%s" kwargs="%s")' % (args, kwargs))
self.name = name
self.ip = ip
self.port = port
self.pin = pin
super(PJLink1, self).__init__()
self.dbid = None
self.location = None
self.notes = None
self.dbid = None if 'dbid' not in kwargs else kwargs['dbid']
self.location = None if 'location' not in kwargs else kwargs['notes']
self.notes = None if 'notes' not in kwargs else kwargs['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.maxSize = PJLINK_MAX_PACKET + 2
self.setReadBufferSize(self.maxSize)
# 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.PJLINK1_FUNC = {'AVMT': self.process_avmt,
'CLSS': self.process_clss,
'ERST': self.process_erst,
'INFO': self.process_info,
'INF1': self.process_inf1,
'INF2': self.process_inf2,
'INPT': self.process_inpt,
'INST': self.process_inst,
'LAMP': self.process_lamp,
'NAME': self.process_name,
'PJLINK': self.check_login,
'POWR': self.process_powr
}
def reset_information(self):
"""
Reset projector-specific information to default
"""
log.debug('(%s) reset_information() connect status is %s' % (self.ip, self.state()))
self.power = S_OFF
self.pjlink_name = None
self.manufacturer = None
self.model = None
self.shutter = None
self.mute = None
self.lamp = None
self.fan = None
self.source_available = None
self.source = None
self.other_info = None
if hasattr(self, 'timer'):
self.timer.stop()
if hasattr(self, 'socket_timer'):
self.socket_timer.stop()
self.send_queue = []
self.send_busy = False
def thread_started(self):
"""
Connects signals to methods when thread is started.
"""
log.debug('(%s) Thread starting' % self.ip)
self.i_am_running = True
self.connected.connect(self.check_login)
self.disconnected.connect(self.disconnect_from_host)
self.error.connect(self.get_error)
def thread_stopped(self):
"""
Cleanups when thread is stopped.
"""
log.debug('(%s) Thread stopped' % self.ip)
try:
self.connected.disconnect(self.check_login)
except TypeError:
pass
try:
self.disconnected.disconnect(self.disconnect_from_host)
except TypeError:
pass
try:
self.error.disconnect(self.get_error)
except TypeError:
pass
try:
self.projectorReceivedData.disconnect(self._send_command)
except TypeError:
pass
self.disconnect_from_host()
self.deleteLater()
self.i_am_running = False
def socket_abort(self):
"""
Aborts connection and closes socket in case of brain-dead projectors.
Should normally be called by socket_timer().
"""
log.debug('(%s) socket_abort() - Killing connection' % self.ip)
self.disconnect_from_host(abort=True)
def poll_loop(self):
"""
Retrieve information from projector that changes.
Normally called by timer().
"""
if self.state() != self.ConnectedState:
return
log.debug('(%s) Updating projector status' % self.ip)
# Reset timer in case we were called from a set command
if self.timer.interval() < self.poll_time:
# Reset timer to 5 seconds
self.timer.setInterval(self.poll_time)
# Restart timer
self.timer.start()
# These commands may change during connetion
for command in ['POWR', 'ERST', 'LAMP', 'AVMT', 'INPT']:
self.send_command(command, queue=True)
# The following commands do not change, so only check them once
if self.power == S_ON and self.source_available is None:
self.send_command('INST', queue=True)
if self.other_info is None:
self.send_command('INFO', queue=True)
if self.manufacturer is None:
self.send_command('INF1', queue=True)
if self.model is None:
self.send_command('INF2', queue=True)
if self.pjlink_name is None:
self.send_command('NAME', queue=True)
if self.power == S_ON and self.source_available is None:
self.send_command('INST', queue=True)
def _get_status(self, status):
"""
Helper to retrieve status/error codes and convert to strings.
:param status: Status/Error code
:returns: (Status/Error code, String)
"""
if status in ERROR_STRING:
return ERROR_STRING[status], ERROR_MSG[status]
elif status in STATUS_STRING:
return STATUS_STRING[status], ERROR_MSG[status]
else:
return status, translate('OpenLP.PJLink1', 'Unknown status')
def change_status(self, status, msg=None):
"""
Check connection/error status, set status for projector, then emit status change signal
for gui to allow changing the icons.
:param status: Status code
:param msg: Optional message
"""
message = translate('OpenLP.PJLink1', 'No message') if msg is None else msg
(code, message) = self._get_status(status)
if msg is not None:
message = msg
if status in CONNECTION_ERRORS:
# Projector, connection state
self.projector_status = self.error_status = self.status_connect = E_NOT_CONNECTED
elif status >= S_NOT_CONNECTED and status < S_STATUS:
self.status_connect = status
self.projector_status = S_NOT_CONNECTED
elif status < S_NETWORK_SENDING:
self.status_connect = S_CONNECTED
self.projector_status = status
(status_code, status_message) = self._get_status(self.status_connect)
log.debug('(%s) status_connect: %s: %s' % (self.ip, status_code, status_message if msg is None else msg))
(status_code, status_message) = self._get_status(self.projector_status)
log.debug('(%s) projector_status: %s: %s' % (self.ip, status_code, status_message if msg is None else msg))
(status_code, status_message) = self._get_status(self.error_status)
log.debug('(%s) error_status: %s: %s' % (self.ip, status_code, status_message if msg is None else msg))
self.changeStatus.emit(self.ip, status, message)
@pyqtSlot()
def check_login(self, data=None):
"""
Processes the initial connection and authentication (if needed).
Starts poll timer if connection is established.
:param data: Optional data if called from another routine
"""
log.debug('(%s) check_login(data="%s")' % (self.ip, data))
if data is None:
# Reconnected setup?
if not self.waitForReadyRead(2000):
# Possible timeout issue
log.error('(%s) Socket timeout waiting for login' % self.ip)
self.change_status(E_SOCKET_TIMEOUT)
return
read = self.readLine(self.maxSize)
dontcare = self.readLine(self.maxSize) # Clean out the trailing \r\n
if read is None:
log.warn('(%s) read is None - socket error?' % self.ip)
return
elif len(read) < 8:
log.warn('(%s) Not enough data read)' % self.ip)
return
data = decode(read, 'ascii')
# Possibility of extraneous data on input when reading.
# Clean out extraneous characters in buffer.
dontcare = self.readLine(self.maxSize)
log.debug('(%s) check_login() read "%s"' % (self.ip, data.strip()))
# At this point, we should only have the initial login prompt with
# possible authentication
# PJLink initial login will be:
# 'PJLink 0' - Unauthenticated login - no extra steps required.
# 'PJLink 1 XXXXXX' Authenticated login - extra processing required.
if not data.upper().startswith('PJLINK'):
# Invalid response
return self.disconnect_from_host()
if '=' in data:
# Processing a login reply
data_check = data.strip().split('=')
else:
# Process initial connection
data_check = data.strip().split(' ')
log.debug('(%s) data_check="%s"' % (self.ip, data_check))
# Check for projector reporting an error
if data_check[1].upper() == 'ERRA':
# Authentication error
self.disconnect_from_host()
self.change_status(E_AUTHENTICATION)
log.debug('(%s) emitting projectorAuthentication() signal' % self.name)
return
elif data_check[1] == '0' and self.pin is not None:
# Pin set and no authentication needed
self.disconnect_from_host()
self.change_status(E_AUTHENTICATION)
log.debug('(%s) emitting projectorNoAuthentication() signal' % self.name)
self.projectorNoAuthentication.emit(self.name)
return
elif data_check[1] == '1':
# Authenticated login with salt
log.debug('(%s) Setting hash with salt="%s"' % (self.ip, data_check[2]))
log.debug('(%s) pin="%s"' % (self.ip, self.pin))
salt = qmd5_hash(salt=data_check[2], data=self.pin)
else:
salt = None
# We're connected at this point, so go ahead and do regular I/O
self.readyRead.connect(self.get_data)
self.projectorReceivedData.connect(self._send_command)
# Initial data we should know about
self.send_command(cmd='CLSS', salt=salt)
self.waitForReadyRead()
if (not self.no_poll) and (self.state() == self.ConnectedState):
log.debug('(%s) Starting timer' % self.ip)
self.timer.setInterval(2000) # Set 2 seconds for initial information
self.timer.start()
@pyqtSlot()
def get_data(self):
"""
Socket interface to retrieve data.
"""
log.debug('(%s) get_data(): Reading data' % self.ip)
if self.state() != self.ConnectedState:
log.debug('(%s) get_data(): Not connected - returning' % self.ip)
self.send_busy = False
return
read = self.readLine(self.maxSize)
if read == -1:
# No data available
log.debug('(%s) get_data(): No data available (-1)' % self.ip)
self.send_busy = False
self.projectorReceivedData.emit()
return
self.socket_timer.stop()
self.projectorNetwork.emit(S_NETWORK_RECEIVED)
data_in = decode(read, 'ascii')
data = data_in.strip()
if len(data) < 7:
# Not enough data for a packet
log.debug('(%s) get_data(): Packet length < 7: "%s"' % (self.ip, data))
self.send_busy = False
self.projectorReceivedData.emit()
return
log.debug('(%s) get_data(): Checking new data "%s"' % (self.ip, data))
if data.upper().startswith('PJLINK'):
# Reconnected from remote host disconnect ?
self.check_login(data)
self.send_busy = False
self.projectorReceivedData.emit()
return
elif '=' not in data:
log.warn('(%s) get_data(): Invalid packet received' % self.ip)
self.send_busy = False
self.projectorReceivedData.emit()
return
data_split = data.split('=')
try:
(prefix, class_, cmd, data) = (data_split[0][0], data_split[0][1], data_split[0][2:], data_split[1])
except ValueError as e:
log.warn('(%s) get_data(): Invalid packet - expected header + command + data' % self.ip)
log.warn('(%s) get_data(): Received data: "%s"' % (self.ip, read))
self.change_status(E_INVALID_DATA)
self.send_busy = False
self.projectorReceivedData.emit()
return
if not (self.pjlink_class in PJLINK_VALID_CMD and cmd in PJLINK_VALID_CMD[self.pjlink_class]):
log.warn('(%s) get_data(): Invalid packet - unknown command "%s"' % (self.ip, cmd))
self.send_busy = False
self.projectorReceivedData.emit()
return
return self.process_command(cmd, data)
@pyqtSlot(int)
def get_error(self, err):
"""
Process error from SocketError signal.
Remaps system error codes to projector error codes.
:param err: Error code
"""
log.debug('(%s) get_error(err=%s): %s' % (self.ip, err, self.errorString()))
if err <= 18:
# QSocket errors. Redefined in projector.constants so we don't mistake
# them for system errors
check = err + E_CONNECTION_REFUSED
self.timer.stop()
else:
check = err
if check < E_GENERAL:
# Some system error?
self.change_status(err, self.errorString())
else:
self.change_status(E_NETWORK, self.errorString())
self.projectorUpdateIcons.emit()
if self.status_connect == E_NOT_CONNECTED:
self.abort()
self.reset_information()
return
def send_command(self, cmd, opts='?', salt=None, queue=False):
"""
Add command to output queue if not already in queue.
:param cmd: Command to send
:param opts: Command option (if any) - defaults to '?' (get information)
:param salt: Optional salt for md5 hash initial authentication
:param queue: Option to force add to queue rather than sending directly
"""
if self.state() != self.ConnectedState:
log.warn('(%s) send_command(): Not connected - returning' % self.ip)
self.send_queue = []
return
self.projectorNetwork.emit(S_NETWORK_SENDING)
log.debug('(%s) send_command(): Building cmd="%s" opts="%s" %s' % (self.ip,
cmd,
opts,
'' if salt is None else 'with hash'))
if salt is None:
out = '%s%s %s%s' % (PJLINK_HEADER, cmd, opts, CR)
else:
out = '%s%s%s %s%s' % (salt, PJLINK_HEADER, cmd, opts, CR)
if out in self.send_queue:
# Already there, so don't add
log.debug('(%s) send_command(out="%s") Already in queue - skipping' % (self.ip, out.strip()))
elif not queue and len(self.send_queue) == 0:
# Nothing waiting to send, so just send it
log.debug('(%s) send_command(out="%s") Sending data' % (self.ip, out.strip()))
return self._send_command(data=out)
else:
log.debug('(%s) send_command(out="%s") adding to queue' % (self.ip, out.strip()))
self.send_queue.append(out)
self.projectorReceivedData.emit()
log.debug('(%s) send_command(): send_busy is %s' % (self.ip, self.send_busy))
if not self.send_busy:
log.debug('(%s) send_command() calling _send_string()')
self._send_command()
@pyqtSlot()
def _send_command(self, data=None):
"""
Socket interface to send data. If data=None, then check queue.
:param data: Immediate data to send
"""
log.debug('(%s) _send_string()' % self.ip)
log.debug('(%s) _send_string(): Connection status: %s' % (self.ip, self.state()))
if self.state() != self.ConnectedState:
log.debug('(%s) _send_string() Not connected - abort' % self.ip)
self.send_queue = []
self.send_busy = False
return
if self.send_busy:
# Still waiting for response from last command sent
return
if data is not None:
out = data
log.debug('(%s) _send_string(data=%s)' % (self.ip, out.strip()))
elif len(self.send_queue) != 0:
out = self.send_queue.pop(0)
log.debug('(%s) _send_string(queued data=%s)' % (self.ip, out.strip()))
else:
# No data to send
log.debug('(%s) _send_string(): No data to send' % self.ip)
self.send_busy = False
return
self.send_busy = True
log.debug('(%s) _send_string(): Sending "%s"' % (self.ip, out.strip()))
log.debug('(%s) _send_string(): Queue = %s' % (self.ip, self.send_queue))
self.socket_timer.start()
try:
self.projectorNetwork.emit(S_NETWORK_SENDING)
sent = self.write(out)
self.waitForBytesWritten(2000) # 2 seconds should be enough
if sent == -1:
# Network error?
self.change_status(E_NETWORK,
translate('OpenLP.PJLink1', 'Error while sending data to projector'))
except SocketError as e:
self.disconnect_from_host(abort=True)
self.changeStatus(E_NETWORK, '%s : %s' % (e.error(), e.errorString()))
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('(%s) Processing command "%s"' % (self.ip, cmd))
if data in PJLINK_ERRORS:
# Oops - projector error
if data.upper() == 'ERRA':
# Authentication error
self.disconnect_from_host()
self.change_status(E_AUTHENTICATION)
log.debug('(%s) emitting projectorAuthentication() signal' % self.ip)
self.projectorAuthentication.emit(self.name)
elif data.upper() == 'ERR1':
# Undefined command
self.change_status(E_UNDEFINED, '%s "%s"' %
(translate('OpenLP.PJLink1', 'Undefined command:'), cmd))
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.send_busy = False
self.projectorReceivedData.emit()
return
# Command succeeded - no extra information
elif data.upper() == 'OK':
log.debug('(%s) Command returned OK' % self.ip)
# A command returned successfully, recheck data
self.send_busy = False
self.projectorReceivedData.emit()
return
if cmd in self.PJLINK1_FUNC:
self.PJLINK1_FUNC[cmd](data)
else:
log.warn('(%s) Invalid command %s' % (self.ip, cmd))
self.send_busy = False
self.projectorReceivedData.emit()
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.warn('(%s) process_lamp(): Invalid data "%s"' % (self.ip, 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
"""
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.warn('Unknown power response: %s' % 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.warn('Unknown shutter response: %s' % 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
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.
"""
self.pjlink_class = data
log.debug('(%s) Setting pjlink_class for this projector to "%s"' % (self.ip, 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
return
def process_inf1(self, data):
"""
Manufacturer name set in projector.
Updates self.manufacturer
:param data: Projector manufacturer
"""
self.manufacturer = data
return
def process_inf2(self, data):
"""
Projector Model set in projector.
Updates self.model.
:param data: Model name
"""
self.model = data
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
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()
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 connect_to_host(self):
"""
Initiate connection to projector.
"""
if self.state() == self.ConnectedState:
log.warn('(%s) connect_to_host(): Already connected - returning' % self.ip)
return
self.change_status(S_CONNECTING)
self.connectToHost(self.ip, self.port if type(self.port) is int else int(self.port))
@pyqtSlot()
def disconnect_from_host(self, abort=False):
"""
Close socket and cleanup.
"""
if abort or self.state() != self.ConnectedState:
if abort:
log.warn('(%s) disconnect_from_host(): Aborting connection' % self.ip)
else:
log.warn('(%s) disconnect_from_host(): Not connected - returning' % self.ip)
self.reset_information()
self.disconnectFromHost()
try:
self.readyRead.disconnect(self.get_data)
except TypeError:
pass
if abort:
self.change_status(E_NOT_CONNECTED)
else:
log.debug('(%s) disconnect_from_host() Current status %s' % (self.ip,
self._get_status(self.status_connect)[0]))
if self.status_connect != E_NOT_CONNECTED:
self.change_status(S_NOT_CONNECTED)
self.reset_information()
self.projectorUpdateIcons.emit()
def get_available_inputs(self):
"""
Send command to retrieve available source inputs.
"""
return self.send_command(cmd='INST')
def get_error_status(self):
"""
Send command to retrieve currently known errors.
"""
return self.send_command(cmd='ERST')
def get_input_source(self):
"""
Send command to retrieve currently selected source input.
"""
return self.send_command(cmd='INPT')
def get_lamp_status(self):
"""
Send command to return the lap status.
"""
return self.send_command(cmd='LAMP')
def get_manufacturer(self):
"""
Send command to retrieve manufacturer name.
"""
return self.send_command(cmd='INF1')
def get_model(self):
"""
Send command to retrieve the model name.
"""
return self.send_command(cmd='INF2')
def get_name(self):
"""
Send command to retrieve name as set by end-user (if set).
"""
return self.send_command(cmd='NAME')
def get_other_info(self):
"""
Send command to retrieve extra info set by manufacturer.
"""
return self.send_command(cmd='INFO')
def get_power_status(self):
"""
Send command to retrieve power status.
"""
return self.send_command(cmd='POWR')
def get_shutter_status(self):
"""
Send command to retrieve shutter status.
"""
return self.send_command(cmd='AVMT')
def set_input_source(self, src=None):
"""
Verify input source available as listed in 'INST' command,
then send the command to select the input source.
:param src: Video source to select in projector
"""
log.debug('(%s) set_input_source(src=%s)' % (self.ip, src))
if self.source_available is None:
return
elif src not in self.source_available:
return
log.debug('(%s) Setting input source to %s' % (self.ip, src))
self.send_command(cmd='INPT', opts=src)
self.poll_loop()
def set_power_on(self):
"""
Send command to turn power to on.
"""
self.send_command(cmd='POWR', opts='1')
self.poll_loop()
def set_power_off(self):
"""
Send command to turn power to standby.
"""
self.send_command(cmd='POWR', opts='0')
self.poll_loop()
def set_shutter_closed(self):
"""
Send command to set shutter to closed position.
"""
self.send_command(cmd='AVMT', opts='11')
self.poll_loop()
def set_shutter_open(self):
"""
Send command to set shutter to open position.
"""
self.send_command(cmd='AVMT', opts='10')
self.poll_loop()

View File

@ -82,3 +82,16 @@ class OpenLPToolbar(QtGui.QToolBar):
self.actions[handle].setVisible(visible)
else:
log.warning('No handle "%s" in actions list.', str(handle))
def set_widget_enabled(self, widgets, enabled=True):
"""
Set the enabled state for a widget or a list of widgets.
:param widgets: A list of string with widget object names.
:param enabled: The new state as bool.
"""
for handle in widgets:
if handle in self.actions:
self.actions[handle].setEnabled(enabled)
else:
log.warning('No handle "%s" in actions list.', str(handle))

View File

@ -127,7 +127,7 @@ class TreeWidgetWithDnD(QtGui.QTreeWidget):
listing = os.listdir(local_file)
for file_name in listing:
files.append(os.path.join(local_file, file_name))
Registry().execute('%s_dnd' % self.mime_Data_Text, {'files': files, 'target': self.itemAt(event.pos())})
Registry().execute('%s_dnd' % self.mime_data_text, {'files': files, 'target': self.itemAt(event.pos())})
elif self.allow_internal_dnd:
event.setDropAction(QtCore.Qt.CopyAction)
event.accept()

View File

@ -124,9 +124,13 @@ from .shortcutlistform import ShortcutListForm
from .mediadockmanager import MediaDockManager
from .servicemanager import ServiceManager
from .thememanager import ThemeManager
from .projector.manager import ProjectorManager
from .projector.tab import ProjectorTab
from .projector.editform import ProjectorEditForm
__all__ = ['SplashScreen', 'AboutForm', 'SettingsForm', 'MainDisplay', 'SlideController', 'ServiceManager', 'ThemeForm',
'ThemeManager', 'MediaDockManager', 'ServiceItemEditForm', 'FirstTimeForm', 'FirstTimeLanguageForm',
'Display', 'ServiceNoteForm', 'ThemeLayoutForm', 'FileRenameForm', 'StartTimeForm', 'MainDisplay',
'SlideController', 'DisplayController', 'GeneralTab', 'ThemesTab', 'AdvancedTab', 'PluginForm',
'FormattingTagForm', 'ShortcutListForm', 'FormattingTagController', 'SingleColumnTableWidget']
'FormattingTagForm', 'ShortcutListForm', 'FormattingTagController', 'SingleColumnTableWidget',
'ProjectorManager', 'ProjectorTab', 'ProjectorEditForm']

View File

@ -53,6 +53,7 @@ from openlp.core.ui.media import MediaController
from openlp.core.utils import LanguageManager, add_actions, get_application_version
from openlp.core.utils.actions import ActionList, CategoryOrder
from openlp.core.ui.firsttimeform import FirstTimeForm
from openlp.core.ui.projector.manager import ProjectorManager
log = logging.getLogger(__name__)
@ -178,6 +179,14 @@ class Ui_MainWindow(object):
self.theme_manager_contents.setObjectName('theme_manager_contents')
self.theme_manager_dock.setWidget(self.theme_manager_contents)
main_window.addDockWidget(QtCore.Qt.RightDockWidgetArea, self.theme_manager_dock)
# Create the projector manager
self.projector_manager_dock = OpenLPDockWidget(parent=main_window,
name='projector_manager_dock',
icon=':/projector/projector_manager.png')
self.projector_manager_contents = ProjectorManager(self.projector_manager_dock)
self.projector_manager_contents.setObjectName('projector_manager_contents')
self.projector_manager_dock.setWidget(self.projector_manager_contents)
main_window.addDockWidget(QtCore.Qt.RightDockWidgetArea, self.projector_manager_dock)
# Create the menu items
action_list = ActionList.get_instance()
action_list.add_category(UiStrings().File, CategoryOrder.standard_menu)
@ -210,6 +219,16 @@ class Ui_MainWindow(object):
can_shortcuts=True)
self.export_language_item = create_action(main_window, 'exportLanguageItem')
action_list.add_category(UiStrings().View, CategoryOrder.standard_menu)
# Projector items
self.import_projector_item = create_action(main_window, 'importProjectorItem', category=UiStrings().Import,
can_shortcuts=False)
action_list.add_category(UiStrings().Import, CategoryOrder.standard_menu)
self.view_projector_manager_item = create_action(main_window, 'viewProjectorManagerItem',
icon=':/projector/projector_manager.png',
checked=self.projector_manager_dock.isVisible(),
can_shortcuts=True,
category=UiStrings().View,
triggers=self.toggle_projector_manager)
self.view_media_manager_item = create_action(main_window, 'viewMediaManagerItem',
icon=':/system/system_mediamanager.png',
checked=self.media_manager_dock.isVisible(),
@ -310,6 +329,11 @@ class Ui_MainWindow(object):
'searchShortcut', can_shortcuts=True,
category=translate('OpenLP.MainWindow', 'General'),
triggers=self.on_search_shortcut_triggered)
'''
Leave until the import projector options are finished
add_actions(self.file_import_menu, (self.settings_import_item, self.import_theme_item,
self.import_projector_item, self.import_language_item, None))
'''
add_actions(self.file_import_menu, (self.settings_import_item, self.import_theme_item,
self.import_language_item, None))
add_actions(self.file_export_menu, (self.settings_export_item, self.export_theme_item,
@ -320,8 +344,8 @@ class Ui_MainWindow(object):
self.print_service_order_item, self.file_exit_item))
add_actions(self.view_mode_menu, (self.mode_default_item, self.mode_setup_item, self.mode_live_item))
add_actions(self.view_menu, (self.view_mode_menu.menuAction(), None, self.view_media_manager_item,
self.view_service_manager_item, self.view_theme_manager_item, None, self.view_preview_panel,
self.view_live_panel, None, self.lock_panel))
self.view_projector_manager_item, self.view_service_manager_item, self.view_theme_manager_item,
None, self.view_preview_panel, self.view_live_panel, None, self.lock_panel))
# i18n add Language Actions
add_actions(self.settings_language_menu, (self.auto_language_item, None))
add_actions(self.settings_language_menu, self.language_group.actions())
@ -375,6 +399,7 @@ class Ui_MainWindow(object):
self.media_manager_dock.setWindowTitle(translate('OpenLP.MainWindow', 'Library'))
self.service_manager_dock.setWindowTitle(translate('OpenLP.MainWindow', 'Service Manager'))
self.theme_manager_dock.setWindowTitle(translate('OpenLP.MainWindow', 'Theme Manager'))
self.projector_manager_dock.setWindowTitle(translate('OpenLP.MainWindow', 'Projector Manager'))
self.file_new_item.setText(translate('OpenLP.MainWindow', '&New'))
self.file_new_item.setToolTip(UiStrings().NewService)
self.file_new_item.setStatusTip(UiStrings().CreateService)
@ -406,6 +431,10 @@ class Ui_MainWindow(object):
translate('OpenLP.MainWindow', 'Import OpenLP settings from a specified *.config file previously '
'exported on this or another machine'))
self.settings_import_item.setText(translate('OpenLP.MainWindow', 'Settings'))
self.view_projector_manager_item.setText(translate('OPenLP.MainWindow', '&ProjectorManager'))
self.view_projector_manager_item.setToolTip(translate('OpenLP.MainWindow', 'Toggle Projector Manager'))
self.view_projector_manager_item.setStatusTip(translate('OpenLP.MainWindow',
'Toggle the visibility of the Projector Manager'))
self.view_media_manager_item.setText(translate('OpenLP.MainWindow', '&Media Manager'))
self.view_media_manager_item.setToolTip(translate('OpenLP.MainWindow', 'Toggle Media Manager'))
self.view_media_manager_item.setStatusTip(translate('OpenLP.MainWindow',
@ -485,6 +514,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow, RegistryProperties):
self.service_manager_settings_section = 'servicemanager'
self.songs_settings_section = 'songs'
self.themes_settings_section = 'themes'
self.projector_settings_section = 'projector'
self.players_settings_section = 'players'
self.display_tags_section = 'displayTags'
self.header_section = 'SettingsImport'
@ -514,6 +544,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow, RegistryProperties):
self.media_manager_dock.visibilityChanged.connect(self.view_media_manager_item.setChecked)
self.service_manager_dock.visibilityChanged.connect(self.view_service_manager_item.setChecked)
self.theme_manager_dock.visibilityChanged.connect(self.view_theme_manager_item.setChecked)
self.projector_manager_dock.visibilityChanged.connect(self.view_projector_manager_item.setChecked)
self.import_theme_item.triggered.connect(self.theme_manager_contents.on_import_theme)
self.export_theme_item.triggered.connect(self.theme_manager_contents.on_export_theme)
self.web_site_item.triggered.connect(self.on_help_web_site_clicked)
@ -826,6 +857,7 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow, RegistryProperties):
setting_sections.extend([self.shortcuts_settings_section])
setting_sections.extend([self.service_manager_settings_section])
setting_sections.extend([self.themes_settings_section])
setting_sections.extend([self.projector_settings_section])
setting_sections.extend([self.players_settings_section])
setting_sections.extend([self.display_tags_section])
setting_sections.extend([self.header_section])
@ -1115,6 +1147,12 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow, RegistryProperties):
"""
self.media_manager_dock.setVisible(not self.media_manager_dock.isVisible())
def toggle_projector_manager(self):
"""
Toggle visibility of the projector manager
"""
self.projector_manager_dock.setVisible(not self.projector_manager_dock.isVisible())
def toggle_service_manager(self):
"""
Toggle the visibility of the service manager

View File

@ -146,7 +146,7 @@ class MediaController(RegistryMixin, OpenLPMixin, RegistryProperties):
if player.is_active:
for item in player.video_extensions_list:
if item not in self.video_extensions_list:
self.video_extensions_list.extend(item)
self.video_extensions_list.append(item)
suffix_list.append(item[2:])
self.service_manager.supported_suffixes(suffix_list)

View File

@ -0,0 +1,269 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2014 Raoul Snyman #
# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan #
# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
# Christian Richter, Philip Ridout, Ken Roberts, Simon Scudder, #
# Jeffrey Smith, Maikel Stuivenberg, Martin Thompson, Jon Tibble, #
# Dave Warnock, Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
:mod: `openlp.core.ui.projector.editform` module
Provides the functions for adding/editing entries in the projector database.
"""
import logging
log = logging.getLogger(__name__)
log.debug('editform loaded')
from PyQt4 import QtCore, QtGui
from PyQt4.QtCore import pyqtSlot, pyqtSignal
from PyQt4.QtGui import QDialog, QPlainTextEdit, QLineEdit, QDialogButtonBox, QLabel, QGridLayout
from openlp.core.common import translate, verify_ip_address
from openlp.core.lib import build_icon
from openlp.core.lib.projector.db import Projector
from openlp.core.lib.projector.constants import PJLINK_PORT
class Ui_ProjectorEditForm(object):
"""
The :class:`~openlp.core.lib.ui.projector.editform.Ui_ProjectorEditForm` class defines
the user interface for the ProjectorEditForm dialog.
"""
def setupUi(self, edit_projector_dialog):
"""
Create the interface layout.
"""
edit_projector_dialog.setObjectName('edit_projector_dialog')
edit_projector_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo-32x32.png'))
edit_projector_dialog.setMinimumWidth(400)
edit_projector_dialog.setModal(True)
# Define the basic layout
self.dialog_layout = QGridLayout(edit_projector_dialog)
self.dialog_layout.setObjectName('dialog_layout')
self.dialog_layout.setSpacing(8)
self.dialog_layout.setContentsMargins(8, 8, 8, 8)
# IP Address
self.ip_label = QLabel(edit_projector_dialog)
self.ip_label.setObjectName('projector_edit_ip_label')
self.ip_text = QLineEdit(edit_projector_dialog)
self.ip_text.setObjectName('projector_edit_ip_text')
self.dialog_layout.addWidget(self.ip_label, 0, 0)
self.dialog_layout.addWidget(self.ip_text, 0, 1)
# Port number
self.port_label = QLabel(edit_projector_dialog)
self.port_label.setObjectName('projector_edit_ip_label')
self.port_text = QLineEdit(edit_projector_dialog)
self.port_text.setObjectName('projector_edit_port_text')
self.dialog_layout.addWidget(self.port_label, 1, 0)
self.dialog_layout.addWidget(self.port_text, 1, 1)
# PIN
self.pin_label = QLabel(edit_projector_dialog)
self.pin_label.setObjectName('projector_edit_pin_label')
self.pin_text = QLineEdit(edit_projector_dialog)
self.pin_label.setObjectName('projector_edit_pin_text')
self.dialog_layout.addWidget(self.pin_label, 2, 0)
self.dialog_layout.addWidget(self.pin_text, 2, 1)
# Name
self.name_label = QLabel(edit_projector_dialog)
self.name_label.setObjectName('projector_edit_name_label')
self.name_text = QLineEdit(edit_projector_dialog)
self.name_text.setObjectName('projector_edit_name_text')
self.dialog_layout.addWidget(self.name_label, 3, 0)
self.dialog_layout.addWidget(self.name_text, 3, 1)
# Location
self.location_label = QLabel(edit_projector_dialog)
self.location_label.setObjectName('projector_edit_location_label')
self.location_text = QLineEdit(edit_projector_dialog)
self.location_text.setObjectName('projector_edit_location_text')
self.dialog_layout.addWidget(self.location_label, 4, 0)
self.dialog_layout.addWidget(self.location_text, 4, 1)
# Notes
self.notes_label = QLabel(edit_projector_dialog)
self.notes_label.setObjectName('projector_edit_notes_label')
self.notes_text = QPlainTextEdit(edit_projector_dialog)
self.notes_text.setObjectName('projector_edit_notes_text')
self.dialog_layout.addWidget(self.notes_label, 5, 0, alignment=QtCore.Qt.AlignTop)
self.dialog_layout.addWidget(self.notes_text, 5, 1)
# Time for the buttons
self.button_box = QDialogButtonBox(QDialogButtonBox.Help |
QDialogButtonBox.Save |
QDialogButtonBox.Cancel)
self.dialog_layout.addWidget(self.button_box, 8, 0, 1, 2)
def retranslateUi(self, edit_projector_dialog):
if self.new_projector:
title = translate('OpenLP.ProjectorEditForm', 'Add New Projector')
self.projector.port = PJLINK_PORT
else:
title = translate('OpenLP.ProjectorEditForm', 'Edit Projector')
edit_projector_dialog.setWindowTitle(title)
self.ip_label.setText(translate('OpenLP.ProjectorEditForm', 'IP Address'))
self.ip_text.setText(self.projector.ip)
self.ip_text.setFocus()
self.port_label.setText(translate('OpenLP.ProjectorEditForm', 'Port Number'))
self.port_text.setText(str(self.projector.port))
self.pin_label.setText(translate('OpenLP.ProjectorEditForm', 'PIN'))
self.pin_text.setText(self.projector.pin)
self.name_label.setText(translate('OpenLP.ProjectorEditForm', 'Name'))
self.name_text.setText(self.projector.name)
self.location_label.setText(translate('OpenLP.ProjectorEditForm', 'Location'))
self.location_text.setText(self.projector.location)
self.notes_label.setText(translate('OpenLP.ProjectorEditForm', 'Notes'))
self.notes_text.insertPlainText(self.projector.notes)
class ProjectorEditForm(QDialog, Ui_ProjectorEditForm):
"""
Class to add or edit a projector entry in the database.
Fields that are editable:
ip = Column(String(100))
port = Column(String(8))
pin = Column(String(20))
name = Column(String(20))
location = Column(String(30))
notes = Column(String(200))
"""
newProjector = pyqtSignal(str)
editProjector = pyqtSignal(object)
def __init__(self, parent=None, projectordb=None):
super(ProjectorEditForm, self).__init__(parent=parent)
self.projectordb = projectordb
self.setupUi(self)
self.button_box.accepted.connect(self.accept_me)
self.button_box.helpRequested.connect(self.help_me)
self.button_box.rejected.connect(self.cancel_me)
def exec_(self, projector=None):
if projector is None:
self.projector = Projector()
self.new_projector = True
else:
self.projector = projector
self.new_projector = False
self.retranslateUi(self)
reply = QDialog.exec_(self)
self.projector = None
return reply
@pyqtSlot()
def accept_me(self):
"""
Validate input before accepting input.
"""
log.debug('accept_me() signal received')
if len(self.name_text.text().strip()) < 1:
QtGui.QMessageBox.warning(self,
translate('OpenLP.ProjectorEdit', 'Name Not Set'),
translate('OpenLP.ProjectorEdit',
'You must enter a name for this entry.<br />'
'Please enter a new name for this entry.'))
valid = False
return
name = self.name_text.text().strip()
record = self.projectordb.get_projector_by_name(name)
if record is not None and record.id != self.projector.id:
QtGui.QMessageBox.warning(self,
translate('OpenLP.ProjectorEdit', 'Duplicate Name'),
translate('OpenLP.ProjectorEdit',
'There is already an entry with name "%s" in '
'the database as ID "%s". <br />'
'Please enter a different name.' % (name, record.id)))
valid = False
return
adx = self.ip_text.text()
valid = verify_ip_address(adx)
if valid:
ip = self.projectordb.get_projector_by_ip(adx)
if ip is None:
valid = True
self.new_projector = True
elif ip.id != self.projector.id:
QtGui.QMessageBox.warning(self,
translate('OpenLP.ProjectorWizard', 'Duplicate IP Address'),
translate('OpenLP.ProjectorWizard',
'IP address "%s"<br />is already in the database as ID %s.'
'<br /><br />Please Enter a different IP address.' % (adx, ip.id)))
valid = False
return
else:
QtGui.QMessageBox.warning(self,
translate('OpenLP.ProjectorWizard', 'Invalid IP Address'),
translate('OpenLP.ProjectorWizard',
'IP address "%s"<br>is not a valid IP address.'
'<br /><br />Please enter a valid IP address.' % adx))
valid = False
return
port = int(self.port_text.text())
if port < 1000 or port > 32767:
QtGui.QMessageBox.warning(self,
translate('OpenLP.ProjectorWizard', 'Invalid Port Number'),
translate('OpenLP.ProjectorWizard',
'Port numbers below 1000 are reserved for admin use only, '
'<br />and port numbers above 32767 are not currently usable.'
'<br /><br />Please enter a valid port number between '
' 1000 and 32767.'
'<br /><br />Default PJLink port is %s' % PJLINK_PORT))
valid = False
if valid:
self.projector.ip = self.ip_text.text()
self.projector.pin = self.pin_text.text()
self.projector.port = int(self.port_text.text())
self.projector.name = self.name_text.text()
self.projector.location = self.location_text.text()
self.projector.notes = self.notes_text.toPlainText()
if self.new_projector:
saved = self.projectordb.add_projector(self.projector)
else:
saved = self.projectordb.update_projector(self.projector)
if not saved:
QtGui.QMessageBox.warning(self,
translate('OpenLP.ProjectorEditForm', 'Database Error'),
translate('OpenLP.ProjectorEditForm',
'There was an error saving projector '
'information. See the log for the error'))
return saved
if self.new_projector:
self.newProjector.emit(adx)
else:
self.editProjector.emit(self.projector)
self.close()
@pyqtSlot()
def help_me(self):
"""
Show a help message about the input fields.
"""
log.debug('help_me() signal received')
@pyqtSlot()
def cancel_me(self):
"""
Cancel button clicked - just close.
"""
log.debug('cancel_me() signal received')
self.close()

View File

@ -0,0 +1,983 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2014 Raoul Snyman #
# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan #
# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
# Christian Richter, Philip Ridout, Ken Roberts, Simon Scudder, #
# Jeffrey Smith, Maikel Stuivenberg, Martin Thompson, Jon Tibble, #
# Dave Warnock, Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
:mod: openlp.core.ui.projector.manager` module
Provides the functions for the display/control of Projectors.
"""
import logging
log = logging.getLogger(__name__)
log.debug('projectormanager loaded')
from PyQt4 import QtCore, QtGui
from PyQt4.QtCore import QObject, QThread, pyqtSlot
from PyQt4.QtGui import QWidget
from openlp.core.common import RegistryProperties, Settings, OpenLPMixin, \
RegistryMixin, translate
from openlp.core.lib import OpenLPToolbar
from openlp.core.lib.ui import create_widget_action
from openlp.core.lib.projector import DialogSourceStyle
from openlp.core.lib.projector.constants import *
from openlp.core.lib.projector.db import ProjectorDB
from openlp.core.lib.projector.pjlink1 import PJLink1
from openlp.core.ui.projector.editform import ProjectorEditForm
from openlp.core.ui.projector.sourceselectform import SourceSelectTabs, SourceSelectSingle
# Dict for matching projector status to display icon
STATUS_ICONS = {S_NOT_CONNECTED: ':/projector/projector_item_disconnect.png',
S_CONNECTING: ':/projector/projector_item_connect.png',
S_CONNECTED: ':/projector/projector_off.png',
S_OFF: ':/projector/projector_off.png',
S_INITIALIZE: ':/projector/projector_off.png',
S_STANDBY: ':/projector/projector_off.png',
S_WARMUP: ':/projector/projector_warmup.png',
S_ON: ':/projector/projector_on.png',
S_COOLDOWN: ':/projector/projector_cooldown.png',
E_ERROR: ':/projector/projector_error.png',
E_NETWORK: ':/projector/projector_not_connected_error.png',
E_AUTHENTICATION: ':/projector/projector_not_connected_error.png',
E_UNKNOWN_SOCKET_ERROR: ':/projector/projector_not_connected_error.png',
E_NOT_CONNECTED: ':/projector/projector_not_connected_error.png'
}
class Ui_ProjectorManager(object):
"""
UI part of the Projector Manager
"""
def setup_ui(self, widget):
"""
Define the UI
:param widget: The screen object the dialog is to be attached to.
"""
log.debug('setup_ui()')
# Create ProjectorManager box
self.layout = QtGui.QVBoxLayout(widget)
self.layout.setSpacing(0)
self.layout.setMargin(0)
self.layout.setObjectName('layout')
# Add one selection toolbar
self.one_toolbar = OpenLPToolbar(widget)
self.one_toolbar.add_toolbar_action('new_projector',
text=translate('OpenLP.ProjectorManager', 'Add Projector'),
icon=':/projector/projector_new.png',
tooltip=translate('OpenLP.ProjectorManager', 'Add a new projector'),
triggers=self.on_add_projector)
# Show edit/delete when projector not connected
self.one_toolbar.add_toolbar_action('edit_projector',
text=translate('OpenLP.ProjectorManager', 'Edit Projector'),
icon=':/general/general_edit.png',
tooltip=translate('OpenLP.ProjectorManager', 'Edit selected projector'),
triggers=self.on_edit_projector)
self.one_toolbar.add_toolbar_action('delete_projector',
text=translate('OpenLP.ProjectorManager', 'Delete Projector'),
icon=':/general/general_delete.png',
tooltip=translate('OpenLP.ProjectorManager', 'Delete selected projector'),
triggers=self.on_delete_projector)
# Show source/view when projector connected
self.one_toolbar.add_toolbar_action('source_view_projector',
text=translate('OpenLP.ProjectorManager', 'Select Input Source'),
icon=':/projector/projector_hdmi.png',
tooltip=translate('OpenLP.ProjectorManager',
'Choose input source on selected projector'),
triggers=self.on_select_input)
self.one_toolbar.add_toolbar_action('view_projector',
text=translate('OpenLP.ProjectorManager', 'View Projector'),
icon=':/system/system_about.png',
tooltip=translate('OpenLP.ProjectorManager',
'View selected projector information'),
triggers=self.on_status_projector)
self.one_toolbar.addSeparator()
self.one_toolbar.add_toolbar_action('connect_projector',
text=translate('OpenLP.ProjectorManager',
'Connect to selected projector'),
icon=':/projector/projector_connect.png',
tootip=translate('OpenLP.ProjectorManager',
'Connect to selected projector'),
triggers=self.on_connect_projector)
self.one_toolbar.add_toolbar_action('connect_projector_multiple',
text=translate('OpenLP.ProjectorManager',
'Connect to selected projectors'),
icon=':/projector/projector_connect_tiled.png',
tootip=translate('OpenLP.ProjectorManager',
'Connect to selected projector'),
triggers=self.on_connect_projector)
self.one_toolbar.add_toolbar_action('disconnect_projector',
text=translate('OpenLP.ProjectorManager',
'Disconnect from selected projectors'),
icon=':/projector/projector_disconnect.png',
tooltip=translate('OpenLP.ProjectorManager',
'Disconnect from selected projector'),
triggers=self.on_disconnect_projector)
self.one_toolbar.add_toolbar_action('disconnect_projector_multiple',
text=translate('OpenLP.ProjectorManager',
'Disconnect from selected projector'),
icon=':/projector/projector_disconnect_tiled.png',
tooltip=translate('OpenLP.ProjectorManager',
'Disconnect from selected projector'),
triggers=self.on_disconnect_projector)
self.one_toolbar.addSeparator()
self.one_toolbar.add_toolbar_action('poweron_projector',
text=translate('OpenLP.ProjectorManager',
'Power on selected projector'),
icon=':/projector/projector_power_on.png',
tooltip=translate('OpenLP.ProjectorManager',
'Power on selected projector'),
triggers=self.on_poweron_projector)
self.one_toolbar.add_toolbar_action('poweron_projector_multiple',
text=translate('OpenLP.ProjectorManager',
'Power on selected projector'),
icon=':/projector/projector_power_on_tiled.png',
tooltip=translate('OpenLP.ProjectorManager',
'Power on selected projector'),
triggers=self.on_poweron_projector)
self.one_toolbar.add_toolbar_action('poweroff_projector',
text=translate('OpenLP.ProjectorManager', 'Standby selected projector'),
icon=':/projector/projector_power_off.png',
tooltip=translate('OpenLP.ProjectorManager',
'Put selected projector in standby'),
triggers=self.on_poweroff_projector)
self.one_toolbar.add_toolbar_action('poweroff_projector_multiple',
text=translate('OpenLP.ProjectorManager', 'Standby selected projector'),
icon=':/projector/projector_power_off_tiled.png',
tooltip=translate('OpenLP.ProjectorManager',
'Put selected projector in standby'),
triggers=self.on_poweroff_projector)
self.one_toolbar.addSeparator()
self.one_toolbar.add_toolbar_action('blank_projector',
text=translate('OpenLP.ProjectorManager',
'Blank selected projector screen'),
icon=':/projector/projector_blank.png',
tooltip=translate('OpenLP.ProjectorManager',
'Blank selected projector screen'),
triggers=self.on_blank_projector)
self.one_toolbar.add_toolbar_action('blank_projector_multiple',
text=translate('OpenLP.ProjectorManager',
'Blank selected projector screen'),
icon=':/projector/projector_blank_tiled.png',
tooltip=translate('OpenLP.ProjectorManager',
'Blank selected projector screen'),
triggers=self.on_blank_projector)
self.one_toolbar.add_toolbar_action('show_projector',
ext=translate('OpenLP.ProjectorManager',
'Show selected projector screen'),
icon=':/projector/projector_show.png',
tooltip=translate('OpenLP.ProjectorManager',
'Show selected projector screen'),
triggers=self.on_show_projector)
self.one_toolbar.add_toolbar_action('show_projector_multiple',
ext=translate('OpenLP.ProjectorManager',
'Show selected projector screen'),
icon=':/projector/projector_show_tiled.png',
tooltip=translate('OpenLP.ProjectorManager',
'Show selected projector screen'),
triggers=self.on_show_projector)
self.layout.addWidget(self.one_toolbar)
self.projector_one_widget = QtGui.QWidgetAction(self.one_toolbar)
self.projector_one_widget.setObjectName('projector_one_toolbar_widget')
# Create projector manager list
self.projector_list_widget = QtGui.QListWidget(widget)
self.projector_list_widget.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection)
self.projector_list_widget.setAlternatingRowColors(True)
self.projector_list_widget.setIconSize(QtCore.QSize(90, 50))
self.projector_list_widget.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
self.projector_list_widget.setObjectName('projector_list_widget')
self.layout.addWidget(self.projector_list_widget)
self.projector_list_widget.customContextMenuRequested.connect(self.context_menu)
self.projector_list_widget.itemDoubleClicked.connect(self.on_doubleclick_item)
# Build the context menu
self.menu = QtGui.QMenu()
self.status_action = create_widget_action(self.menu,
text=translate('OpenLP.ProjectorManager',
'&View Projector Information'),
icon=':/system/system_about.png',
triggers=self.on_status_projector)
self.edit_action = create_widget_action(self.menu,
text=translate('OpenLP.ProjectorManager',
'&Edit Projector'),
icon=':/projector/projector_edit.png',
triggers=self.on_edit_projector)
self.menu.addSeparator()
self.connect_action = create_widget_action(self.menu,
text=translate('OpenLP.ProjectorManager',
'&Connect Projector'),
icon=':/projector/projector_connect.png',
triggers=self.on_connect_projector)
self.disconnect_action = create_widget_action(self.menu,
text=translate('OpenLP.ProjectorManager',
'D&isconnect Projector'),
icon=':/projector/projector_disconnect.png',
triggers=self.on_disconnect_projector)
self.menu.addSeparator()
self.poweron_action = create_widget_action(self.menu,
text=translate('OpenLP.ProjectorManager',
'Power &On Projector'),
icon=':/projector/projector_power_on.png',
triggers=self.on_poweron_projector)
self.poweroff_action = create_widget_action(self.menu,
text=translate('OpenLP.ProjectorManager',
'Power O&ff Projector'),
icon=':/projector/projector_power_off.png',
triggers=self.on_poweroff_projector)
self.menu.addSeparator()
self.select_input_action = create_widget_action(self.menu,
text=translate('OpenLP.ProjectorManager',
'Select &Input'),
icon=':/projector/projector_hdmi.png',
triggers=self.on_select_input)
self.edit_input_action = create_widget_action(self.menu,
text=translate('OpenLP.ProjectorManager',
'Edit Input Source'),
icon=':/general/general_edit.png',
triggers=self.on_edit_input)
self.blank_action = create_widget_action(self.menu,
text=translate('OpenLP.ProjectorManager',
'&Blank Projector Screen'),
icon=':/projector/projector_blank.png',
triggers=self.on_blank_projector)
self.show_action = create_widget_action(self.menu,
text=translate('OpenLP.ProjectorManager',
'&Show Projector Screen'),
icon=':/projector/projector_show.png',
triggers=self.on_show_projector)
self.menu.addSeparator()
self.delete_action = create_widget_action(self.menu,
text=translate('OpenLP.ProjectorManager',
'&Delete Projector'),
icon=':/general/general_delete.png',
triggers=self.on_delete_projector)
self.update_icons()
class ProjectorManager(OpenLPMixin, RegistryMixin, QWidget, Ui_ProjectorManager, RegistryProperties):
"""
Manage the projectors.
"""
def __init__(self, parent=None, projectordb=None):
"""
Basic initialization.
:param parent: Who I belong to.
:param projectordb: Database session inherited from superclass.
"""
log.debug('__init__()')
super().__init__(parent)
self.settings_section = 'projector'
self.projectordb = projectordb
self.projector_list = []
self.source_select_form = None
def bootstrap_initialise(self):
"""
Pre-initialize setups.
"""
self.setup_ui(self)
if self.projectordb is None:
# Work around for testing creating a ~/.openlp.data.projector.projector.sql file
log.debug('Creating new ProjectorDB() instance')
self.projectordb = ProjectorDB()
else:
log.debug('Using existing ProjectorDB() instance')
self.get_settings()
def bootstrap_post_set_up(self):
"""
Post-initialize setups.
"""
# Set 1.5 second delay before loading all projectors
if self.autostart:
log.debug('Delaying 1.5 seconds before loading all projectors')
QtCore.QTimer().singleShot(1500, self._load_projectors)
else:
log.debug('Loading all projectors')
self._load_projectors()
self.projector_form = ProjectorEditForm(self, projectordb=self.projectordb)
self.projector_form.newProjector.connect(self.add_projector_from_wizard)
self.projector_form.editProjector.connect(self.edit_projector_from_wizard)
self.projector_list_widget.itemSelectionChanged.connect(self.update_icons)
def get_settings(self):
"""
Retrieve the saved settings
"""
settings = Settings()
settings.beginGroup(self.settings_section)
self.autostart = settings.value('connect on start')
self.poll_time = settings.value('poll time')
self.socket_timeout = settings.value('socket timeout')
self.source_select_dialog_type = settings.value('source dialog type')
settings.endGroup()
del settings
def context_menu(self, point):
"""
Build the Right Click Context menu and set state.
:param point: The position of the mouse so the correct item can be found.
"""
# QListWidgetItem to build menu for.
item = self.projector_list_widget.itemAt(point)
if item is None:
return
real_projector = item.data(QtCore.Qt.UserRole)
projector_name = str(item.text())
visible = real_projector.link.status_connect >= S_CONNECTED
log.debug('(%s) Building menu - visible = %s' % (projector_name, visible))
self.delete_action.setVisible(True)
self.edit_action.setVisible(True)
self.connect_action.setVisible(not visible)
self.disconnect_action.setVisible(visible)
self.status_action.setVisible(visible)
if visible:
self.select_input_action.setVisible(real_projector.link.power == S_ON)
self.edit_input_action.setVisible(real_projector.link.power == S_ON)
self.poweron_action.setVisible(real_projector.link.power == S_STANDBY)
self.poweroff_action.setVisible(real_projector.link.power == S_ON)
self.blank_action.setVisible(real_projector.link.power == S_ON and
not real_projector.link.shutter)
self.show_action.setVisible(real_projector.link.power == S_ON and
real_projector.link.shutter)
else:
self.select_input_action.setVisible(False)
self.edit_input_action.setVisible(False)
self.poweron_action.setVisible(False)
self.poweroff_action.setVisible(False)
self.blank_action.setVisible(False)
self.show_action.setVisible(False)
self.menu.projector = real_projector
self.menu.exec_(self.projector_list_widget.mapToGlobal(point))
def on_edit_input(self, opt=None):
self.on_select_input(opt=opt, edit=True)
def on_select_input(self, opt=None, edit=False):
"""
Builds menu for 'Select Input' option, then calls the selected projector
item to change input source.
:param opt: Needed by PyQt4
"""
self.get_settings() # In case the dialog interface setting was changed
list_item = self.projector_list_widget.item(self.projector_list_widget.currentRow())
projector = list_item.data(QtCore.Qt.UserRole)
# QTabwidget for source select
source = 100
while source > 99:
if self.source_select_dialog_type == DialogSourceStyle.Tabbed:
source_select_form = SourceSelectTabs(parent=self,
projectordb=self.projectordb,
edit=edit)
else:
source_select_form = SourceSelectSingle(parent=self,
projectordb=self.projectordb,
edit=edit)
source = source_select_form.exec_(projector.link)
log.debug('(%s) source_select_form() returned %s' % (projector.link.ip, source))
if source is not None and source > 0:
projector.link.set_input_source(str(source))
return
def on_add_projector(self, opt=None):
"""
Calls edit dialog to add a new projector to the database
:param opt: Needed by PyQt4
"""
self.projector_form.exec_()
def on_blank_projector(self, opt=None):
"""
Calls projector thread to send blank screen command
:param opt: Needed by PyQt4
"""
try:
ip = opt.link.ip
projector = opt
projector.link.set_shutter_closed()
except AttributeError:
for list_item in self.projector_list_widget.selectedItems():
if list_item is None:
return
projector = list_item.data(QtCore.Qt.UserRole)
try:
projector.link.set_shutter_closed()
except:
continue
def on_doubleclick_item(self, item, opt=None):
"""
When item is doubleclicked, will connect to projector.
:param item: List widget item for connection.
:param opt: Needed by PyQt4
"""
projector = item.data(QtCore.Qt.UserRole)
if projector.link.state() != projector.link.ConnectedState:
try:
projector.link.connect_to_host()
except:
pass
return
def on_connect_projector(self, opt=None):
"""
Calls projector thread to connect to projector
:param opt: Needed by PyQt4
"""
try:
ip = opt.link.ip
projector = opt
projector.link.connect_to_host()
except AttributeError:
for list_item in self.projector_list_widget.selectedItems():
if list_item is None:
return
projector = list_item.data(QtCore.Qt.UserRole)
try:
projector.link.connect_to_host()
except:
continue
def on_delete_projector(self, opt=None):
"""
Deletes a projector from the list and the database
:param opt: Needed by PyQt4
"""
list_item = self.projector_list_widget.item(self.projector_list_widget.currentRow())
if list_item is None:
return
projector = list_item.data(QtCore.Qt.UserRole)
msg = QtGui.QMessageBox()
msg.setText('Delete projector (%s) %s?' % (projector.link.ip, projector.link.name))
msg.setInformativeText('Are you sure you want to delete this projector?')
msg.setStandardButtons(msg.Cancel | msg.Ok)
msg.setDefaultButton(msg.Cancel)
ans = msg.exec_()
if ans == msg.Cancel:
return
try:
projector.link.projectorNetwork.disconnect(self.update_status)
except (AttributeError, TypeError):
pass
try:
projector.link.changeStatus.disconnect(self.update_status)
except (AttributeError, TypeError):
pass
try:
projector.link.authentication_error.disconnect(self.authentication_error)
except (AttributeError, TypeError):
pass
try:
projector.link.no_authentication_error.disconnect(self.no_authentication_error)
except (AttributeError, TypeError):
pass
try:
projector.link.projectorUpdateIcons.disconnect(self.update_icons)
except (AttributeError, TypeError):
pass
try:
projector.timer.stop()
projector.timer.timeout.disconnect(projector.link.poll_loop)
except (AttributeError, TypeError):
pass
try:
projector.socket_timer.stop()
projector.socket_timer.timeout.disconnect(projector.link.socket_abort)
except (AttributeError, TypeError):
pass
projector.thread.quit()
new_list = []
for item in self.projector_list:
if item.link.dbid == projector.link.dbid:
continue
new_list.append(item)
self.projector_list = new_list
list_item = self.projector_list_widget.takeItem(self.projector_list_widget.currentRow())
list_item = None
deleted = self.projectordb.delete_projector(projector.db_item)
for item in self.projector_list:
log.debug('New projector list - item: %s %s' % (item.link.ip, item.link.name))
def on_disconnect_projector(self, opt=None):
"""
Calls projector thread to disconnect from projector
:param opt: Needed by PyQt4
"""
try:
ip = opt.link.ip
projector = opt
projector.link.disconnect_from_host()
except AttributeError:
for list_item in self.projector_list_widget.selectedItems():
if list_item is None:
return
projector = list_item.data(QtCore.Qt.UserRole)
try:
projector.link.disconnect_from_host()
except:
continue
def on_edit_projector(self, opt=None):
"""
Calls edit dialog with selected projector to edit information
:param opt: Needed by PyQt4
"""
list_item = self.projector_list_widget.item(self.projector_list_widget.currentRow())
projector = list_item.data(QtCore.Qt.UserRole)
if projector is None:
return
self.old_projector = projector
projector.link.disconnect_from_host()
record = self.projectordb.get_projector_by_ip(projector.link.ip)
self.projector_form.exec_(record)
new_record = self.projectordb.get_projector_by_id(record.id)
def on_poweroff_projector(self, opt=None):
"""
Calls projector link to send Power Off command
:param opt: Needed by PyQt4
"""
try:
ip = opt.link.ip
projector = opt
projector.link.set_power_off()
except AttributeError:
for list_item in self.projector_list_widget.selectedItems():
if list_item is None:
return
projector = list_item.data(QtCore.Qt.UserRole)
try:
projector.link.set_power_off()
except:
continue
def on_poweron_projector(self, opt=None):
"""
Calls projector link to send Power On command
:param opt: Needed by PyQt4
"""
try:
ip = opt.link.ip
projector = opt
projector.link.set_power_on()
except AttributeError:
for list_item in self.projector_list_widget.selectedItems():
if list_item is None:
return
projector = list_item.data(QtCore.Qt.UserRole)
try:
projector.link.set_power_on()
except:
continue
def on_show_projector(self, opt=None):
"""
Calls projector thread to send open shutter command
:param opt: Needed by PyQt4
"""
try:
ip = opt.link.ip
projector = opt
projector.link.set_shutter_open()
except AttributeError:
for list_item in self.projector_list_widget.selectedItems():
if list_item is None:
return
projector = list_item.data(QtCore.Qt.UserRole)
try:
projector.link.set_shutter_open()
except:
continue
def on_status_projector(self, opt=None):
"""
Builds message box with projector status information
:param opt: Needed by PyQt4
"""
lwi = self.projector_list_widget.item(self.projector_list_widget.currentRow())
projector = lwi.data(QtCore.Qt.UserRole)
message = '<b>%s</b>: %s<BR />' % (translate('OpenLP.ProjectorManager', 'Name'),
projector.link.name)
message = '%s<b>%s</b>: %s<br />' % (message, translate('OpenLP.ProjectorManager', 'IP'),
projector.link.ip)
message = '%s<b>%s</b>: %s<br />' % (message, translate('OpenLP.ProjectorManager', 'Port'),
projector.link.port)
message = '%s<b>%s</b>: %s<br />' % (message, translate('OpenLP.ProjectorManager', 'Notes'),
projector.link.notes)
message = '%s<hr /><br >' % message
if projector.link.manufacturer is None:
message = '%s%s' % (message, translate('OpenLP.ProjectorManager',
'Projector information not available at this time.'))
else:
message = '%s<b>%s</b>: %s<BR />' % (message, translate('OpenLP.ProjectorManager', 'Projector Name'),
projector.link.pjlink_name)
message = '%s<b>%s</b>: %s<br />' % (message, translate('OpenLP.ProjectorManager', 'Manufacturer'),
projector.link.manufacturer)
message = '%s<b>%s</b>: %s<br />' % (message, translate('OpenLP.ProjectorManager', 'Model'),
projector.link.model)
message = '%s<b>%s</b>: %s<br /><br />' % (message, translate('OpenLP.ProjectorManager', 'Other info'),
projector.link.other_info)
message = '%s<b>%s</b>: %s<br />' % (message, translate('OpenLP.ProjectorManager', 'Power status'),
ERROR_MSG[projector.link.power])
message = '%s<b>%s</b>: %s<br />' % (message, translate('OpenLP.ProjectorManager', 'Shutter is'),
translate('OpenLP.ProjectorManager', 'Closed')
if projector.link.shutter else translate('OpenLP', 'Open'))
message = '%s<b>%s</b>: %s<br />' % (message,
translate('OpenLP.ProjectorManager', 'Current source input is'),
projector.link.source)
count = 1
for item in projector.link.lamp:
message = '%s <b>%s %s</b> (%s) %s: %s<br />' % (message,
translate('OpenLP.ProjectorManager', 'Lamp'),
count,
translate('OpenLP.ProjectorManager', 'On')
if item['On']
else translate('OpenLP.ProjectorManager', 'Off'),
translate('OpenLP.ProjectorManager', 'Hours'),
item['Hours'])
count = count + 1
message = '%s<hr /><br />' % message
if projector.link.projector_errors is None:
message = '%s%s' % (message, translate('OpenLP.ProjectorManager', 'No current errors or warnings'))
else:
message = '%s<b>%s</b>' % (message, translate('OpenLP.ProjectorManager', 'Current errors/warnings'))
for (key, val) in projector.link.projector_errors.items():
message = '%s<b>%s</b>: %s<br />' % (message, key, ERROR_MSG[val])
QtGui.QMessageBox.information(self, translate('OpenLP.ProjectorManager', 'Projector Information'), message)
def _add_projector(self, projector):
"""
Helper app to build a projector instance
:param projector: Dict of projector database information
:returns: PJLink1() instance
"""
log.debug('_add_projector()')
return PJLink1(dbid=projector.id,
ip=projector.ip,
port=int(projector.port),
name=projector.name,
location=projector.location,
notes=projector.notes,
pin=None if projector.pin == '' else projector.pin,
poll_time=self.poll_time,
socket_timeout=self.socket_timeout
)
def add_projector(self, projector, start=False):
"""
Builds manager list item, projector thread, and timer for projector instance.
:param projector: Projector instance to add
:param start: Start projector if True
"""
item = ProjectorItem(link=self._add_projector(projector))
item.db_item = projector
icon = QtGui.QIcon(QtGui.QPixmap(STATUS_ICONS[S_NOT_CONNECTED]))
item.icon = icon
widget = QtGui.QListWidgetItem(icon,
item.link.name,
self.projector_list_widget
)
widget.setData(QtCore.Qt.UserRole, item)
item.link.db_item = item.db_item
item.widget = widget
thread = QThread(parent=self)
thread.my_parent = self
item.moveToThread(thread)
thread.started.connect(item.link.thread_started)
thread.finished.connect(item.link.thread_stopped)
thread.finished.connect(thread.deleteLater)
item.link.projectorNetwork.connect(self.update_status)
item.link.changeStatus.connect(self.update_status)
item.link.projectorAuthentication.connect(self.authentication_error)
item.link.projectorNoAuthentication.connect(self.no_authentication_error)
item.link.projectorUpdateIcons.connect(self.update_icons)
timer = QtCore.QTimer(self)
timer.setInterval(self.poll_time)
timer.timeout.connect(item.link.poll_loop)
item.timer = timer
# Timeout in case of brain-dead projectors or cable disconnected
socket_timer = QtCore.QTimer(self)
socket_timer.setInterval(11000)
socket_timer.timeout.connect(item.link.socket_abort)
item.socket_timer = socket_timer
thread.start()
item.thread = thread
item.link.timer = timer
item.link.socket_timer = socket_timer
item.link.widget = item.widget
self.projector_list.append(item)
if start:
item.link.connect_to_host()
for item in self.projector_list:
log.debug('New projector list - item: (%s) %s' % (item.link.ip, item.link.name))
@pyqtSlot(str)
def add_projector_from_wizard(self, ip, opts=None):
"""
Add a projector from the edit dialog
:param ip: IP address of new record item to find
:param opts: Needed by PyQt4
"""
log.debug('add_projector_from_wizard(ip=%s)' % ip)
item = self.projectordb.get_projector_by_ip(ip)
self.add_projector(item)
@pyqtSlot(object)
def edit_projector_from_wizard(self, projector):
"""
Update projector from the wizard edit page
:param projector: Projector() instance of projector with updated information
"""
log.debug('edit_projector_from_wizard(ip=%s)' % projector.ip)
self.old_projector.link.name = projector.name
self.old_projector.link.ip = projector.ip
self.old_projector.link.pin = None if projector.pin == '' else projector.pin
self.old_projector.link.port = projector.port
self.old_projector.link.location = projector.location
self.old_projector.link.notes = projector.notes
self.old_projector.widget.setText(projector.name)
def _load_projectors(self):
"""'
Load projectors - only call when initializing
"""
log.debug('_load_projectors()')
self.projector_list_widget.clear()
for item in self.projectordb.get_projector_all():
self.add_projector(projector=item, start=self.autostart)
def get_projector_list(self):
"""
Return the list of active projectors
:returns: list
"""
return self.projector_list
@pyqtSlot(str, int, str)
def update_status(self, ip, status=None, msg=None):
"""
Update the status information/icon for selected list item
:param ip: IP address of projector
:param status: Optional status code
:param msg: Optional status message
"""
if status is None:
return
item = None
for list_item in self.projector_list:
if ip == list_item.link.ip:
item = list_item
break
message = translate('OpenLP.ProjectorManager', 'No message') if msg is None else msg
if status in STATUS_STRING:
status_code = STATUS_STRING[status]
message = ERROR_MSG[status] if msg is None else msg
elif status in ERROR_STRING:
status_code = ERROR_STRING[status]
message = ERROR_MSG[status] if msg is None else msg
else:
status_code = status
message = ERROR_MSG[status] if msg is None else msg
log.debug('(%s) updateStatus(status=%s) message: "%s"' % (item.link.name, status_code, message))
if status in STATUS_ICONS:
if item.status == status:
return
item.status = status
item.icon = QtGui.QIcon(QtGui.QPixmap(STATUS_ICONS[status]))
if status in ERROR_STRING:
status_code = ERROR_STRING[status]
elif status in STATUS_STRING:
status_code = STATUS_STRING[status]
log.debug('(%s) Updating icon with %s' % (item.link.name, status_code))
item.widget.setIcon(item.icon)
self.update_icons()
def get_toolbar_item(self, name, enabled=False, hidden=False):
item = self.one_toolbar.findChild(QtGui.QAction, name)
if item == 0:
log.debug('No item found with name "%s"' % name)
return
item.setVisible(False if hidden else True)
item.setEnabled(True if enabled else False)
@pyqtSlot()
def update_icons(self):
"""
Update the icons when the selected projectors change
"""
count = len(self.projector_list_widget.selectedItems())
projector = None
if count == 0:
self.get_toolbar_item('edit_projector')
self.get_toolbar_item('delete_projector')
self.get_toolbar_item('view_projector', hidden=True)
self.get_toolbar_item('source_view_projector', hidden=True)
self.get_toolbar_item('connect_projector')
self.get_toolbar_item('disconnect_projector')
self.get_toolbar_item('poweron_projector')
self.get_toolbar_item('poweroff_projector')
self.get_toolbar_item('blank_projector')
self.get_toolbar_item('show_projector')
self.get_toolbar_item('connect_projector_multiple', hidden=True)
self.get_toolbar_item('disconnect_projector_multiple', hidden=True)
self.get_toolbar_item('poweron_projector_multiple', hidden=True)
self.get_toolbar_item('poweroff_projector_multiple', hidden=True)
self.get_toolbar_item('blank_projector_multiple', hidden=True)
self.get_toolbar_item('show_projector_multiple', hidden=True)
elif count == 1:
projector = self.projector_list_widget.selectedItems()[0].data(QtCore.Qt.UserRole)
connected = projector.link.state() == projector.link.ConnectedState
power = projector.link.power == S_ON
self.get_toolbar_item('connect_projector_multiple', hidden=True)
self.get_toolbar_item('disconnect_projector_multiple', hidden=True)
self.get_toolbar_item('poweron_projector_multiple', hidden=True)
self.get_toolbar_item('poweroff_projector_multiple', hidden=True)
self.get_toolbar_item('blank_projector_multiple', hidden=True)
self.get_toolbar_item('show_projector_multiple', hidden=True)
if connected:
self.get_toolbar_item('view_projector', enabled=True)
self.get_toolbar_item('source_view_projector',
enabled=connected and power and projector.link.source_available is not None)
self.get_toolbar_item('edit_projector', hidden=True)
self.get_toolbar_item('delete_projector', hidden=True)
else:
self.get_toolbar_item('view_projector', hidden=True)
self.get_toolbar_item('source_view_projector', hidden=True)
self.get_toolbar_item('edit_projector', enabled=True)
self.get_toolbar_item('delete_projector', enabled=True)
self.get_toolbar_item('connect_projector', enabled=not connected)
self.get_toolbar_item('disconnect_projector', enabled=connected)
self.get_toolbar_item('poweron_projector', enabled=connected and (projector.link.power == S_STANDBY))
self.get_toolbar_item('poweroff_projector', enabled=connected and (projector.link.power == S_ON))
if projector.link.shutter is not None:
self.get_toolbar_item('blank_projector', enabled=(connected and power and not projector.link.shutter))
self.get_toolbar_item('show_projector', enabled=(connected and power and projector.link.shutter))
else:
self.get_toolbar_item('blank_projector', enabled=False)
self.get_toolbar_item('show_projector', enabled=False)
else:
self.get_toolbar_item('edit_projector', enabled=False)
self.get_toolbar_item('delete_projector', enabled=False)
self.get_toolbar_item('view_projector', hidden=True)
self.get_toolbar_item('source_view_projector', hidden=True)
self.get_toolbar_item('connect_projector', hidden=True)
self.get_toolbar_item('disconnect_projector', hidden=True)
self.get_toolbar_item('poweron_projector', hidden=True)
self.get_toolbar_item('poweroff_projector', hidden=True)
self.get_toolbar_item('blank_projector', hidden=True)
self.get_toolbar_item('show_projector', hidden=True)
self.get_toolbar_item('connect_projector_multiple', hidden=False, enabled=True)
self.get_toolbar_item('disconnect_projector_multiple', hidden=False, enabled=True)
self.get_toolbar_item('poweron_projector_multiple', hidden=False, enabled=True)
self.get_toolbar_item('poweroff_projector_multiple', hidden=False, enabled=True)
self.get_toolbar_item('blank_projector_multiple', hidden=False, enabled=True)
self.get_toolbar_item('show_projector_multiple', hidden=False, enabled=True)
@pyqtSlot(str)
def authentication_error(self, name):
"""
Display warning dialog when attempting to connect with invalid pin
:param name: Name from QListWidgetItem
"""
QtGui.QMessageBox.warning(self, translate('OpenLP.ProjectorManager',
'"%s" Authentication Error' % name),
'<br />There was an authentication error while trying to connect.'
'<br /><br />Please verify your PIN setting '
'for projector item "%s"' % name)
@pyqtSlot(str)
def no_authentication_error(self, name):
"""
Display warning dialog when pin saved for item but projector does not
require pin.
:param name: Name from QListWidgetItem
"""
QtGui.QMessageBox.warning(self, translate('OpenLP.ProjectorManager',
'"%s" No Authentication Error' % name),
'<br />PIN is set and projector does not require authentication.'
'<br /><br />Please verify your PIN setting '
'for projector item "%s"' % name)
class ProjectorItem(QObject):
"""
Class for the projector list widget item.
NOTE: Actual PJLink class instance should be saved as self.link
"""
def __init__(self, link=None):
"""
Initialization for ProjectorItem instance
:param link: PJLink1 instance for QListWidgetItem
"""
self.link = link
self.thread = None
self.icon = None
self.widget = None
self.my_parent = None
self.timer = None
self.projectordb_item = None
self.poll_time = None
self.socket_timeout = None
self.status = S_NOT_CONNECTED
super(ProjectorItem, self).__init__()
def not_implemented(function):
"""
Temporary function to build an information message box indicating function not implemented yet
:param func: Function name
"""
QtGui.QMessageBox.information(None,
translate('OpenLP.ProjectorManager', 'Not Implemented Yet'),
translate('OpenLP.ProjectorManager',
'Function "%s"<br />has not been implemented yet.'
'<br />Please check back again later.' % function))

View File

@ -0,0 +1,500 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2014 Raoul Snyman #
# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan #
# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
# Christian Richter, Philip Ridout, Ken Roberts, Simon Scudder, #
# Jeffrey Smith, Maikel Stuivenberg, Martin Thompson, Jon Tibble, #
# Dave Warnock, Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
:mod: `openlp.core.ui.projector.sourceselectform` module
Provides the dialog window for selecting video source for projector.
"""
import logging
log = logging.getLogger(__name__)
log.debug('editform loaded')
from PyQt4 import QtCore, QtGui
from PyQt4.QtCore import pyqtSlot, QSize
from PyQt4.QtGui import QDialog, QButtonGroup, QDialogButtonBox, QFormLayout, QLineEdit, QRadioButton, \
QStyle, QStylePainter, QStyleOptionTab, QTabBar, QTabWidget, QVBoxLayout, QWidget
from openlp.core.common import translate, is_macosx
from openlp.core.lib import build_icon
from openlp.core.lib.projector.db import ProjectorSource
from openlp.core.lib.projector.constants import PJLINK_DEFAULT_SOURCES, PJLINK_DEFAULT_CODES
def source_group(inputs, source_text):
"""
Return a dictionary where key is source[0] and values are inputs
grouped by source[0].
source_text = dict{"key1": "key1-text",
"key2": "key2-text",
...}
return:
dict{ key1[0]: { "key11": "key11-text",
"key12": "key12-text",
"key13": "key13-text",
... }
key2[0]: {"key21": "key21-text",
"key22": "key22-text",
... }
:param inputs: List of inputs
:param source_text: Dictionary of {code: text} values to display
:returns: dict
"""
groupdict = {}
keydict = {}
checklist = inputs
key = checklist[0][0]
for item in checklist:
if item[0] == key:
groupdict[item] = source_text[item]
continue
else:
keydict[key] = groupdict
key = item[0]
groupdict = {item: source_text[item]}
keydict[key] = groupdict
return keydict
def Build_Tab(group, source_key, default, projector, projectordb, edit=False):
"""
Create the radio button page for a tab.
Dictionary will be a 1-key entry where key=tab to setup, val=list of inputs.
source_key: {"groupkey1": {"key11": "key11-text",
"key12": "key12-text",
...
},
"groupkey2": {"key21": "key21-text",
"key22": "key22-text",
....
},
...
}
:param group: Button group widget to add buttons to
:param source_key: Dictionary of sources for radio buttons
:param default: Default radio button to check
:param projector: Projector instance
:param projectordb: ProjectorDB instance for session
:param edit: If we're editing the source text
"""
buttonchecked = False
widget = QWidget()
layout = QFormLayout() if edit else QVBoxLayout()
layout.setSpacing(10)
widget.setLayout(layout)
tempkey = list(source_key.keys())[0] # Should only be 1 key
sourcelist = list(source_key[tempkey])
sourcelist.sort()
button_count = len(sourcelist)
if edit:
for key in sourcelist:
item = QLineEdit()
item.setObjectName('source_key_%s' % key)
source_item = projectordb.get_source_by_code(code=key, projector_id=projector.db_item.id)
if source_item is None:
item.setText(PJLINK_DEFAULT_CODES[key])
else:
item.setText(source_item.text)
layout.addRow(PJLINK_DEFAULT_CODES[key], item)
group.append(item)
else:
for key in sourcelist:
source_item = projectordb.get_source_by_code(code=key, projector_id=projector.db_item.id)
if source_item is None:
text = source_key[tempkey][key]
else:
text = source_item.text
itemwidget = QRadioButton(text)
itemwidget.setAutoExclusive(True)
if default == key:
itemwidget.setChecked(True)
buttonchecked = itemwidget.isChecked() or buttonchecked
group.addButton(itemwidget, int(key))
layout.addWidget(itemwidget)
layout.addStretch()
return widget, button_count, buttonchecked
def set_button_tooltip(bar):
"""
Set the toolip for the standard buttons used
:param bar: QDialogButtonBar instance to update
"""
for button in bar.buttons():
if bar.standardButton(button) == QDialogButtonBox.Cancel:
tip = "Ignoring current changes and return to OpenLP"
elif bar.standardButton(button) == QDialogButtonBox.Reset:
tip = "Delete all user-defined text and revert to PJLink default text"
elif bar.standardButton(button) == QDialogButtonBox.Discard:
tip = "Discard changes and reset to previous user-defined text"
elif bar.standardButton(button) == QDialogButtonBox.Ok:
tip = "Save changes and return to OpenLP"
else:
tip = ""
button.setToolTip(tip)
class FingerTabBarWidget(QTabBar):
"""
Realign west -orientation tabs to left-right text rather than south-north text
Borrowed from
http://www.kidstrythisathome.com/2013/03/fingertabs-horizontal-tabs-with-horizontal-text-in-pyqt/
"""
def __init__(self, parent=None, *args, **kwargs):
"""
Reset tab text orientation on initialization
:param width: Remove default width parameter in kwargs
:param height: Remove default height parameter in kwargs
"""
self.tabSize = QSize(kwargs.pop('width', 100), kwargs.pop('height', 25))
QTabBar.__init__(self, parent, *args, **kwargs)
def paintEvent(self, event):
"""
Repaint tab in left-right text orientation.
:param event: Repaint event signal
"""
painter = QStylePainter(self)
option = QStyleOptionTab()
for index in range(self.count()):
self.initStyleOption(option, index)
tabRect = self.tabRect(index)
tabRect.moveLeft(10)
painter.drawControl(QStyle.CE_TabBarTabShape, option)
painter.drawText(tabRect, QtCore.Qt.AlignVCenter |
QtCore.Qt.TextDontClip,
self.tabText(index))
painter.end()
def tabSizeHint(self, index):
"""
Return tabsize
:param index: Tab index to fetch tabsize from
:returns: instance tabSize
"""
return self.tabSize
class FingerTabWidget(QTabWidget):
"""
A QTabWidget equivalent which uses our FingerTabBarWidget
Based on thread discussion
http://www.riverbankcomputing.com/pipermail/pyqt/2005-December/011724.html
"""
def __init__(self, parent, *args):
"""
Initialize FingerTabWidget instance
"""
QTabWidget.__init__(self, parent, *args)
self.setTabBar(FingerTabBarWidget(self))
class SourceSelectTabs(QDialog):
"""
Class for handling selecting the source for the projector to use.
Uses tabbed interface.
"""
def __init__(self, parent, projectordb, edit=False):
"""
Build the source select dialog using tabbed interface.
:param projectordb: ProjectorDB session to use
"""
log.debug('Initializing SourceSelectTabs()')
super(SourceSelectTabs, self).__init__(parent)
self.projectordb = projectordb
self.edit = edit
if self.edit:
title = translate('OpenLP.SourceSelectForm', 'Select Projector Source')
else:
title = translate('OpenLP.SourceSelectForm', 'Edit Projector Source Text')
self.setWindowTitle(title)
self.setObjectName('source_select_tabs')
self.setWindowIcon(build_icon(':/icon/openlp-log-32x32.png'))
self.setModal(True)
self.layout = QVBoxLayout()
self.layout.setObjectName('source_select_tabs_layout')
if is_macosx():
self.tabwidget = QTabWidget(self)
else:
self.tabwidget = FingerTabWidget(self)
self.tabwidget.setObjectName('source_select_tabs_tabwidget')
self.tabwidget.setUsesScrollButtons(False)
if is_macosx():
self.tabwidget.setTabPosition(QTabWidget.North)
else:
self.tabwidget.setTabPosition(QTabWidget.West)
self.layout.addWidget(self.tabwidget)
self.setLayout(self.layout)
def exec_(self, projector):
"""
Override initial method so we can build the tabs.
:param projector: Projector instance to build source list from
"""
self.projector = projector
self.source_text = self.projectordb.get_source_list(projector=projector)
self.source_group = source_group(projector.source_available, self.source_text)
# self.source_group = {'4': {'41': 'Storage 1'}, '5': {"51": 'Network 1'}}
self.button_group = [] if self.edit else QButtonGroup()
keys = list(self.source_group.keys())
keys.sort()
if self.edit:
for key in keys:
(tab, button_count, buttonchecked) = Build_Tab(group=self.button_group,
source_key={key: self.source_group[key]},
default=self.projector.source,
projector=self.projector,
projectordb=self.projectordb,
edit=self.edit)
thistab = self.tabwidget.addTab(tab, PJLINK_DEFAULT_SOURCES[key])
if buttonchecked:
self.tabwidget.setCurrentIndex(thistab)
else:
for key in keys:
(tab, button_count, buttonchecked) = Build_Tab(group=self.button_group,
source_key={key: self.source_group[key]},
default=self.projector.source,
projector=self.projector,
projectordb=self.projectordb,
edit=self.edit)
thistab = self.tabwidget.addTab(tab, PJLINK_DEFAULT_SOURCES[key])
if buttonchecked:
self.tabwidget.setCurrentIndex(thistab)
self.button_box = QDialogButtonBox(QtGui.QDialogButtonBox.Reset |
QtGui.QDialogButtonBox.Discard |
QtGui.QDialogButtonBox.Ok |
QtGui.QDialogButtonBox.Cancel)
self.button_box.clicked.connect(self.button_clicked)
self.layout.addWidget(self.button_box)
set_button_tooltip(self.button_box)
selected = super(SourceSelectTabs, self).exec_()
return selected
@pyqtSlot(object)
def button_clicked(self, button):
"""
Checks which button was clicked
:param button: Button that was clicked
:returns: Ok: Saves current edits
Delete: Resets text to last-saved text
Reset: Reset all text to PJLink default text
Cancel: Cancel text edit
"""
if self.button_box.standardButton(button) == self.button_box.Cancel:
self.done(0)
elif self.button_box.standardButton(button) == self.button_box.Reset:
self.delete_sources()
elif self.button_box.standardButton(button) == self.button_box.Discard:
self.done(100)
elif self.button_box.standardButton(button) == self.button_box.Ok:
return self.accept_me()
else:
return 100
def delete_sources(self):
msg = QtGui.QMessageBox()
msg.setText('Delete entries for this projector')
msg.setInformativeText('Are you sure you want to delete ALL user-defined '
'source input text for this projector?')
msg.setStandardButtons(msg.Cancel | msg.Ok)
msg.setDefaultButton(msg.Cancel)
ans = msg.exec_()
if ans == msg.Cancel:
return
self.projectordb.delete_all_objects(ProjectorSource, ProjectorSource.projector_id == self.projector.db_item.id)
self.done(100)
def accept_me(self):
"""
Slot to accept 'OK' button
"""
projector = self.projector.db_item
if self.edit:
for key in self.button_group:
code = key.objectName().split("_")[-1]
text = key.text().strip()
if key.text().strip().lower() == PJLINK_DEFAULT_CODES[code].strip().lower():
continue
item = self.projectordb.get_source_by_code(code=code, projector_id=projector.id)
if item is None:
log.debug("(%s) Adding new source text %s: %s" % (projector.ip, code, text))
item = ProjectorSource(projector_id=projector.id, code=code, text=text)
else:
item.text = text
log.debug('(%s) Updating source code %s with text="%s"' % (projector.ip, item.code, item.text))
self.projectordb.add_source(item)
selected = 0
else:
selected = self.button_group.checkedId()
log.debug('SourceSelectTabs().accepted() Setting source to %s' % selected)
self.done(selected)
class SourceSelectSingle(QDialog):
"""
Class for handling selecting the source for the projector to use.
Uses single dialog interface.
"""
def __init__(self, parent, projectordb, edit=False):
"""
Build the source select dialog.
:param projectordb: ProjectorDB session to use
"""
log.debug('Initializing SourceSelectSingle()')
self.projectordb = projectordb
super(SourceSelectSingle, self).__init__(parent)
self.setWindowTitle(translate('OpenLP.SourceSelectSingle', 'Select Projector Source'))
self.setObjectName('source_select_single')
self.setWindowIcon(build_icon(':/icon/openlp-log-32x32.png'))
self.setModal(True)
self.edit = edit
def exec_(self, projector, edit=False):
"""
Override initial method so we can build the tabs.
:param projector: Projector instance to build source list from
"""
self.projector = projector
self.layout = QFormLayout() if self.edit else QVBoxLayout()
self.layout.setObjectName('source_select_tabs_layout')
self.layout.setSpacing(10)
self.setLayout(self.layout)
self.setMinimumWidth(350)
self.button_group = [] if self.edit else QButtonGroup()
self.source_text = self.projectordb.get_source_list(projector=projector)
keys = list(self.source_text.keys())
keys.sort()
key_count = len(keys)
button_list = []
if self.edit:
for key in keys:
item = QLineEdit()
item.setObjectName('source_key_%s' % key)
source_item = self.projectordb.get_source_by_code(code=key, projector_id=self.projector.db_item.id)
if source_item is None:
item.setText(PJLINK_DEFAULT_CODES[key])
else:
item.old_text = item.text()
item.setText(source_item.text)
self.layout.addRow(PJLINK_DEFAULT_CODES[key], item)
self.button_group.append(item)
else:
for key in keys:
source_text = self.projectordb.get_source_by_code(code=key, projector_id=self.projector.db_item.id)
text = self.source_text[key] if source_text is None else source_text.text
button = QtGui.QRadioButton(text)
button.setChecked(True if key == projector.source else False)
self.layout.addWidget(button)
self.button_group.addButton(button, int(key))
button_list.append(key)
self.button_box = QDialogButtonBox(QtGui.QDialogButtonBox.Reset |
QtGui.QDialogButtonBox.Discard |
QtGui.QDialogButtonBox.Ok |
QtGui.QDialogButtonBox.Cancel)
self.button_box.clicked.connect(self.button_clicked)
self.layout.addWidget(self.button_box)
self.setMinimumHeight(key_count*25)
set_button_tooltip(self.button_box)
selected = super(SourceSelectSingle, self).exec_()
return selected
@pyqtSlot(object)
def button_clicked(self, button):
"""
Checks which button was clicked
:param button: Button that was clicked
:returns: Ok: Saves current edits
Delete: Resets text to last-saved text
Reset: Reset all text to PJLink default text
Cancel: Cancel text edit
"""
if self.button_box.standardButton(button) == self.button_box.Cancel:
self.done(0)
elif self.button_box.standardButton(button) == self.button_box.Reset:
self.delete_sources()
elif self.button_box.standardButton(button) == self.button_box.Discard:
self.done(100)
elif self.button_box.standardButton(button) == self.button_box.Ok:
return self.accept_me()
else:
return 100
def delete_sources(self):
msg = QtGui.QMessageBox()
msg.setText('Delete entries for this projector')
msg.setInformativeText('Are you sure you want to delete ALL user-defined '
'source input text for this projector?')
msg.setStandardButtons(msg.Cancel | msg.Ok)
msg.setDefaultButton(msg.Cancel)
ans = msg.exec_()
if ans == msg.Cancel:
return
self.projectordb.delete_all_objects(ProjectorSource, ProjectorSource.projector_id == self.projector.db_item.id)
self.done(100)
@pyqtSlot()
def accept_me(self):
"""
Slot to accept 'OK' button
"""
projector = self.projector.db_item
if self.edit:
for key in self.button_group:
code = key.objectName().split("_")[-1]
text = key.text().strip()
if key.text().strip().lower() == PJLINK_DEFAULT_CODES[code].strip().lower():
continue
item = self.projectordb.get_source_by_code(code=code, projector_id=projector.id)
if item is None:
log.debug("(%s) Adding new source text %s: %s" % (projector.ip, code, text))
item = ProjectorSource(projector_id=projector.id, code=code, text=text)
else:
item.text = text
log.debug('(%s) Updating source code %s with text="%s"' % (projector.ip, item.code, item.text))
self.projectordb.add_source(item)
selected = 0
else:
selected = self.button_group.checkedId()
log.debug('SourceSelectDialog().accepted() Setting source to %s' % selected)
self.done(selected)

View File

@ -0,0 +1,146 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2014 Raoul Snyman #
# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan #
# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
# Christian Richter, Philip Ridout, Ken Roberts, Simon Scudder, #
# Jeffrey Smith, Maikel Stuivenberg, Martin Thompson, Jon Tibble, #
# Dave Warnock, Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
:mod:`openlp.core.ui.projector.tab`
Provides the settings tab in the settings dialog.
"""
import logging
log = logging.getLogger(__name__)
log.debug('projectortab module loaded')
from PyQt4 import QtCore, QtGui
from openlp.core.common import Settings, UiStrings, translate
from openlp.core.lib import SettingsTab
from openlp.core.lib.projector import DialogSourceStyle
class ProjectorTab(SettingsTab):
"""
Openlp Settings -> Projector settings
"""
def __init__(self, parent):
"""
ProjectorTab initialization
:param parent: Parent widget
"""
self.icon_path = ':/projector/projector_manager.png'
projector_translated = translate('OpenLP.ProjectorTab', 'Projector')
super(ProjectorTab, self).__init__(parent, 'Projector', projector_translated)
def setupUi(self):
"""
Setup the UI
"""
self.setObjectName('ProjectorTab')
super(ProjectorTab, self).setupUi()
self.connect_box = QtGui.QGroupBox(self.left_column)
self.connect_box.setObjectName('connect_box')
self.connect_box_layout = QtGui.QFormLayout(self.connect_box)
self.connect_box_layout.setObjectName('connect_box_layout')
# Start comms with projectors on startup
self.connect_on_startup = QtGui.QCheckBox(self.connect_box)
self.connect_on_startup.setObjectName('connect_on_startup')
self.connect_box_layout.addRow(self.connect_on_startup)
# Socket timeout
self.socket_timeout_label = QtGui.QLabel(self.connect_box)
self.socket_timeout_label.setObjectName('socket_timeout_label')
self.socket_timeout_spin_box = QtGui.QSpinBox(self.connect_box)
self.socket_timeout_spin_box.setObjectName('socket_timeout_spin_box')
self.socket_timeout_spin_box.setMinimum(2)
self.socket_timeout_spin_box.setMaximum(10)
self.connect_box_layout.addRow(self.socket_timeout_label, self.socket_timeout_spin_box)
# Poll interval
self.socket_poll_label = QtGui.QLabel(self.connect_box)
self.socket_poll_label.setObjectName('socket_poll_label')
self.socket_poll_spin_box = QtGui.QSpinBox(self.connect_box)
self.socket_poll_spin_box.setObjectName('socket_timeout_spin_box')
self.socket_poll_spin_box.setMinimum(5)
self.socket_poll_spin_box.setMaximum(60)
self.connect_box_layout.addRow(self.socket_poll_label, self.socket_poll_spin_box)
self.left_layout.addWidget(self.connect_box)
# Source input select dialog box type
self.dialog_type_label = QtGui.QLabel(self.connect_box)
self.dialog_type_label.setObjectName('dialog_type_label')
self.dialog_type_combo_box = QtGui.QComboBox(self.connect_box)
self.dialog_type_combo_box.setObjectName('dialog_type_combo_box')
self.dialog_type_combo_box.addItems(['', ''])
self.connect_box_layout.addRow(self.dialog_type_label, self.dialog_type_combo_box)
self.left_layout.addStretch()
self.dialog_type_combo_box.activated.connect(self.on_dialog_type_combo_box_changed)
def retranslateUi(self):
"""
Translate the UI on the fly
"""
self.tab_title_visible = UiStrings().Projectors
self.connect_box.setTitle(
translate('OpenLP.ProjectorTab', 'Communication Options'))
self.connect_on_startup.setText(
translate('OpenLP.ProjectorTab', 'Connect to projectors on startup'))
self.socket_timeout_label.setText(
translate('OpenLP.ProjectorTab', 'Socket timeout (seconds)'))
self.socket_poll_label.setText(
translate('OpenLP.ProjectorTab', 'Poll time (seconds)'))
self.dialog_type_label.setText(
translate('Openlp.ProjectorTab', 'Source select dialog interface'))
self.dialog_type_combo_box.setItemText(DialogSourceStyle.Tabbed,
translate('OpenLP.ProjectorTab', 'Tabbed dialog box'))
self.dialog_type_combo_box.setItemText(DialogSourceStyle.Single,
translate('OpenLP.ProjectorTab', 'Single dialog box'))
def load(self):
"""
Load the projector settings on startup
"""
settings = Settings()
settings.beginGroup(self.settings_section)
self.connect_on_startup.setChecked(settings.value('connect on start'))
self.socket_timeout_spin_box.setValue(settings.value('socket timeout'))
self.socket_poll_spin_box.setValue(settings.value('poll time'))
self.dialog_type_combo_box.setCurrentIndex(settings.value('source dialog type'))
settings.endGroup()
def save(self):
"""
Save the projector settings
"""
settings = Settings()
settings.beginGroup(self.settings_section)
settings.setValue('connect on start', self.connect_on_startup.isChecked())
settings.setValue('socket timeout', self.socket_timeout_spin_box.value())
settings.setValue('poll time', self.socket_poll_spin_box.value())
settings.setValue('source dialog type', self.dialog_type_combo_box.currentIndex())
settings.endGroup
def on_dialog_type_combo_box_changed(self):
self.dialog_type = self.dialog_type_combo_box.currentIndex()

View File

@ -38,6 +38,7 @@ from openlp.core.lib import build_icon
from openlp.core.ui import AdvancedTab, GeneralTab, ThemesTab
from openlp.core.ui.media import PlayerTab
from .settingsdialog import Ui_SettingsDialog
from openlp.core.ui.projector.tab import ProjectorTab
log = logging.getLogger(__name__)
@ -70,6 +71,7 @@ class SettingsForm(QtGui.QDialog, Ui_SettingsDialog, RegistryProperties):
self.insert_tab(self.themes_tab)
self.insert_tab(self.advanced_tab)
self.insert_tab(self.player_tab)
self.insert_tab(self.projector_tab)
for plugin in self.plugin_manager.plugins:
if plugin.settings_tab:
self.insert_tab(plugin.settings_tab, plugin.is_active())
@ -96,9 +98,21 @@ class SettingsForm(QtGui.QDialog, Ui_SettingsDialog, RegistryProperties):
Process the form saving the settings
"""
log.debug('Processing settings exit')
for tabIndex in range(self.stacked_layout.count()):
self.stacked_layout.widget(tabIndex).save()
# if the display of image background are changing we need to regenerate the image cache
# We add all the forms into the stacked layout, even if the plugin is inactive,
# but we don't add the item to the list on the side if the plugin is inactive,
# so loop through the list items, and then find the tab for that item.
for item_index in range(self.setting_list_widget.count()):
# Get the list item
list_item = self.setting_list_widget.item(item_index)
if not list_item:
continue
# Now figure out if there's a tab for it, and save the tab.
plugin_name = list_item.data(QtCore.Qt.UserRole)
for tab_index in range(self.stacked_layout.count()):
tab_widget = self.stacked_layout.widget(tab_index)
if tab_widget.tab_title == plugin_name:
tab_widget.save()
# if the image background has been changed we need to regenerate the image cache
if 'images_config_updated' in self.processes or 'config_screen_changed' in self.processes:
self.register_post_process('images_regenerate')
# Now lets process all the post save handlers
@ -123,6 +137,8 @@ class SettingsForm(QtGui.QDialog, Ui_SettingsDialog, RegistryProperties):
self.general_tab = GeneralTab(self)
# Themes tab
self.themes_tab = ThemesTab(self)
# Projector Tab
self.projector_tab = ProjectorTab(self)
# Advanced tab
self.advanced_tab = AdvancedTab(self)
# Advanced tab
@ -143,6 +159,9 @@ class SettingsForm(QtGui.QDialog, Ui_SettingsDialog, RegistryProperties):
"""
# Get the item we clicked on
list_item = self.setting_list_widget.item(item_index)
# Quick exit to the left if the item doesn't exist (maybe -1?)
if not list_item:
return
# Loop through the list of tabs in the stacked layout
for tab_index in range(self.stacked_layout.count()):
# Get the widget

View File

@ -1245,7 +1245,7 @@ class SlideController(DisplayController, RegistryProperties):
if event.timerId() == self.timer_id:
self.on_slide_selected_next(self.play_slides_loop.isChecked())
def on_edit_song(self):
def on_edit_song(self, field=None):
"""
From the preview display requires the service Item to be editied
"""
@ -1254,7 +1254,7 @@ class SlideController(DisplayController, RegistryProperties):
if new_item:
self.add_service_item(new_item)
def on_preview_add_to_service(self):
def on_preview_add_to_service(self, field=None):
"""
From the preview display request the Item to be added to service
"""
@ -1351,7 +1351,7 @@ class SlideController(DisplayController, RegistryProperties):
seconds %= 60
self.audio_time_label.setText(' %02d:%02d ' % (minutes, seconds))
def on_track_triggered(self):
def on_track_triggered(self, field=None):
"""
Start playing a track
"""

View File

@ -135,11 +135,11 @@ class PptviewDocument(PresentationDocument):
self.file_path = os.path.normpath(self.file_path)
preview_path = os.path.join(temp_folder, 'slide')
# Ensure that the paths are null terminated
self.file_path = self.file_path.encode('utf-16-le') + b'\0'
byte_file_path = self.file_path.encode('utf-16-le') + b'\0'
preview_path = preview_path.encode('utf-16-le') + b'\0'
if not os.path.isdir(temp_folder):
os.makedirs(temp_folder)
self.ppt_id = self.controller.process.OpenPPT(self.file_path, None, rect, preview_path)
self.ppt_id = self.controller.process.OpenPPT(byte_file_path, None, rect, preview_path)
if self.ppt_id >= 0:
self.create_thumbnails()
self.stop_presentation()

View File

@ -526,7 +526,6 @@ class HttpRouter(RegistryProperties):
Settings().value('remotes/thumbnails'):
# If the file is under our app directory tree send the portion after the match
data_path = AppLocation.get_data_path()
print(frame)
if frame['image'][0:len(data_path)] == data_path:
item['img'] = urllib.request.pathname2url(frame['image'][len(data_path):])
item['text'] = str(frame['title'])
@ -534,7 +533,6 @@ class HttpRouter(RegistryProperties):
item['selected'] = (self.live_controller.selected_row == index)
if current_item.notes:
item['notes'] = item.get('notes', '') + '\n' + current_item.notes
print(item)
data.append(item)
json_data = {'results': {'slides': data}}
if current_item:

View File

@ -625,7 +625,8 @@ class EditSongForm(QtGui.QDialog, Ui_EditSongDialog, RegistryProperties):
"""
Remove the author from the list when the delete button is clicked.
"""
self.author_remove_button.setEnabled(False)
if self.authors_list_view.count() <= 2:
self.author_remove_button.setEnabled(False)
item = self.authors_list_view.currentItem()
row = self.authors_list_view.row(item)
self.authors_list_view.takeItem(row)

View File

@ -106,6 +106,7 @@
<file>wizard_firsttime.bmp</file>
<file>wizard_createtheme.bmp</file>
<file>wizard_duplicateremoval.bmp</file>
<file>wizard_createprojector.png</file>
</qresource>
<qresource prefix="services">
<file>service_collapse_all.png</file>
@ -169,6 +170,34 @@
<file>theme_new.png</file>
<file>theme_edit.png</file>
</qresource>
<qresource prefix="projector">
<file>projector_blank.png</file>
<file>projector_blank_tiled.png</file>
<file>projector_connect.png</file>
<file>projector_connect_tiled.png</file>
<file>projector_hdmi.png</file>
<file>projector_cooldown.png</file>
<file>projector_disconnect.png</file>
<file>projector_disconnect_tiled.png</file>
<file>projector_edit.png</file>
<file>projector_error.png</file>
<file>projector_item_connect.png</file>
<file>projector_item_disconnect.png</file>
<file>projector_manager.png</file>
<file>projector_new.png</file>
<file>projector_not_connected_error.png</file>
<file>projector_off.png</file>
<file>projector_on.png</file>
<file>projector_power_off.png</file>
<file>projector_power_off_tiled.png</file>
<file>projector_power_on.png</file>
<file>projector_power_on_tiled.png</file>
<file>projector_show.png</file>
<file>projector_show_tiled.png</file>
<file>projector_spacer.png</file>
<file>projector_warmup.png</file>
<file>projector_view.png</file>
</qresource>
<qresource prefix="remotes">
<file>android_app_qr.png</file>
</qresource>

Binary file not shown.

After

Width:  |  Height:  |  Size: 385 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 652 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 928 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 920 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 855 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 914 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 726 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 351 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 842 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 781 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 851 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 894 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 889 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 935 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 744 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 893 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 694 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

View File

@ -53,5 +53,6 @@ cat openlp/core/resources.py.new | sed '/# Created: /d;/# by: /d' > openlp/
patch --posix -s openlp/core/resources.py scripts/resources.patch
# Remove temporary file
rm openlp/core/resources.py.new
rm openlp/core/resources.py.new 2>/dev/null
rm openlp/core/resources.py.old 2>/dev/null
rm openlp/core/resources.py.orig 2>/dev/null

View File

@ -0,0 +1,163 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2014 Raoul Snyman #
# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan #
# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, #
# Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
Package to test the openlp.core.ui.projector.networkutils package.
"""
from unittest import TestCase
from openlp.core.common import verify_ip_address, md5_hash, qmd5_hash
salt = '498e4a67'
pin = 'JBMIAProjectorLink'
test_hash = '5d8409bc1c3fa39749434aa3a5c38682'
ip4_loopback = '127.0.0.1'
ip4_local = '192.168.1.1'
ip4_broadcast = '255.255.255.255'
ip4_bad = '192.168.1.256'
ip6_loopback = '::1'
ip6_link_local = 'fe80::223:14ff:fe99:d315'
ip6_bad = 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff'
class testProjectorUtilities(TestCase):
"""
Validate functions in the projector utilities module
"""
def test_ip4_loopback_valid(self):
"""
Test IPv4 loopbackvalid
"""
# WHEN: Test with a local loopback test
valid = verify_ip_address(addr=ip4_loopback)
# THEN: Verify we received True
self.assertTrue(valid, 'IPv4 loopback address should have been valid')
def test_ip4_local_valid(self):
"""
Test IPv4 local valid
"""
# WHEN: Test with a local loopback test
valid = verify_ip_address(addr=ip4_local)
# THEN: Verify we received True
self.assertTrue(valid, 'IPv4 local address should have been valid')
def test_ip4_broadcast_valid(self):
"""
Test IPv4 broadcast valid
"""
# WHEN: Test with a local loopback test
valid = verify_ip_address(addr=ip4_broadcast)
# THEN: Verify we received True
self.assertTrue(valid, 'IPv4 broadcast address should have been valid')
def test_ip4_address_invalid(self):
"""
Test IPv4 address invalid
"""
# WHEN: Test with a local loopback test
valid = verify_ip_address(addr=ip4_bad)
# THEN: Verify we received True
self.assertFalse(valid, 'Bad IPv4 address should not have been valid')
def test_ip6_loopback_valid(self):
"""
Test IPv6 loopback valid
"""
# WHEN: Test IPv6 loopback address
valid = verify_ip_address(addr=ip6_loopback)
# THEN: Validate return
self.assertTrue(valid, 'IPv6 loopback address should have been valid')
def test_ip6_local_valid(self):
"""
Test IPv6 link-local valid
"""
# WHEN: Test IPv6 link-local address
valid = verify_ip_address(addr=ip6_link_local)
# THEN: Validate return
self.assertTrue(valid, 'IPv6 link-local address should have been valid')
def test_ip6_address_invalid(self):
"""
Test NetworkUtils IPv6 address invalid
"""
# WHEN: Given an invalid IPv6 address
valid = verify_ip_address(addr=ip6_bad)
# THEN: Validate bad return
self.assertFalse(valid, 'IPv6 bad address should have been invalid')
def test_md5_hash(self):
"""
Test MD5 hash from salt+data pass (python)
"""
# WHEN: Given a known salt+data
hash_ = md5_hash(salt=salt, data=pin)
# THEN: Validate return has is same
self.assertEquals(hash_, test_hash, 'MD5 should have returned a good hash')
def test_md5_hash_bad(self):
"""
Test MD5 hash from salt+data fail (python)
"""
# WHEN: Given a different salt+hash
hash_ = md5_hash(salt=pin, data=salt)
# THEN: return data is different
self.assertNotEquals(hash_, test_hash, 'MD5 should have returned a bad hash')
def test_qmd5_hash(self):
"""
Test MD5 hash from salt+data pass (Qt)
"""
# WHEN: Given a known salt+data
hash_ = qmd5_hash(salt=salt, data=pin)
# THEN: Validate return has is same
self.assertEquals(hash_, test_hash, 'Qt-MD5 should have returned a good hash')
def test_qmd5_hash_bad(self):
"""
Test MD5 hash from salt+hash fail (Qt)
"""
# WHEN: Given a different salt+hash
hash_ = qmd5_hash(salt=pin, data=salt)
# THEN: return data is different
self.assertNotEquals(hash_, test_hash, 'Qt-MD5 should have returned a bad hash')

View File

@ -27,5 +27,28 @@
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
Package to test the openlp.core.lib package.
Module-level functions for the functional test suite
"""
import os
from tests.functional import patch
from openlp.core.common import is_win
from .test_projectordb import tmpfile
def setUp():
if not is_win():
# Wine creates a sharing violation during tests. Ignore.
try:
os.remove(tmpfile)
except:
pass
def tearDown():
"""
Ensure test suite has been cleaned up after tests
"""
patch.stopall()

View File

@ -0,0 +1,160 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2014 Raoul Snyman #
# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan #
# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, #
# Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
Package to test the openlp.core.ui.projectordb find, edit, delete
record functions.
PREREQUISITE: add_record() and get_all() functions validated.
"""
from unittest import TestCase
from tests.functional import MagicMock, patch
from openlp.core.lib.projector.db import Projector, ProjectorDB
from tests.resources.projector.data import TEST1_DATA, TEST2_DATA, TEST3_DATA
tmpfile = '/tmp/openlp-test-projectordb.sql'
def compare_data(one, two):
"""
Verify two Projector() instances contain the same data
"""
return one is not None and \
two is not None and \
one.ip == two.ip and \
one.port == two.port and \
one.name == two.name and \
one.location == two.location and \
one.notes == two.notes
def add_records(self, test):
"""
Add record if not in database
"""
record_list = self.projector.get_projector_all()
if len(record_list) < 1:
added = False
for record in test:
added = self.projector.add_projector(record) or added
return added
for new_record in test:
added = None
for record in record_list:
if compare_data(record, new_record):
break
added = self.projector.add_projector(new_record)
return added
class TestProjectorDB(TestCase):
"""
Test case for ProjectorDB
"""
def setUp(self):
"""
Set up anything necessary for all tests
"""
if not hasattr(self, 'projector'):
with patch('openlp.core.lib.projector.db.init_url') as mocked_init_url:
mocked_init_url.start()
mocked_init_url.return_value = 'sqlite:///%s' % tmpfile
self.projector = ProjectorDB()
def find_record_by_ip_test(self):
"""
Test find record by IP
"""
# GIVEN: Record entries in database
add_records(self, [TEST1_DATA, TEST2_DATA])
# WHEN: Search for record using IP
record = self.projector.get_projector_by_ip(TEST2_DATA.ip)
# THEN: Verify proper record returned
self.assertTrue(compare_data(TEST2_DATA, record),
'Record found should have been test_2 data')
def find_record_by_name_test(self):
"""
Test find record by name
"""
# GIVEN: Record entries in database
add_records(self, [TEST1_DATA, TEST2_DATA])
# WHEN: Search for record using name
record = self.projector.get_projector_by_name(TEST2_DATA.name)
# THEN: Verify proper record returned
self.assertTrue(compare_data(TEST2_DATA, record),
'Record found should have been test_2 data')
def record_delete_test(self):
"""
Test record can be deleted
"""
# GIVEN: Record in database
add_records(self, [TEST3_DATA, ])
record = self.projector.get_projector_by_ip(TEST3_DATA.ip)
# WHEN: Record deleted
self.projector.delete_projector(record)
# THEN: Verify record not retrievable
found = self.projector.get_projector_by_ip(TEST3_DATA.ip)
self.assertFalse(found, 'test_3 record should have been deleted')
def record_edit_test(self):
"""
Test edited record returns the same record ID with different data
"""
# GIVEN: Record entries in database
add_records(self, [TEST1_DATA, TEST2_DATA])
# WHEN: We retrieve a specific record
record = self.projector.get_projector_by_ip(TEST1_DATA.ip)
record_id = record.id
# WHEN: Data is changed
record.ip = TEST3_DATA.ip
record.port = TEST3_DATA.port
record.pin = TEST3_DATA.pin
record.name = TEST3_DATA.name
record.location = TEST3_DATA.location
record.notes = TEST3_DATA.notes
updated = self.projector.update_projector(record)
self.assertTrue(updated, 'Save updated record should have returned True')
record = self.projector.get_projector_by_ip(TEST3_DATA.ip)
# THEN: Record ID should remain the same, but data should be changed
self.assertEqual(record_id, record.id, 'Edited record should have the same ID')
self.assertTrue(compare_data(TEST3_DATA, record), 'Edited record should have new data')

View File

@ -0,0 +1,31 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2014 Raoul Snyman #
# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan #
# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, #
# Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
Package to test the openlp.core.ui package.
"""

View File

@ -29,10 +29,10 @@
"""
Package to test the openlp.core.ui.settingsform package.
"""
from PyQt4 import QtGui
from unittest import TestCase
from openlp.core.common import Registry
from openlp.core.ui.generaltab import GeneralTab
from openlp.core.ui.settingsform import SettingsForm
from tests.functional import MagicMock, patch
@ -62,7 +62,7 @@ class TestSettingsForm(TestCase):
patch.object(settings_form.setting_list_widget, 'addItem') as mocked_add_item:
settings_form.insert_tab(general_tab, is_visible=True)
# THEN: Stuff should happen
# THEN: The general tab should have been inserted into the stacked layout and an item inserted into the list
mocked_add_widget.assert_called_with(general_tab)
self.assertEqual(1, mocked_add_item.call_count, 'addItem should have been called')
@ -80,6 +80,53 @@ class TestSettingsForm(TestCase):
patch.object(settings_form.setting_list_widget, 'addItem') as mocked_add_item:
settings_form.insert_tab(general_tab, is_visible=False)
# THEN: Stuff should happen
# THEN: The general tab should have been inserted, but no list item should have been inserted into the list
mocked_add_widget.assert_called_with(general_tab)
self.assertEqual(0, mocked_add_item.call_count, 'addItem should not have been called')
def accept_with_inactive_plugins_test(self):
"""
Test that the accept() method works correctly when some of the plugins are inactive
"""
# GIVEN: A visible general tab and an invisible theme tab in a Settings Form
settings_form = SettingsForm(None)
general_tab = QtGui.QWidget(None)
general_tab.tab_title = 'mock-general'
general_tab.tab_title_visible = 'Mock General'
general_tab.icon_path = ':/icon/openlp-logo-16x16.png'
mocked_general_save = MagicMock()
general_tab.save = mocked_general_save
settings_form.insert_tab(general_tab, is_visible=True)
themes_tab = QtGui.QWidget(None)
themes_tab.tab_title = 'mock-themes'
themes_tab.tab_title_visible = 'Mock Themes'
themes_tab.icon_path = ':/icon/openlp-logo-16x16.png'
mocked_theme_save = MagicMock()
themes_tab.save = mocked_theme_save
settings_form.insert_tab(themes_tab, is_visible=False)
# WHEN: The accept() method is called
settings_form.accept()
# THEN: The general tab's save() method should have been called, but not the themes tab
mocked_general_save.assert_called_with()
self.assertEqual(0, mocked_theme_save.call_count, 'The Themes tab\'s save() should not have been called')
def list_item_changed_invalid_item_test(self):
"""
Test that the list_item_changed() slot handles a non-existent item
"""
# GIVEN: A mocked tab inserted into a Settings Form
settings_form = SettingsForm(None)
general_tab = QtGui.QWidget(None)
general_tab.tab_title = 'mock'
general_tab.tab_title_visible = 'Mock'
general_tab.icon_path = ':/icon/openlp-logo-16x16.png'
settings_form.insert_tab(general_tab, is_visible=True)
with patch.object(settings_form.stacked_layout, 'count') as mocked_count:
# WHEN: The list_item_changed() slot is called with an invalid item index
settings_form.list_item_changed(100)
# THEN: The rest of the method should not have been called
self.assertEqual(0, mocked_count.call_count, 'The count method of the stacked layout should not be called')

View File

@ -0,0 +1,31 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2014 Raoul Snyman #
# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan #
# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, #
# Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
Package to test the openlp.core.ui.media package.
"""

View File

@ -0,0 +1,67 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2014 Raoul Snyman #
# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan #
# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, #
# Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
Package to test the openlp.core.ui.media package.
"""
from unittest import TestCase
from openlp.core.ui.media.mediacontroller import MediaController
from openlp.core.ui.media.mediaplayer import MediaPlayer
from openlp.core.common import Registry
from tests.functional import MagicMock
from tests.helpers.testmixin import TestMixin
class TestMediaController(TestCase, TestMixin):
def setUp(self):
Registry.create()
Registry().register('service_manager', MagicMock())
def generate_extensions_lists_test(self):
"""
Test that the extensions are create correctly
"""
# GIVEN: A MediaController and an active player with audio and video extensions
media_controller = MediaController()
media_player = MediaPlayer(None)
media_player.is_active = True
media_player.audio_extensions_list = ['*.mp3', '*.wav', '*.wma', '*.ogg']
media_player.video_extensions_list = ['*.mp4', '*.mov', '*.avi', '*.ogm']
media_controller.register_players(media_player)
# WHEN: calling _generate_extensions_lists
media_controller._generate_extensions_lists()
# THEN: extensions list should have been copied from the player to the mediacontroller
self.assertListEqual(media_player.video_extensions_list, media_controller.video_extensions_list,
'Video extensions should be the same')
self.assertListEqual(media_player.audio_extensions_list, media_controller.audio_extensions_list,
'Audio extensions should be the same')

View File

@ -107,6 +107,33 @@ class TestRouter(TestCase, TestMixin):
self.assertEqual(mocked_function, function['function'], 'The mocked function should match defined value.')
self.assertFalse(function['secure'], 'The mocked function should not require any security.')
def process_secure_http_request_test(self):
"""
Test the router control functionality
"""
# GIVEN: A testing set of Routes
mocked_function = MagicMock()
test_route = [
(r'^/stage/api/poll$', {'function': mocked_function, 'secure': True}),
]
self.router.routes = test_route
self.router.settings_section = 'remotes'
Settings().setValue('remotes/authentication enabled', True)
self.router.path = '/stage/api/poll'
self.router.auth = ''
self.router.headers = {'Authorization': None}
self.router.send_response = MagicMock()
self.router.send_header = MagicMock()
self.router.end_headers = MagicMock()
self.router.wfile = MagicMock()
# WHEN: called with a poll route
self.router.do_post_processor()
# THEN: the function should have been called only once
self.router.send_response.assert_called_once_with(401)
self.assertEqual(self.router.send_header.call_count, 2, 'The header should have been called twice.')
def get_content_type_test(self):
"""
Test the get_content_type logic

View File

@ -26,3 +26,35 @@
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
Module-level functions for the functional test suite
"""
import os
from tests.interfaces import patch
from openlp.core.common import is_win
from .test_projectormanager import tmpfile
def setUp():
if not is_win():
# Wine creates a sharing violation during tests. Ignore.
try:
os.remove(tmpfile)
except:
pass
def tearDown():
"""
Ensure test suite has been cleaned up after tests
"""
patch.stopall()
if not is_win():
try:
# In case of changed schema, remove old test file
os.remove(tmpfile)
except FileNotFoundError:
pass

View File

@ -0,0 +1,106 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2014 Raoul Snyman #
# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan #
# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, #
# Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
# --------------------------------------------------------------------------- #
# 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 #
###############################################################################
"""
Interface tests to test the themeManager class and related methods.
"""
import os
from unittest import TestCase
from openlp.core.common import Registry, Settings
from tests.functional import patch, MagicMock
from tests.helpers.testmixin import TestMixin
from openlp.core.ui import ProjectorManager, ProjectorEditForm
from openlp.core.lib.projector.db import Projector, ProjectorDB
from tests.resources.projector.data import TEST1_DATA, TEST2_DATA, TEST3_DATA
tmpfile = '/tmp/openlp-test-projectormanager.sql'
class TestProjectorManager(TestCase, TestMixin):
"""
Test the functions in the ProjectorManager module
"""
def setUp(self):
"""
Create the UI and setup necessary options
"""
self.build_settings()
self.setup_application()
Registry.create()
if not hasattr(self, 'projector_manager'):
with patch('openlp.core.lib.projector.db.init_url') as mocked_init_url:
mocked_init_url.start()
mocked_init_url.return_value = 'sqlite:///%s' % tmpfile
self.projectordb = ProjectorDB()
if not hasattr(self, 'projector_manager'):
self.projector_manager = ProjectorManager(projectordb=self.projectordb)
def tearDown(self):
"""
Remove test database.
Delete all the C++ objects at the end so that we don't have a segfault.
"""
self.projectordb.session.close()
del self.projector_manager
self.destroy_settings()
def bootstrap_initialise_test(self):
"""
Test initialize calls correct startup functions
"""
# WHEN: we call bootstrap_initialise
self.projector_manager.bootstrap_initialise()
# THEN: ProjectorDB is setup
self.assertEqual(type(self.projector_manager.projectordb), ProjectorDB,
'Initialization should have created a ProjectorDB() instance')
def bootstrap_post_set_up_test(self):
"""
Test post-initialize calls proper setups
"""
# GIVEN: setup mocks
self.projector_manager._load_projectors = MagicMock()
# WHEN: Call to initialize is run
self.projector_manager.bootstrap_initialise()
self.projector_manager.bootstrap_post_set_up()
# THEN: verify calls to retrieve saved projectors
self.assertEqual(1, self.projector_manager._load_projectors.call_count,
'Initialization should have called load_projectors()')
# THEN: Verify edit page is initialized
self.assertEqual(type(self.projector_manager.projector_form), ProjectorEditForm,
'Initialization should have created a Projector Edit Form')
self.assertIs(self.projector_manager.projectordb,
self.projector_manager.projector_form.projectordb,
'ProjectorEditForm should be using same ProjectorDB() instance as ProjectorManager')

View File

@ -0,0 +1,55 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2014 Raoul Snyman #
# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan #
# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
# Christian Richter, Philip Ridout, Ken Roberts, Simon Scudder, #
# Jeffrey Smith, Maikel Stuivenberg, Martin Thompson, Jon Tibble, #
# Dave Warnock, Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
The :mod:`tests.resources.projector.data file contains test data
"""
from openlp.core.lib.projector.db import Projector
# Test data
TEST1_DATA = Projector(ip='111.111.111.111',
port='1111',
pin='1111',
name='___TEST_ONE___',
location='location one',
notes='notes one')
TEST2_DATA = Projector(ip='222.222.222.222',
port='2222',
pin='2222',
name='___TEST_TWO___',
location='location two',
notes='notes two')
TEST3_DATA = Projector(ip='333.333.333.333',
port='3333',
pin='3333',
name='___TEST_THREE___',
location='location three',
notes='notes three')