Initial projector code

This commit is contained in:
Ken Roberts 2014-10-06 12:10:03 -07:00
parent ba464e5faa
commit 0227e0a2bd
43 changed files with 3511 additions and 30 deletions

View File

@ -33,3 +33,4 @@ tests.kdev4
__pycache__
*.dll
.directory
*.kate-swp

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 MD%Sum on salt, data
using PyQt4.QCryptograhicHash.
: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

@ -275,6 +275,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')],
@ -295,7 +296,13 @@ 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/query time': 20 # PJLink socket timeout is 30 seconds
}
__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', 'Singluar')
self.Manufacturers = translate('OpenLP.Ui', 'Manufacturers', 'Plural')
self.Model = translate('OpenLP.Ui', 'Model', 'Singluar')
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', 'Singluar')
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

@ -334,3 +334,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

@ -0,0 +1,317 @@
# -*- 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:`projector` module
"""
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': ['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', # Projuct 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_NETWORK: translate('OpenLP.ProjectorConstants', 'Network 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', 'Invald 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 availble'),
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')}

View File

@ -0,0 +1,291 @@
# -*- 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:`projector.db` module provides the database functions for the
Projector module.
"""
import logging
log = logging.getLogger(__name__)
log.debug('projector.lib.db module loaded')
from os import path
from sqlalchemy import Column, ForeignKey, Integer, MetaData, Sequence, String, and_
from sqlalchemy.ext.declarative import declarative_base, declared_attr
from sqlalchemy.orm import backref, joinedload, relationship
from sqlalchemy.orm.exc import NoResultFound, MultipleResultsFound
from sqlalchemy.sql import select
from openlp.core.common import translate
from openlp.core.lib.db import BaseModel, Manager, init_db, init_url
from openlp.core.lib.projector.constants import PJLINK_DEFAULT_SOURCES
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):
"""
Manufacturer table.
Model table is related.
"""
def __repr__(self):
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):
"""
Model table.
Manufacturer table links here.
Source table is related.
"""
def __repr__(self):
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):
"""
Input ource table.
Model table links here.
These entries map PJLink source codes to text strings.
"""
def __repr__(self):
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.
No relation. This keeps track of installed projectors.
"""
ip = Column(String(100))
port = Column(String(8))
pin = Column(String(6))
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))
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.
Change to Declarative means we really don't do much here.
"""
url = init_url('projector')
session, metadata = init_db(url, base=Base)
Base.metadata.create_all(checkfirst=True)
return session
def get_projector_all(self):
"""
Retrieve all projector entries so they can be added to the Projector
Manager list pane.
"""
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: Projetor() 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: Projetor() 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
NOTE: Will not add new entry if IP is the same as already in the table.
:param projector: Projetor() instance to add
:returns: bool
"""
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
"""
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
"""
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, make, model, sources):
"""
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 make: Manufacturer name as retrieved from projector
:param model: Manufacturer model as retrieved from projector
:returns: dict
"""
source_dict = {}
model_list = self.get_all_objects(Model, Model.name == model)
if model_list is None or len(model_list) < 1:
# No entry for model, so see if there's a default entry
default_list = self.get_object_filtered(Manufacturer, Manufacturer.name == make)
if default_list is None or len(default_list) < 1:
# No entry for manufacturer, so can't check for default text
log.debug('Using default PJLink text for input select')
for source in sources:
log.debug('source = "%s"' % source)
source_dict[source] = '%s %s' % (PJLINK_DEFAULT_SOURCES[source[0]], source[1])
else:
# We have a manufacturer entry, see if there's a default
# TODO: Finish this section once edit source input is done
pass
else:
# There's at least one model entry, see if there's more than one manufacturer
# TODO: Finish this section once edit source input text is done
pass
return source_dict

View File

@ -0,0 +1,668 @@
# -*- 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:`projector.pjlink1` module provides the necessary functions
for connecting to a PJLink-capable projector.
See PJLink Specifications for Class 1 for details.
NOTE:
Function names follow the following syntax:
def process_CCCC(...):
WHERE:
CCCC = PJLink command being processed.
See PJLINK_FUNC(...) for command returned from projector.
"""
import logging
log = logging.getLogger(__name__)
log.debug('projectorpjlink1 loaded')
__all__ = ['PJLink1']
from time import sleep
from codecs import decode, encode
from PyQt4 import QtCore, QtGui
from PyQt4.QtCore import QObject, 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.
"""
changeStatus = pyqtSignal(str, int, str)
projectorNetwork = pyqtSignal(int) # Projector network activity
projectorStatus = pyqtSignal(int)
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
"""
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
# Allowances for Projector Wizard option
if 'dbid' in kwargs:
self.dbid = kwargs['dbid']
else:
self.dbid = None
if 'location' in kwargs:
self.location = kwargs['location']
else:
self.location = None
if 'notes' in kwargs:
self.notes = kwargs['notes']
else:
self.notes = None
if 'wizard' in kwargs:
self.new_wizard = True
else:
self.new_wizard = False
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
# Account for self.readLine appending \0 and/or exraneous \r
self.maxSize = PJLINK_MAX_PACKET + 2
self.setReadBufferSize(self.maxSize)
# PJLink projector information
self.pjlink_class = '1' # Default class
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.projector_errors = None
# Set from ProjectorManager.add_projector()
self.widget = None # QListBox entry
self.timer = None # Timer that calls the poll_loop
# Map command returned 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,
'POWR': self.process_powr
}
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)
self.connected.disconnect(self.check_login)
self.disconnected.disconnect(self.disconnect_from_host)
self.error.disconnect(self.get_error)
self.disconnect_from_host()
self.deleteLater()
self.i_am_running = False
def poll_loop(self):
"""
Called by QTimer in ProjectorManager.ProjectorItem.
Retrieves status information.
"""
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
self.timer.start()
for i in ['POWR', 'ERST', 'LAMP', 'AVMT', 'INPT']:
self.send_command(i)
self.waitForReadyRead()
def _get_status(self, status):
"""
Helper to retrieve status/error codes and convert to strings.
"""
# Return the status code as a 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, '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.
"""
message = '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)
def check_command(self, cmd):
"""
Verifies command is valid based on PJLink class.
"""
return self.pjlink_class in PJLINK_VALID_CMD and \
cmd in PJLINK_VALID_CMD[self.pjlink_class]
def check_login(self):
"""
Processes the initial connection and authentication (if needed).
"""
self.waitForReadyRead(5000) # 5 seconds should be more than enough
read = self.readLine(self.maxSize)
dontcare = self.readLine(self.maxSize) # Clean out the trailing \r\n
if 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))
# At this point, we should only have the initial login prompt with
# possible authentication
if not data.upper().startswith('PJLINK'):
# Invalid response
return self.disconnect_from_host()
data_check = data.strip().split(' ')
log.debug('(%s) data_check="%s"' % (self.ip, data_check))
salt = None
# PJLink initial login will be:
# 'PJLink 0' - Unauthenticated login - no extra steps required.
# 'PJLink 1 XXXXXX' Authenticated login - extra processing required.
if data_check[1] == '1':
# Authenticated login with salt
salt = qmd5_hash(salt=data_check[2], data=self.pin)
# We're connected at this point, so go ahead and do regular I/O
self.readyRead.connect(self.get_data)
# Initial data we should know about
self.send_command(cmd='CLSS', salt=salt)
self.waitForReadyRead()
# These should never change once we get this information
if self.manufacturer is None:
for i in ['INF1', 'INF2', 'INFO', 'NAME', 'INST']:
self.send_command(cmd=i)
self.waitForReadyRead()
self.change_status(S_CONNECTED)
if not self.new_wizard:
self.timer.start()
self.poll_loop()
def get_data(self):
"""
Socket interface to retrieve data.
"""
log.debug('(%s) Reading data' % self.ip)
if self.state() != self.ConnectedState:
log.debug('(%s) get_data(): Not connected - returning' % self.ip)
return
read = self.readLine(self.maxSize)
if read == -1:
# No data available
log.debug('(%s) get_data(): No data available (-1)' % self.ip)
return
self.projectorNetwork.emit(S_NETWORK_RECEIVED)
data_in = decode(read, 'ascii')
data = data_in.strip()
if len(data) < 8:
# Not enough data for a packet
log.debug('(%s) get_data(): Packet length < 8: "%s"' % (self.ip, data))
return
log.debug('(%s) Checking new data "%s"' % (self.ip, data))
if '=' in data:
pass
else:
log.warn('(%s) Invalid packet received')
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) Invalid packet - expected header + command + data' % self.ip)
log.warn('(%s) Received data: "%s"' % (self.ip, read))
self.change_status(E_INVALID_DATA)
return
if not self.check_command(cmd):
log.warn('(%s) Invalid packet - unknown command "%s"' % self.ip, cmd)
return
return self.process_command(cmd, data)
@pyqtSlot(int)
def get_error(self, err):
"""
Process error from SocketError signal
"""
log.debug('(%s) get_error(err=%s): %s' % (self.ip, err, self.errorString()))
if err <= 18:
# QSocket errors. Redefined in projectorconstants 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())
return
def send_command(self, cmd, opts='?', salt=None):
"""
Socket interface to send commands to projector.
"""
if self.state() != self.ConnectedState:
log.warn('(%s) send_command(): Not connected - returning' % self.ip)
return
self.projectorNetwork.emit(S_NETWORK_SENDING)
log.debug('(%s) Sending 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' % (salt, cmd, opts, CR)
sent = self.write(out)
self.waitForBytesWritten(5000) # 5 seconds should be enough
if sent == -1:
# Network error?
self.projectorNetwork.emit(S_NETWORK_RECEIVED)
self.change_status(E_NETWORK, 'Error while sending data to projector')
def process_command(self, cmd, data):
"""
Verifies any return error code. Calls the appropriate command handler.
"""
log.debug('(%s) Processing command "%s"' % (self.ip, cmd))
if data in PJLINK_ERRORS:
# Oops - projector error
if data.upper() == 'ERRA':
# Authentication error
self.change_status(E_AUTHENTICATION)
return
elif data.upper() == 'ERR1':
# Undefined command
self.change_status(E_UNDEFINED, 'Undefined command: "%s"' % cmd)
return
elif data.upper() == 'ERR2':
# Invalid parameter
self.change_status(E_PARAMETER)
return
elif data.upper() == 'ERR3':
# Projector busy
self.change_status(E_UNAVAILABLE)
return
elif data.upper() == 'ERR4':
# Projector/display error
self.change_status(E_PROJECTOR)
return
# Command succeeded - no extra information
if data.upper() == 'OK':
log.debug('(%s) Command returned OK' % self.ip)
return
if cmd in self.PJLINK1_FUNC:
return self.PJLINK1_FUNC[cmd](data)
else:
log.warn('(%s) Invalid command %s' % (self.ip, cmd))
def process_lamp(self, data):
"""
Lamp(s) status. See PJLink Specifications for format.
"""
lamps = []
data_dict = data.split()
while data_dict:
fill = {'Hours': int(data_dict[0]), 'On': False if data_dict[1] == '0' else True}
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.
"""
if data in PJLINK_POWR_STATUS:
self.power = PJLINK_POWR_STATUS[data]
self.change_status(PJLINK_POWR_STATUS[data])
else:
# Log unknown status response
log.warn('Unknown power response: %s' % data)
return
def process_avmt(self, data):
"""
Shutter open/closed. See PJLink specification for format.
"""
if data == '11':
self.shutter = True
self.mute = False
elif data == '21':
self.shutter = False
self.mute = True
elif data == '30':
self.shutter = False
self.mute = False
elif data == '31':
self.shutter = True
self.mute = True
else:
log.warn('Unknown shutter response: %s' % data)
return
def process_inpt(self, data):
"""
Current source input selected. See PJLink specification for format.
"""
self.source = data
return
def process_clss(self, data):
"""
PJLink class that this projector supports. See PJLink specification for format.
"""
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 by customer.
"""
self.pjlink_name = data
return
def process_inf1(self, data):
"""
Manufacturer name set by manufacturer.
"""
self.manufacturer = data
return
def process_inf2(self, data):
"""
Projector Model set by manufacturer.
"""
self.model = data
return
def process_info(self, data):
"""
Any extra info set by manufacturer.
"""
self.other_info = data
return
def process_inst(self, data):
"""
Available source inputs. See PJLink specification for format.
"""
sources = []
check = data.split()
for source in check:
sources.append(source)
self.source_available = sources
return
def process_erst(self, data):
"""
Error status. See PJLink Specifications for format.
"""
if int(data) == 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.
"""
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):
"""
Close socket and cleanup.
"""
if self.state() != self.ConnectedState:
log.warn('(%s) disconnect_from_host(): Not connected - returning' % self.ip)
return
self.disconnectFromHost()
try:
self.readyRead.disconnect(self.get_data)
except TypeError:
pass
self.change_status(S_NOT_CONNECTED)
self.timer.stop()
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 retrive 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.
"""
if self.source_available is None:
return
elif src not in self.source_available:
return
self.send_command(cmd='INPT', opts=src)
self.waitForReadyRead()
self.poll_loop()
def set_power_on(self):
"""
Send command to turn power to on.
"""
self.send_command(cmd='POWR', opts='1')
self.waitForReadyRead()
self.poll_loop()
def set_power_off(self):
"""
Send command to turn power to standby.
"""
self.send_command(cmd='POWR', opts='0')
self.waitForReadyRead()
self.poll_loop()
def set_shutter_closed(self):
"""
Send command to set shutter to closed position.
"""
self.send_command(cmd='AVMT', opts='11')
self.waitForReadyRead()
self.poll_loop()
def set_shutter_open(self):
"""
Send command to set shutter to open position.
"""
self.send_command(cmd='AVMT', opts='10')
self.waitForReadyRead()
self.poll_loop()

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.wizard import ProjectorWizard
from .projector.tab import ProjectorTab
__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', 'ProjectorWizard']

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', 'Toogle Projector Manager'))
self.view_projector_manager_item.setStatusTip(translate('OpenLP.MainWindow',
'Toggle the visibiilty 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'
@ -515,6 +545,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)
@ -825,6 +856,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])
@ -1114,6 +1146,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

@ -0,0 +1,836 @@
# -*- 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: projectormanager` 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 openlp.core.common import Registry, RegistryProperties, Settings, OpenLPMixin, \
RegistryMixin, translate
from openlp.core.lib import OpenLPToolbar, ImageSource, get_text_file_string, build_icon,\
check_item_selected, create_thumb
from openlp.core.lib.ui import critical_error_message_box, create_widget_action
from openlp.core.utils import get_locale_key, get_filesystem_encoding
from openlp.core.lib.projector.db import ProjectorDB
from openlp.core.lib.projector.pjlink1 import PJLink1
from openlp.core.ui.projector.wizard import ProjectorWizard
from openlp.core.lib.projector.constants import *
# Dict for matching projector status to display icon
STATUS_ICONS = {S_NOT_CONNECTED: ':/projector/projector_disconnect.png',
S_CONNECTING: ':/projector/projector_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.png',
E_AUTHENTICATION: ':/projector/projector_not_connected.png',
E_UNKNOWN_SOCKET_ERROR: ':/icons/openlp-logo-64x64.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 toolbar
self.toolbar = OpenLPToolbar(widget)
self.toolbar.add_toolbar_action('newProjector',
text=translate('OpenLP.Projector', 'Add Projector'),
icon=':/projector/projector_new.png',
tooltip=translate('OpenLP.ProjectorManager', 'Add a new projector'),
triggers=self.on_add_projector)
self.toolbar.addSeparator()
self.toolbar.add_toolbar_action('connect_all_projectors',
text=translate('OpenLP.ProjectorManager', 'Connect to all projectors'),
icon=':/projector/projector_connect.png',
tootip=translate('OpenLP.ProjectorManager', 'Connect to all projectors'),
triggers=self.on_connect_all_projectors)
self.toolbar.add_toolbar_action('disconnect_all_projectors',
text=translate('OpenLP.ProjectorManager', 'Disconnect from all projectors'),
icon=':/projector/projector_disconnect.png',
tooltip=translate('OpenLP.ProjectorManager', 'Disconnect from all projectors'),
triggers=self.on_disconnect_all_projectors)
self.toolbar.addSeparator()
self.toolbar.add_toolbar_action('poweron_all_projectors',
text=translate('OpenLP.ProjectorManager', 'Power On All Projectors'),
icon=':/projector/projector_power_on.png',
tooltip=translate('OpenLP.ProjectorManager', 'Power on all projectors'),
triggers=self.on_poweron_all_projectors)
self.toolbar.add_toolbar_action('poweroff_all_projectors',
text=translate('OpenLP.ProjectorManager', 'Standby All Projector'),
icon=':/projector/projector_power_off.png',
tooltip=translate('OpenLP.ProjectorManager', 'Put all projectors in standby'),
triggers=self.on_poweroff_all_projectors)
self.toolbar.addSeparator()
self.toolbar.add_toolbar_action('blank_projector',
text=translate('OpenLP.ProjectorManager', 'Blank All Projector Screens'),
icon=':/projector/projector_blank.png',
tooltip=translate('OpenLP.ProjectorManager', 'Blank all projector screens'),
triggers=self.on_blank_all_projectors)
self.toolbar.add_toolbar_action('show_all_projector',
text=translate('OpenLP.ProjectorManager', 'Show All Projector Screens'),
icon=':/projector/projector_show.png',
tooltip=translate('OpenLP.ProjectorManager', 'Show all projector screens'),
triggers=self.on_show_all_projectors)
self.layout.addWidget(self.toolbar)
# Add the projector list box
self.projector_widget = QtGui.QWidgetAction(self.toolbar)
self.projector_widget.setObjectName('projector_widget')
# Create projector manager list
self.projector_list_widget = QtGui.QListWidget(widget)
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)
# Build the context menu
self.menu = QtGui.QMenu()
self.view_action = create_widget_action(self.menu,
text=translate('OpenLP.ProjectorManager',
'&View Projector Information'),
icon=':/projector/projector_view.png',
triggers=self.on_view_projector)
self.status_action = create_widget_action(self.menu,
text=translate('OpenLP.ProjectorManager',
'View &Projector Status'),
icon=':/projector/projector_status.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_connectors.png',
triggers=self.on_select_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)
class ProjectorManager(OpenLPMixin, RegistryMixin, QtGui.QWidget, Ui_ProjectorManager, RegistryProperties):
"""
Manage the projectors.
"""
def __init__(self, parent=None, projectordb=None):
log.debug('__init__()')
super().__init__(parent)
self.settings_section = 'projector'
self.projectordb = projectordb
self.projector_list = []
def bootstrap_initialise(self):
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')
settings = Settings()
settings.beginGroup(self.settings_section)
self.autostart = settings.value('connect on start')
settings.endGroup()
del(settings)
def bootstrap_post_set_up(self):
self.load_projectors()
self.projector_form = ProjectorWizard(self, projectordb=self.projectordb)
self.projector_form.edit_page.newProjector.connect(self.add_projector_from_wizard)
self.projector_form.edit_page.editProjector.connect(self.edit_projector_from_wizard)
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
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.view_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.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.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 _select_input_widget(self, parent, selected, code, text):
"""
Build the radio button widget for selecting source input menu
:param parent: parent widget
:param selected: Selected widget text
:param code: PJLink code for this widget
:param text: Text to display
:returns: radio button widget
"""
widget = QtGui.QRadioButton(text, parent=parent)
widget.setChecked(True if code == selected else False)
widget.button_role = code
widget.clicked.connect(self._select_input_radio)
self.radio_buttons.append(widget)
return widget
def _select_input_radio(self, opt1=None, opt2=None):
"""
Returns the currently selected radio button
:param opt1: Needed by PyQt4
:param op2: future
:returns: Selected button role
"""
for i in self.radio_buttons:
if i.isChecked():
self.radio_button_selected = i.button_role
break
return
def on_select_input(self, opt=None):
"""
Builds menu for 'Select Input' option, then calls the selected projector
item to change input source.
:param opt: Needed by PyQt4
:returns: None
"""
list_item = self.projector_list_widget.item(self.projector_list_widget.currentRow())
projector = list_item.data(QtCore.Qt.UserRole)
layout = QtGui.QVBoxLayout()
box = QtGui.QDialog(parent=self)
box.setModal(True)
title = QtGui.QLabel(translate('OpenLP.ProjectorManager', 'Select the input source below'))
layout.addWidget(title)
self.radio_button_selected = None
self.radio_buttons = []
source_list = self.projectordb.get_source_list(make=projector.link.manufacturer,
model=projector.link.model,
sources=projector.link.source_available
)
if source_list is None:
return
sort = []
for i in source_list.keys():
sort.append(i)
sort.sort()
for i in sort:
button = self._select_input_widget(parent=self,
selected=projector.link.source,
code=i,
text=source_list[i])
layout.addWidget(button)
button_box = QtGui.QDialogButtonBox(QtGui.QDialogButtonBox.Ok |
QtGui.QDialogButtonBox.Cancel)
button_box.accepted.connect(box.accept)
button_box.rejected.connect(box.reject)
layout.addWidget(button_box)
box.setLayout(layout)
check = box.exec_()
if check == 0:
# Cancel button clicked or window closed - don't set source
return
selected = self.radio_button_selected
projector.link.set_input_source(self.radio_button_selected)
self.radio_button_selected = None
def on_add_projector(self, opt=None):
"""
Calls wizard to add a new projector to the database
:param opt: Needed by PyQt4
:returns: None
"""
self.projector_form.exec_()
def on_blank_all_projectors(self, opt=None):
"""
Cycles through projector list to send blank screen command
:param opt: Needed by PyQt4
:returns: None
"""
for item in self.projector_list:
self.on_blank_projector(item)
def on_blank_projector(self, opt=None):
"""
Calls projector thread to send blank screen command
:param opt: Needed by PyQt4
:returns: None
"""
try:
ip = opt.link.ip
projector = opt
except AttributeError:
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)
return projector.link.set_shutter_closed()
def on_connect_projector(self, opt=None):
"""
Calls projector thread to connect to projector
:param opt: Needed by PyQt4
:returns: None
"""
try:
ip = opt.link.ip
projector = opt
except AttributeError:
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)
return projector.link.connect_to_host()
def on_connect_all_projectors(self, opt=None):
"""
Cycles through projector list to tell threads to connect to projectors
:param opt: Needed by PyQt4
:returns: None
"""
for item in self.projector_list:
self.on_connect_projector(item)
def on_delete_projector(self, opt=None):
"""
Deletes a projector from the list and the database
:param opt: Needed by PyQt4
:returns: None
"""
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 TypeError:
pass
try:
projector.link.changeStatus.disconnect(self.update_status)
except TypeError:
pass
try:
projector.timer.stop()
projector.timer.timeout.disconnect(projector.link.poll_loop)
except 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
:returns: None
"""
try:
ip = opt.link.ip
projector = opt
except AttributeError:
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)
return projector.link.disconnect_from_host()
def on_disconnect_all_projectors(self, opt=None):
"""
Cycles through projector list to have projector threads disconnect
:param opt: Needed by PyQt4
:returns: None
"""
for item in self.projector_list:
self.on_disconnect_projector(item)
def on_edit_projector(self, opt=None):
"""
Calls wizard with selected projector to edit information
:param opt: Needed by PyQt4
:returns: None
"""
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)
def on_poweroff_all_projectors(self, opt=None):
"""
Cycles through projector list to send Power Off command
:param opt: Needed by PyQt4
:returns: None
"""
for item in self.projector_list:
self.on_poweroff_projector(item)
def on_poweroff_projector(self, opt=None):
"""
Calls projector link to send Power Off command
:param opt: Needed by PyQt4
:returns: None
"""
try:
ip = opt.link.ip
projector = opt
except AttributeError:
# Must have been called by a mouse-click on item
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)
return projector.link.set_power_off()
def on_poweron_all_projectors(self, opt=None):
"""
Cycles through projector list to send Power On command
:param opt: Needed by PyQt4
:returns: None
"""
for item in self.projector_list:
self.on_poweron_projector(item)
def on_poweron_projector(self, opt=None):
"""
Calls projector link to send Power On command
:param opt: Needed by PyQt4
:returns: None
"""
try:
ip = opt.link.ip
projector = opt
except AttributeError:
lwi = self.projector_list_widget.item(self.projector_list_widget.currentRow())
if lwi is None:
return
projector = lwi.data(QtCore.Qt.UserRole)
return projector.link.set_power_on()
def on_show_all_projectors(self, opt=None):
"""
Cycles through projector list to send open shutter command
:param opt: Needed by PyQt4
:returns: None
"""
for i in self.projector_list:
self.on_show_projector(i.link)
def on_show_projector(self, opt=None):
"""
Calls projector thread to send open shutter command
:param opt: Needed by PyQt4
:returns: None
"""
try:
ip = opt.link.ip
projector = opt
except AttributeError:
lwi = self.projector_list_widget.item(self.projector_list_widget.currentRow())
if lwi is None:
return
projector = lwi.data(QtCore.Qt.UserRole)
return projector.link.set_shutter_open()
def on_status_projector(self, opt=None):
"""
Builds message box with projector status information
:param opt: Needed by PyQt4
:returns: None
"""
lwi = self.projector_list_widget.item(self.projector_list_widget.currentRow())
projector = lwi.data(QtCore.Qt.UserRole)
s = '<b>%s</b>: %s<BR />' % (translate('OpenLP.ProjectorManager', 'Name'), projector.link.name)
s = '%s<b>%s</b>: %s<br />' % (s, translate('OpenLP.ProjectorManager', 'IP'), projector.link.ip)
s = '%s<b>%s</b>: %s<br />' % (s, translate('OpenLP.ProjectorManager', 'Port'), projector.link.port)
s = '%s<hr /><br >' % s
if projector.link.manufacturer is None:
s = '%s%s' % (s, translate('OpenLP.ProjectorManager',
'Projector information not available at this time.'))
else:
s = '%s<b>%s</b>: %s<br />' % (s, translate('OpenLP.ProjectorManager', 'Manufacturer'),
projector.link.manufacturer)
s = '%s<b>%s</b>: %s<br /><br />' % (s, translate('OpenLP.ProjectorManager', 'Model'),
projector.link.model)
s = '%s<b>%s</b>: %s<br />' % (s, translate('OpenLP.ProjectorManager', 'Power status'),
ERROR_MSG[projector.link.power])
s = '%s<b>%s</b>: %s<br />' % (s, translate('OpenLP.ProjectorManager', 'Shutter is'),
'Closed' if projector.link.shutter else 'Open')
s = '%s<b>%s</b>: %s<br />' % (s, translate('OpenLP.ProjectorManager', 'Current source input is'),
projector.link.source)
s = '%s<hr /><br />' % s
if projector.link.projector_errors is None:
s = '%s%s' % (s, translate('OpenLP.ProjectorManager', 'No current errors or warnings'))
else:
s = '%s<b>%s</b>' % (s, translate('OpenLP.ProjectorManager', 'Current errors/warnings'))
for (key, val) in projector.link.projector_errors.items():
s = '%s<b>%s</b>: %s<br />' % (s, key, ERROR_MSG[val])
s = '%s<hr /><br />' % s
s = '%s<b>%s</b><br />' % (s, translate('OpenLP.ProjectorManager', 'Lamp status'))
c = 1
for i in projector.link.lamp:
s = '%s <b>%s %s</b> (%s) %s: %s<br />' % (s,
translate('OpenLP.ProjectorManager', 'Lamp'),
c,
translate('OpenLP.ProjectorManager', 'On') if i['On'] else
translate('OpenLP.ProjectorManager', 'Off'),
translate('OpenLP.ProjectorManager', 'Hours'),
i['Hours'])
c = c + 1
QtGui.QMessageBox.information(self, translate('OpenLP.ProjectorManager', 'Projector Information'), s)
def on_view_projector(self, opt=None):
"""
Builds message box with projector information stored in database
:param opt: Needed by PyQt4
:returns: None
"""
lwi = self.projector_list_widget.item(self.projector_list_widget.currentRow())
projector = lwi.data(QtCore.Qt.UserRole)
dbid = translate('OpenLP.ProjectorManager', 'DB Entry')
ip = translate('OpenLP.ProjectorManager', 'IP')
port = translate('OpenLP.ProjectorManager', 'Port')
name = translate('OpenLP.ProjectorManager', 'Name')
location = translate('OpenLP.ProjectorManager', 'Location')
notes = translate('OpenLP.ProjectorManager', 'Notes')
QtGui.QMessageBox.information(self, translate('OpenLP.ProjectorManager',
'Projector %s Information' % projector.link.name),
'%s: %s<br /><br />%s: %s<br /><br />%s: %s<br /><br />'
'%s: %s<br /><br />%s: %s<br /><br />'
'%s:<br />%s' % (dbid, projector.link.dbid,
ip, projector.link.ip,
port, projector.link.port,
name, projector.link.name,
location, projector.link.location,
notes, projector.link.notes))
def _add_projector(self, projector):
"""
Helper app to build a projector instance
:param p: Dict of projector database information
:returns: PJLink() 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=projector.pin
)
def add_projector(self, opt1, opt2=None):
"""
Builds manager list item, projector thread, and timer for projector instance.
If called by add projector wizard:
opt1 = wizard instance
opt2 = item
Otherwise:
opt1 = item
opt2 = None
We are not concerned with the wizard instance,
just the projector item
:param opt1: See docstring
:param opt2: See docstring
:returns: None
"""
if opt1 is None:
return
if opt2 is None:
projector = opt1
else:
projector = opt2
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.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)
timer = QtCore.QTimer(self)
timer.setInterval(20000) # 20 second poll interval
timer.timeout.connect(item.link.poll_loop)
item.timer = timer
thread.start()
item.thread = thread
item.link.timer = timer
item.link.widget = item.widget
self.projector_list.append(item)
if self.autostart:
item.link.connect_to_host()
for i in self.projector_list:
log.debug('New projector list - item: (%s) %s' % (i.link.ip, i.link.name))
@pyqtSlot(str)
def add_projector_from_wizard(self, ip, opts=None):
"""
Add a projector from the wizard
:param ip: IP address of new record item
:param opts: Needed by PyQt4
:returns: None
"""
log.debug('load_projector(ip=%s)' % ip)
item = self.projectordb.get_projector_by_ip(ip)
self.add_projector(item)
@pyqtSlot(object)
def edit_projector_from_wizard(self, projector, opts=None):
"""
Update projector from the wizard edit page
:param projector: Projector() instance of projector with updated information
:param opts: Needed by PyQt4
:returns: None
"""
self.old_projector.link.name = projector.name
self.old_projector.link.ip = projector.ip
self.old_projector.link.pin = 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 i in self.projectordb.get_projector_all():
self.add_projector(i)
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
:returns: None
"""
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 = '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:
item.icon = QtGui.QIcon(QtGui.QPixmap(STATUS_ICONS[status]))
log.debug('(%s) Updating icon' % item.link.name)
item.widget.setIcon(item.icon)
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):
self.link = link
self.thread = None
self.icon = None
self.widget = None
self.my_parent = None
self.timer = None
self.projectordb_item = None
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
:returns: None
"""
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,96 @@
# -*- 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:`projector.ui.projectortab` module 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 Registry, Settings, UiStrings, translate
from openlp.core.lib import SettingsTab
from openlp.core.lib.ui import find_and_set_in_combo_box
class ProjectorTab(SettingsTab):
"""
Openlp Settings -> Projector settings
"""
def __init__(self, parent):
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.setTitle('Communication Options')
self.connect_box.setObjectName('connect_box')
self.connect_box_layout = QtGui.QVBoxLayout(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.addWidget(self.connect_on_startup)
self.left_layout.addWidget(self.connect_box)
self.left_layout.addStretch()
def retranslateUi(self):
"""
Translate the UI on the fly
"""
self.tab_title_visible = UiStrings().Projectors
self.connect_on_startup.setText(
translate('OpenLP.ProjectorTab', 'Connect to projectors on startup'))
def load(self):
"""
Load the projetor settings on startup
"""
settings = Settings()
settings.beginGroup(self.settings_section)
self.connect_on_startup.setChecked(settings.value('connect on start'))
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.endGroup

View File

@ -0,0 +1,551 @@
# -*- 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:`projector.projectorwizard` module handles the GUI Wizard for adding
new projetor entries.
"""
import logging
log = logging.getLogger(__name__)
log.debug('projectorwizard loaded')
from ipaddress import IPv4Address, IPv6Address, AddressValueError
from PyQt4 import QtCore, QtGui
from PyQt4.QtCore import pyqtSlot, pyqtSignal
from openlp.core.common import Registry, RegistryProperties, translate
from openlp.core.lib import build_icon
from openlp.core.common import verify_ip_address
from openlp.core.lib.projector.db import ProjectorDB, Projector
from openlp.core.lib.projector.pjlink1 import PJLink1
from openlp.core.lib.projector.constants import *
PAGE_COUNT = 4
(ConnectWelcome,
ConnectHost,
ConnectEdit,
ConnectFinish) = range(PAGE_COUNT)
PAGE_NEXT = {ConnectWelcome: ConnectHost,
ConnectHost: ConnectEdit,
ConnectEdit: ConnectFinish,
ConnectFinish: -1}
class ProjectorWizard(QtGui.QWizard, RegistryProperties):
"""
Wizard for adding/editing projector entries.
"""
def __init__(self, parent, projectordb):
log.debug('__init__()')
super().__init__(parent)
self.db = projectordb
self.projector = None
self.setObjectName('projector_wizard')
self.setWindowIcon(build_icon(u':/icon/openlp-logo.svg'))
self.setModal(True)
self.setWizardStyle(QtGui.QWizard.ModernStyle)
self.setMinimumSize(650, 550)
self.setOption(QtGui.QWizard.NoBackButtonOnStartPage)
self.spacer = QtGui.QSpacerItem(10, 0,
QtGui.QSizePolicy.Fixed,
QtGui.QSizePolicy.Minimum)
self.setOption(self.HaveHelpButton, True)
self.welcome_page = ConnectWelcomePage(self, ConnectWelcome)
self.host_page = ConnectHostPage(self, ConnectHost)
self.edit_page = ConnectEditPage(self, ConnectEdit)
self.finish_page = ConnectFinishPage(self, ConnectFinish)
self.setPage(self.welcome_page.pageId, self.welcome_page)
self.setPage(self.host_page.pageId, self.host_page)
self.setPage(self.edit_page.pageId, self.edit_page)
self.setPage(self.finish_page.pageId, self.finish_page)
self.registerFields()
self.retranslateUi()
# Connect signals
self.button(QtGui.QWizard.HelpButton).clicked.connect(self.showHelp)
log.debug('ProjectorWizard() started')
def exec_(self, projector=None):
"""
Override function to determine whether we are called to add a new
projector or edit an old projector.
:param projector: Projector instance
:returns: None
"""
self.projector = projector
if self.projector is None:
log.debug('ProjectorWizard() Adding new projector')
self.setWindowTitle(translate('OpenLP.ProjectorWizard',
'New Projector Wizard'))
self.setStartId(ConnectWelcome)
self.restart()
else:
log.debug('ProjectorWizard() Editing existing projector')
self.setWindowTitle(translate('OpenLP.ProjectorWizard',
'Edit Projector Wizard'))
self.setStartId(ConnectEdit)
self.restart()
saved = QtGui.QWizard.exec_(self)
self.projector = None
return saved
def registerFields(self):
"""
Register selected fields for use by all pages.
"""
self.host_page.registerField('ip_number*', self.host_page.ip_number_text)
self.edit_page.registerField('pjlink_port', self.host_page.pjlink_port_text)
self.edit_page.registerField('pjlink_pin', self.host_page.pjlink_pin_text)
self.edit_page.registerField('projector_name*', self.edit_page.name_text)
self.edit_page.registerField('projector_location', self.edit_page.location_text)
self.edit_page.registerField('projector_notes', self.edit_page.notes_text, 'plainText')
self.edit_page.registerField('projector_make', self.host_page.manufacturer_text)
self.edit_page.registerField('projector_model', self.host_page.model_text)
@pyqtSlot()
def showHelp(self):
"""
Show the pop-up help message.
"""
page = self.currentPage()
try:
help_page = page.help_
except:
help_page = self.no_help
QtGui.QMessageBox.information(self, self.title_help, help_page)
def retranslateUi(self):
"""
Fixed-text strings used for translations
"""
self.title_help = translate('OpenLP.ProjectorWizard', 'Projector Wizard Help')
self.no_help = translate('OpenLP.ProjectorWizard',
'Sorry - no help available for this page.')
self.welcome_page.title_label.setText('<span style=\'font-size:14pt; font-weight:600;\'>%s</span>' %
translate('OpenLP.ProjectorWizard',
'Welcome to the<br />Projector Wizard'))
self.welcome_page.information_label.setText(translate('OpenLP.ProjectorWizard', 'This wizard will help you to '
'create and edit your Projector control. <br /><br />'
'Press "Next" button below to continue.'))
self.host_page.setTitle(translate('OpenLP.ProjectorWizard', 'Host IP Number'))
self.host_page.setSubTitle(translate('OpenLP.ProjectorWizard',
'Enter the IP address, port, and PIN used to conenct to the projector. '
'The port should only be changed if you know what you\'re doing, and '
'the pin should only be entered if it\'s required.'
'<br /><br />Once the IP address is checked and is '
'not in the database, you can continue to the next page'))
self.host_page.help_ = translate('OpenLP.ProjectorWizard',
'<b>IP</b>: The IP address of the projector to connect to.<br />'
'<b>Port</b>: The port number. Default is 4352.<br />'
'<b>PIN</b>: If needed, enter the PIN access code for the projector.<br />'
'<br />Once I verify the address is a valid IP and not in the database, you '
'can then add the rest of the information on the next page.')
self.host_page.ip_number_label.setText(translate('OpenLP.ProjectorWizard', 'IP Number: '))
self.host_page.pjlink_port_label.setText(translate('OpenLP.ProjectorWizard', 'Port: '))
self.host_page.pjlink_pin_label.setText(translate('OpenLP.ProjectorWizard', 'PIN: '))
self.edit_page.setTitle(translate('OpenLP.ProjectorWizard', 'Add/Edit Projector Information'))
self.edit_page.setSubTitle(translate('OpenLP.ProjectorWizard',
'Enter the information below in the left panel for the projector.'))
self.edit_page.help_ = translate('OpenLP.ProjectorWizard',
'Please enter the following information:'
'<br /><br /><b>PJLink Port</b>: The network port to use. Default is %s.'
'<br /><br /><b>PJLink PIN</b>: The PJLink access PIN. Only required if '
'PJLink PIN is set in projector. 4 characters max. <br /><br /><b>Name</b>: '
'A unique name you want to give to this projector entry. 20 characters max. '
'<br /><br /><b>Location</b>: The location of the projector. 30 characters '
'max.<br /><br /><b>Notes</b>: Any notes you want to add about this '
'projector. 200 characters max.<br /><br />The "Manufacturer" and "Model" '
'information will only be available if the projector is connected to the '
'network and can be accessed while running this wizard. '
'(Currently not implemented)' % PJLINK_PORT)
self.edit_page.ip_number_label.setText(translate('OpenLP.ProjectorWizard', 'IP Number: '))
self.edit_page.pjlink_port_label.setText(translate('OpenLP.ProjectorWizard', 'PJLink port: '))
self.edit_page.pjlink_pin_label.setText(translate('OpenLP.ProjectorWizard', 'PJLink PIN: '))
self.edit_page.name_label.setText(translate('OpenLP.ProjectorWizard', 'Name: '))
self.edit_page.location_label.setText(translate('OpenLP.ProjectorWizard', 'Location: '))
self.edit_page.notes_label.setText(translate('OpenLP.ProjectorWizard', 'Notes: '))
self.edit_page.projector_make_label.setText(translate('OpenLP.ProjectorWizard', 'Manufacturer: '))
self.edit_page.projector_model_label.setText(translate('OpenLP.ProjectorWizard', 'Model: '))
self.finish_page.title_label.setText('<span style=\'font-size:14pt; font-weight:600;\'>%s</span>' %
translate('OpenLP.ProjectorWizard', 'Projector Added'))
self.finish_page.information_label.setText(translate('OpenLP.ProjectorWizard',
'<br />Have fun with your new projector.'))
class ConnectBase(QtGui.QWizardPage):
"""
Base class for the projector wizard pages.
"""
def __init__(self, parent=None, page=None):
super().__init__(parent)
self.pageId = page
def nextId(self):
"""
Returns next page to show.
"""
return PAGE_NEXT[self.pageId]
def setVisible(self, visible):
"""
Set buttons for bottom of page.
"""
QtGui.QWizardPage.setVisible(self, visible)
if visible:
try:
self.myCustomButton()
except:
try:
self.wizard().setButtonLayout(self.myButtons)
except:
self.wizard().setButtonLayout([QtGui.QWizard.Stretch,
QtGui.QWizard.BackButton,
QtGui.QWizard.NextButton,
QtGui.QWizard.CancelButton])
class ConnectWelcomePage(ConnectBase):
"""
Splash screen
"""
def __init__(self, parent, page):
super().__init__(parent, page)
self.setPixmap(QtGui.QWizard.WatermarkPixmap,
QtGui.QPixmap(':/wizards/wizard_createprojector.png'))
self.setObjectName('welcome_page')
self.myButtons = [QtGui.QWizard.Stretch,
QtGui.QWizard.NextButton]
self.layout = QtGui.QVBoxLayout(self)
self.layout.setObjectName('layout')
self.title_label = QtGui.QLabel(self)
self.title_label.setObjectName('title_label')
self.layout.addWidget(self.title_label)
self.layout.addSpacing(40)
self.information_label = QtGui.QLabel(self)
self.information_label.setWordWrap(True)
self.information_label.setObjectName('information_label')
self.layout.addWidget(self.information_label)
self.layout.addStretch()
class ConnectHostPage(ConnectBase):
"""
Get initial information.
"""
def __init__(self, parent, page):
super().__init__(parent, page)
self.setObjectName('host_page')
self.myButtons = [QtGui.QWizard.HelpButton,
QtGui.QWizard.Stretch,
QtGui.QWizard.BackButton,
QtGui.QWizard.NextButton,
QtGui.QWizard.CancelButton]
self.hostPageLayout = QtGui.QHBoxLayout(self)
self.hostPageLayout.setObjectName('layout')
# Projector DB information
self.localAreaBox = QtGui.QGroupBox(self)
self.localAreaBox.setObjectName('host_local_area_box')
self.localAreaForm = QtGui.QFormLayout(self.localAreaBox)
self.localAreaForm.setObjectName('host_local_area_form')
self.ip_number_label = QtGui.QLabel(self.localAreaBox)
self.ip_number_label.setObjectName('host_ip_number_label')
self.ip_number_text = QtGui.QLineEdit(self.localAreaBox)
self.ip_number_text.setObjectName('host_ip_number_text')
self.localAreaForm.addRow(self.ip_number_label, self.ip_number_text)
self.pjlink_port_label = QtGui.QLabel(self.localAreaBox)
self.pjlink_port_label.setObjectName('host_pjlink_port_label')
self.pjlink_port_text = QtGui.QLineEdit(self.localAreaBox)
self.pjlink_port_text.setMaxLength(5)
self.pjlink_port_text.setText(str(PJLINK_PORT))
self.pjlink_port_text.setObjectName('host_pjlink_port_text')
self.localAreaForm.addRow(self.pjlink_port_label, self.pjlink_port_text)
self.pjlink_pin_label = QtGui.QLabel(self.localAreaBox)
self.pjlink_pin_label.setObjectName('host_pjlink_pin_label')
self.pjlink_pin_text = QtGui.QLineEdit(self.localAreaBox)
self.pjlink_pin_text.setObjectName('host_pjlink_pin_text')
self.localAreaForm.addRow(self.pjlink_pin_label, self.pjlink_pin_text)
self.hostPageLayout.addWidget(self.localAreaBox)
self.manufacturer_text = QtGui.QLineEdit(self)
self.manufacturer_text.setVisible(False)
self.model_text = QtGui.QLineEdit(self)
self.model_text.setVisible(False)
def validatePage(self):
"""
Validate IP number/FQDN before continuing to next page.
"""
adx = self.wizard().field('ip_number')
port = self.wizard().field('pjlink_port')
pin = self.wizard().field('pjlink_pin')
log.debug('ip="%s" port="%s" pin="%s"' % (adx, port, pin))
valid = verify_ip_address(adx)
if valid:
ip = self.wizard().db.get_projector_by_ip(adx)
if ip is None:
valid = True
else:
QtGui.QMessageBox.warning(self,
translate('OpenLP.ProjectorWizard', 'Already Saved'),
translate('OpenLP.ProjectorWizard',
'IP "%s"<br />is already in the database as ID %s.'
'<br /><br />Please Enter a different IP.' % (adx, ip.id)))
valid = False
else:
QtGui.QMessageBox.warning(self,
translate('OpenLP.ProjectorWizard', 'Invalid IP'),
translate('OpenLP.ProjectorWizard',
'IP "%s"<br>is not a valid IP address.'
'<br /><br />Please enter a valid IP address.' % adx))
valid = False
"""
FIXME - Future plan to retrieve manufacture/model input source information. Not implemented yet.
new = PJLink(host=adx, port=port, pin=pin if pin.strip() != '' else None)
if new.connect():
mfg = new.get_manufacturer()
log.debug('Setting manufacturer_text to %s' % mfg)
self.manufacturer_text.setText(mfg)
model = new.get_model()
log.debug('Setting model_text to %s' % model)
self.model_text.setText(model)
else:
if new.status_error == E_AUTHENTICATION:
QtGui.QMessageBox.warning(self,
translate('OpenLP.ProjectorWizard', 'Requires Authorization'),
translate('OpenLP.ProjectorWizard',
'Projector requires authorization and either PIN not set '
'or invalid PIN set.'
'<br />Enter a valid PIN before hitting "NEXT"')
)
elif new.status_error == E_NO_AUTHENTICATION:
QtGui.QMessageBox.warning(self,
translate('OpenLP.ProjectorWizard', 'No Authorization Required'),
translate('OpenLP.ProjectorWizard',
'Projector does not require authorization and PIN set.'
'<br />Remove PIN entry before hitting "NEXT"')
)
valid = False
new.disconnect()
del(new)
"""
return valid
class ConnectEditPage(ConnectBase):
"""
Full information page.
"""
newProjector = QtCore.pyqtSignal(str)
editProjector = QtCore.pyqtSignal(object)
def __init__(self, parent, page):
super().__init__(parent, page)
self.setObjectName('edit_page')
self.editPageLayout = QtGui.QHBoxLayout(self)
self.editPageLayout.setObjectName('layout')
# Projector DB information
self.localAreaBox = QtGui.QGroupBox(self)
self.localAreaBox.setObjectName('edit_local_area_box')
self.localAreaForm = QtGui.QFormLayout(self.localAreaBox)
self.localAreaForm.setObjectName('edit_local_area_form')
self.ip_number_label = QtGui.QLabel(self.localAreaBox)
self.ip_number_label.setObjectName('edit_ip_number_label')
self.ip_number_text = QtGui.QLineEdit(self.localAreaBox)
self.ip_number_text.setObjectName('edit_ip_number_text')
self.localAreaForm.addRow(self.ip_number_label, self.ip_number_text)
self.pjlink_port_label = QtGui.QLabel(self.localAreaBox)
self.pjlink_port_label.setObjectName('edit_pjlink_port_label')
self.pjlink_port_text = QtGui.QLineEdit(self.localAreaBox)
self.pjlink_port_text.setMaxLength(5)
self.pjlink_port_text.setObjectName('edit_pjlink_port_text')
self.localAreaForm.addRow(self.pjlink_port_label, self.pjlink_port_text)
self.pjlink_pin_label = QtGui.QLabel(self.localAreaBox)
self.pjlink_pin_label.setObjectName('pjlink_pin_label')
self.pjlink_pin_text = QtGui.QLineEdit(self.localAreaBox)
self.pjlink_pin_text.setObjectName('pjlink_pin_text')
self.localAreaForm.addRow(self.pjlink_pin_label, self.pjlink_pin_text)
self.name_label = QtGui.QLabel(self.localAreaBox)
self.name_label.setObjectName('name_label')
self.name_text = QtGui.QLineEdit(self.localAreaBox)
self.name_text.setObjectName('name_label')
self.name_text.setMaxLength(20)
self.localAreaForm.addRow(self.name_label, self.name_text)
self.location_label = QtGui.QLabel(self.localAreaBox)
self.location_label.setObjectName('location_label')
self.location_text = QtGui.QLineEdit(self.localAreaBox)
self.location_text.setObjectName('location_text')
self.location_text.setMaxLength(30)
self.localAreaForm.addRow(self.location_label, self.location_text)
self.notes_label = QtGui.QLabel(self.localAreaBox)
self.notes_label.setObjectName('notes_label')
self.notes_text = QtGui.QPlainTextEdit(self.localAreaBox)
self.notes_text.setObjectName('notes_text')
self.localAreaForm.addRow(self.notes_label, self.notes_text)
self.editPageLayout.addWidget(self.localAreaBox)
# Projector retrieved information
self.remoteAreaBox = QtGui.QGroupBox(self)
self.remoteAreaBox.setObjectName('edit_remote_area_box')
self.remoteAreaForm = QtGui.QFormLayout(self.remoteAreaBox)
self.remoteAreaForm.setObjectName('edit_remote_area_form')
self.projector_make_label = QtGui.QLabel(self.remoteAreaBox)
self.projector_make_label.setObjectName('projector_make_label')
self.projector_make_text = QtGui.QLabel(self.remoteAreaBox)
self.projector_make_text.setObjectName('projector_make_text')
self.remoteAreaForm.addRow(self.projector_make_label, self.projector_make_text)
self.projector_model_label = QtGui.QLabel(self.remoteAreaBox)
self.projector_model_label.setObjectName('projector_model_text')
self.projector_model_text = QtGui.QLabel(self.remoteAreaBox)
self.projector_model_text.setObjectName('projector_model_text')
self.remoteAreaForm.addRow(self.projector_model_label, self.projector_model_text)
self.editPageLayout.addWidget(self.remoteAreaBox)
def initializePage(self):
"""
Fill in the blanks for information from previous page/projector to edit.
"""
if self.wizard().projector is not None:
log.debug('ConnectEditPage.initializePage() Editing existing projector')
self.ip_number_text.setText(self.wizard().projector.ip)
self.pjlink_port_text.setText(str(self.wizard().projector.port))
self.pjlink_pin_text.setText(self.wizard().projector.pin)
self.name_text.setText(self.wizard().projector.name)
self.location_text.setText(self.wizard().projector.location)
self.notes_text.insertPlainText(self.wizard().projector.notes)
self.myButtons = [QtGui.QWizard.HelpButton,
QtGui.QWizard.Stretch,
QtGui.QWizard.FinishButton,
QtGui.QWizard.CancelButton]
else:
log.debug('Retrieving information from host page')
self.ip_number_text.setText(self.wizard().field('ip_number'))
self.pjlink_port_text.setText(self.wizard().field('pjlink_port'))
self.pjlink_pin_text.setText(self.wizard().field('pjlink_pin'))
make = self.wizard().field('projector_make')
model = self.wizard().field('projector_model')
if make is None or make.strip() == '':
self.projector_make_text.setText('Unavailable ')
else:
self.projector_make_text.setText(make)
if model is None or model.strip() == '':
self.projector_model_text.setText('Unavailable ')
else:
self.projector_model_text.setText(model)
self.myButtons = [QtGui.QWizard.HelpButton,
QtGui.QWizard.Stretch,
QtGui.QWizard.BackButton,
QtGui.QWizard.NextButton,
QtGui.QWizard.CancelButton]
def validatePage(self):
"""
Last verification if editiing existing entry in case of IP change. Add entry to DB.
"""
log.debug('ConnectEditPage().validatePage()')
if self.wizard().projector is not None:
ip = self.ip_number_text.text()
port = self.pjlink_port_text.text()
name = self.name_text.text()
location = self.location_text.text()
notes = self.notes_text.toPlainText()
pin = self.pjlink_pin_text.text()
log.debug('edit-page() Verifying info : ip="%s"' % ip)
valid = verify_ip_address(ip)
if not valid:
QtGui.QMessageBox.warning(self,
translate('OpenLP.ProjectorWizard', 'Invalid IP'),
translate('OpenLP.ProjectorWizard',
'IP "%s"<br>is not a valid IP address.'
'<br /><br />Please enter a valid IP address.' % ip))
return False
log.debug('Saving edited projector %s' % ip)
self.wizard().projector.ip = ip
self.wizard().projector.port = port
self.wizard().projector.name = name
self.wizard().projector.location = location
self.wizard().projector.notes = notes
self.wizard().projector.pin = pin
saved = self.db.update_projector(self.wizard().projector)
if not saved:
QtGui.QMessageBox.error(self, translate('OpenLP.ProjectorWizard', 'Database Error'),
translate('OpenLP.ProjectorWizard', 'There was an error saving projector '
'information. See the log for the error'))
return False
self.editProjector.emit(self.wizard().projector)
else:
projector = Projector(ip=self.wizard().field('ip_number'),
port=self.wizard().field('pjlink_port'),
name=self.wizard().field('projector_name'),
location=self.wizard().field('projector_location'),
notes=self.wizard().field('projector_notes'),
pin=self.wizard().field('pjlink_pin'))
log.debug('Adding new projector %s' % projector.ip)
if self.wizard().db.get_projector_by_ip(projector.ip) is None:
saved = self.wizard().db.add_projector(projector)
if not saved:
QtGui.QMessageBox.error(self, translate('OpenLP.ProjectorWizard', 'Database Error'),
translate('OpenLP.ProjectorWizard', 'There was an error saving projector '
'information. See the log for the error'))
return False
self.newProjector.emit('%s' % projector.ip)
return True
def nextId(self):
"""
Returns the next page ID if new entry or end of wizard if editing entry.
"""
if self.wizard().projector is None:
return PAGE_NEXT[self.pageId]
else:
return -1
class ConnectFinishPage(ConnectBase):
"""
Buh-Bye page
"""
def __init__(self, parent, page):
super().__init__(parent, page)
self.setObjectName('connect_finish_page')
self.setPixmap(QtGui.QWizard.WatermarkPixmap, QtGui.QPixmap(':/wizards/wizard_createprojector.png'))
self.myButtons = [QtGui.QWizard.Stretch,
QtGui.QWizard.FinishButton]
self.isFinalPage()
self.layout = QtGui.QVBoxLayout(self)
self.layout.setObjectName('layout')
self.title_label = QtGui.QLabel(self)
self.title_label.setObjectName('title_label')
self.layout.addWidget(self.title_label)
self.layout.addSpacing(40)
self.information_label = QtGui.QLabel(self)
self.information_label.setWordWrap(True)
self.information_label.setObjectName('information_label')
self.layout.addWidget(self.information_label)
self.layout.addStretch()

View File

@ -38,6 +38,7 @@ from openlp.core.lib import PluginStatus, 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__)
@ -67,9 +68,10 @@ class SettingsForm(QtGui.QDialog, Ui_SettingsDialog, RegistryProperties):
self.stacked_layout.takeAt(0)
self.insert_tab(self.general_tab, 0, PluginStatus.Active)
self.insert_tab(self.themes_tab, 1, PluginStatus.Active)
self.insert_tab(self.advanced_tab, 2, PluginStatus.Active)
self.insert_tab(self.player_tab, 3, PluginStatus.Active)
count = 4
self.insert_tab(self.projector_tab, 2, PluginStatus.Active)
self.insert_tab(self.advanced_tab, 3, PluginStatus.Active)
self.insert_tab(self.player_tab, 4, PluginStatus.Active)
count = 5
for plugin in self.plugin_manager.plugins:
if plugin.settings_tab:
self.insert_tab(plugin.settings_tab, count, plugin.status)
@ -125,6 +127,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

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,26 @@
<file>theme_new.png</file>
<file>theme_edit.png</file>
</qresource>
<qresource prefix="projector">
<file>projector_blank.png</file>
<file>projector_connect.png</file>
<file>projector_connectors.png</file>
<file>projector_cooldown.png</file>
<file>projector_disconnect.png</file>
<file>projector_edit.png</file>
<file>projector_error.png</file>
<file>projector_manager.png</file>
<file>projector_new.png</file>
<file>projector_not_connected.png</file>
<file>projector_off.png</file>
<file>projector_on.png</file>
<file>projector_power_off.png</file>
<file>projector_power_on.png</file>
<file>projector_show.png</file>
<file>projector_status.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: 1.4 KiB

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: 1.2 KiB

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: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 720 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 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: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 744 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

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

@ -26,3 +26,28 @@
# 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
"""
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()

View File

@ -0,0 +1,101 @@
# -*- 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.
"""
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, ProjectorWizard
from openlp.core.lib.projectordb 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.get_application()
Registry.create()
if not hasattr(self, 'projector_manager'):
with patch('openlp.core.lib.projectordb.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):
"""
Delete all the C++ objects at the end so that we don't have a segfault
"""
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 wizard page is initialized
self.assertEqual(type(self.projector_manager.projector_form), ProjectorWizard,
'Initialization should have created a Wizard')
self.assertIs(self.projector_manager.projectordb,
self.projector_manager.projector_form.db,
'Wizard should be using same ProjectorDB() instance')

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.projectordb 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')