openlp-core team mailing list archive
-
openlp-core team
-
Mailing list archive
-
Message #24304
[Merge] lp:~alisonken1/openlp/projector-2.1 into lp:openlp
Ken Roberts has proposed merging lp:~alisonken1/openlp/projector-2.1 into lp:openlp.
Requested reviews:
OpenLP Core (openlp-core)
For more details, see:
https://code.launchpad.net/~alisonken1/openlp/projector-2.1/+merge/236600
Remote control of networked projector(s). Control includes:
power on
power standby
blank screen
show screen
select source input
Jenkins output:
lp:~alisonken1/openlp/projector-2.1 (revision 2423)
[SUCCESS] http://ci.openlp.org/job/Branch-01-Pull/644/
[SUCCESS] http://ci.openlp.org/job/Branch-02-Functional-Tests/592/
[SUCCESS] http://ci.openlp.org/job/Branch-03-Interface-Tests/536/
[SUCCESS] http://ci.openlp.org/job/Branch-04a-Windows_Functional_Tests/488/
[SUCCESS] http://ci.openlp.org/job/Branch-04b-Windows_Interface_Tests/98/
[SUCCESS] http://ci.openlp.org/job/Branch-05a-Code_Analysis/309/
[SUCCESS] http://ci.openlp.org/job/Branch-05b-Test_Coverage/183/
--
https://code.launchpad.net/~alisonken1/openlp/projector-2.1/+merge/236600
Your team OpenLP Core is requested to review the proposed merge of lp:~alisonken1/openlp/projector-2.1 into lp:openlp.
=== modified file '.bzrignore'
--- .bzrignore 2014-05-03 15:01:43 +0000
+++ .bzrignore 2014-09-30 20:09:40 +0000
@@ -33,3 +33,4 @@
__pycache__
*.dll
.directory
+*.kate-swp
=== modified file 'openlp/core/common/__init__.py'
--- openlp/core/common/__init__.py 2014-08-27 23:18:06 +0000
+++ openlp/core/common/__init__.py 2014-09-30 20:09:40 +0000
@@ -30,13 +30,18 @@
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 +159,81 @@
"""
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
=== modified file 'openlp/core/common/registryproperties.py'
--- openlp/core/common/registryproperties.py 2014-08-27 23:18:06 +0000
+++ openlp/core/common/registryproperties.py 2014-09-30 20:09:40 +0000
@@ -148,3 +148,12 @@
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
=== modified file 'openlp/core/common/settings.py'
--- openlp/core/common/settings.py 2014-08-27 23:18:06 +0000
+++ openlp/core/common/settings.py 2014-09-30 20:09:40 +0000
@@ -275,6 +275,7 @@
'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 @@
'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__ = [
=== modified file 'openlp/core/common/uistrings.py'
--- openlp/core/common/uistrings.py 2014-03-20 19:10:31 +0000
+++ openlp/core/common/uistrings.py 2014-09-30 20:09:40 +0000
@@ -99,6 +99,10 @@
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 @@
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')
=== modified file 'openlp/core/lib/__init__.py'
--- openlp/core/lib/__init__.py 2014-05-01 17:49:43 +0000
+++ openlp/core/lib/__init__.py 2014-09-30 20:09:40 +0000
@@ -330,3 +330,6 @@
from .imagemanager import ImageManager
from .renderer import Renderer
from .mediamanageritem import MediaManagerItem
+from .projectordb import ProjectorDB, Projector
+from .projectorpjlink1 import PJLink1
+from .projectorconstants import PJLINK_PORT, ErrorMsg, ErrorString
=== modified file 'openlp/core/lib/db.py'
--- openlp/core/lib/db.py 2014-07-17 21:04:58 +0000
+++ openlp/core/lib/db.py 2014-09-30 20:09:40 +0000
@@ -48,20 +48,53 @@
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.create_all(engine, checkfirst=True)
+ 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 @@
"""
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 @@
: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)
=== added file 'openlp/core/lib/projectorconstants.py'
--- openlp/core/lib/projectorconstants.py 1970-01-01 00:00:00 +0000
+++ openlp/core/lib/projectorconstants.py 2014-09-30 20:09:40 +0000
@@ -0,0 +1,272 @@
+# -*- 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')
+
+__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',
+ 'ErrorString', "CR", "LF",
+ "PJLINK_PORT", "PJLINK_MAX_PACKET", "TIMEOUT", 'ErrorMsg', 'PJLinkErrors',
+ 'StatusString', 'PJLINK_VALID_CMD', 'ConnectionErrors']
+
+# 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
+
+ConnectionErrors = {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}
+
+PJLinkErrors = {'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: 'ERRA',
+ E_UNDEFINED: 'ERR1',
+ E_PARAMETER: 'ERR2',
+ E_UNAVAILABLE: 'ERR3',
+ E_PROJECTOR: 'ERR4'
+ }
+
+# Map error/status codes to string
+ErrorString = {0: 'S_OK',
+ E_GENERAL: 'E_GENERAL',
+ E_NOT_CONNECTED: 'E_NOT_CONNECTED',
+ E_FAN: 'E_FAN',
+ E_LAMP: 'E_LAMP',
+ E_TEMP: 'E_TEMP',
+ E_COVER: 'E_COVER',
+ E_FILTER: 'E_FILTER',
+ E_AUTHENTICATION: 'E_AUTHENTICATION',
+ E_NO_AUTHENTICATION: 'E_NO_AUTHENTICATION',
+ E_UNDEFINED: 'E_UNDEFINED',
+ E_PARAMETER: 'E_PARAMETER',
+ E_UNAVAILABLE: 'E_UNAVAILABLE',
+ E_PROJECTOR: 'E_PROJECTOR',
+ E_INVALID_DATA: 'E_INVALID_DATA',
+ E_WARN: 'E_WARN',
+ E_ERROR: 'E_ERROR',
+ E_CLASS: 'E_CLASS',
+ E_PREFIX: 'E_PREFIX', # Last projector error
+ E_CONNECTION_REFUSED: 'E_CONNECTION_REFUSED', # First QtSocket error
+ E_REMOTE_HOST_CLOSED_CONNECTION: 'E_REMOTE_HOST_CLOSED_CONNECTION',
+ E_HOST_NOT_FOUND: 'E_HOST_NOT_FOUND',
+ E_SOCKET_ACCESS: 'E_SOCKET_ACCESS',
+ E_SOCKET_RESOURCE: 'E_SOCKET_RESOURCE',
+ E_SOCKET_TIMEOUT: 'E_SOCKET_TIMEOUT',
+ E_DATAGRAM_TOO_LARGE: 'E_DATAGRAM_TOO_LARGE',
+ E_NETWORK: 'E_NETWORK',
+ E_ADDRESS_IN_USE: 'E_ADDRESS_IN_USE',
+ E_SOCKET_ADDRESS_NOT_AVAILABLE: 'E_SOCKET_ADDRESS_NOT_AVAILABLE',
+ E_UNSUPPORTED_SOCKET_OPERATION: 'E_UNSUPPORTED_SOCKET_OPERATION',
+ E_PROXY_AUTHENTICATION_REQUIRED: 'E_PROXY_AUTHENTICATION_REQUIRED',
+ E_SLS_HANDSHAKE_FAILED: 'E_SLS_HANDSHAKE_FAILED',
+ E_UNFINISHED_SOCKET_OPERATION: 'E_UNFINISHED_SOCKET_OPERATION',
+ E_PROXY_CONNECTION_REFUSED: 'E_PROXY_CONNECTION_REFUSED',
+ E_PROXY_CONNECTION_CLOSED: 'E_PROXY_CONNECTION_CLOSED',
+ E_PROXY_CONNECTION_TIMEOUT: 'E_PROXY_CONNECTION_TIMEOUT',
+ E_PROXY_NOT_FOUND: 'E_PROXY_NOT_FOUND',
+ E_PROXY_PROTOCOL: 'E_PROXY_PROTOCOL',
+ E_UNKNOWN_SOCKET_ERROR: 'E_UNKNOWN_SOCKET_ERROR'
+ }
+
+StatusString = {S_NOT_CONNECTED: 'S_NOT_CONNECTED',
+ S_CONNECTING: 'S_CONNECTING',
+ S_CONNECTED: 'S_CONNECTED',
+ S_STATUS: 'S_STATUS',
+ S_OFF: 'S_OFF',
+ S_INITIALIZE: 'S_INITIALIZE',
+ S_STANDBY: 'S_STANDBY',
+ S_WARMUP: 'S_WARMUP',
+ S_ON: 'S_ON',
+ S_COOLDOWN: 'S_COOLDOWN',
+ S_INFO: 'S_INFO',
+ S_NETWORK_SENDING: 'S_NETWORK_SENDING',
+ S_NETWORK_RECEIVED: 'S_NETWORK_RECEIVED'
+ }
+
+# Map error/status codes to message strings
+ErrorMsg = {E_OK: 'OK', # E_OK | S_OK
+ E_GENERAL: "General projector error",
+ E_NOT_CONNECTED: "Not connected error",
+ E_NETWORK: "Network error",
+ E_LAMP: "Lamp error",
+ E_FAN: "Fan error",
+ E_TEMP: "High temperature detected",
+ E_COVER: "Cover open detected",
+ E_FILTER: "Check filter",
+ E_AUTHENTICATION: "Authentication Error",
+ E_UNDEFINED: "Undefined Command",
+ E_PARAMETER: "Invalid Parameter",
+ E_UNAVAILABLE: "Projector Busy",
+ E_PROJECTOR: "Projector/Display Error",
+ E_INVALID_DATA: "Invald packet received",
+ E_WARN: "Warning condition detected",
+ E_ERROR: "Error condition detected",
+ E_CLASS: "PJLink class not supported",
+ E_PREFIX: "Invalid prefix character",
+ E_CONNECTION_REFUSED: "The connection was refused by the peer (or timed out)",
+ E_REMOTE_HOST_CLOSED_CONNECTION: "The remote host closed the connection",
+ E_HOST_NOT_FOUND: "The host address was not found",
+ E_SOCKET_ACCESS: "The socket operation failed because the application lacked the required privileges",
+ E_SOCKET_RESOURCE: "The local system ran out of resources (e.g., too many sockets)",
+ E_SOCKET_TIMEOUT: "The socket operation timed out",
+ E_DATAGRAM_TOO_LARGE: "The datagram was larger than the operating system's limit",
+ E_NETWORK: "An error occurred with the network (Possibly someone pulled the plug?)",
+ E_ADDRESS_IN_USE: "The address specified with socket.bind() is already in use and was set to be exclusive",
+ E_SOCKET_ADDRESS_NOT_AVAILABLE: "The address specified to socket.bind() does not belong to the host",
+ E_UNSUPPORTED_SOCKET_OPERATION: "The requested socket operation is not supported by the local "
+ "operating system (e.g., lack of IPv6 support)",
+ E_PROXY_AUTHENTICATION_REQUIRED: "The socket is using a proxy, and the proxy requires authentication",
+ E_SLS_HANDSHAKE_FAILED: "The SSL/TLS handshake failed",
+ E_UNFINISHED_SOCKET_OPERATION: "The last operation attempted has not finished yet "
+ "(still in progress in the background)",
+ E_PROXY_CONNECTION_REFUSED: "Could not contact the proxy server because the connection "
+ "to that server was denied",
+ E_PROXY_CONNECTION_CLOSED: "The connection to the proxy server was closed unexpectedly "
+ "(before the connection to the final peer was established)",
+ E_PROXY_CONNECTION_TIMEOUT: "The connection to the proxy server timed out or "
+ "the proxy server stopped responding in the authentication phase.",
+ E_PROXY_NOT_FOUND: "The proxy address set with setProxy() was not found",
+ E_PROXY_PROTOCOL: "The connection negotiation with the proxy server because the response "
+ "from the proxy server could not be understood",
+ E_UNKNOWN_SOCKET_ERROR: "An unidentified error occurred",
+ S_NOT_CONNECTED: "Not connected",
+ S_CONNECTING: "Connecting",
+ S_CONNECTED: "Connected",
+ S_STATUS: "Getting status",
+ S_OFF: "Off",
+ S_INITIALIZE: "Initialize in progress",
+ S_STANDBY: "Power in standby",
+ S_WARMUP: "Warmup in progress",
+ S_ON: "Power is on",
+ S_COOLDOWN: "Cooldown in progress",
+ S_INFO: "Projector Information availble",
+ S_NETWORK_SENDING: 'Sending data',
+ S_NETWORK_RECEIVED: 'Received data'
+ }
=== added file 'openlp/core/lib/projectordb.py'
--- openlp/core/lib/projectordb.py 1970-01-01 00:00:00 +0000
+++ openlp/core/lib/projectordb.py 2014-09-30 20:09:40 +0000
@@ -0,0 +1,296 @@
+# -*- 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
+
+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))
+
+
+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)
+ 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', pin='%s'" % (projector.ip,
+ projector.name,
+ projector.location,
+ projector.pin))
+ 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 projetor 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
+ 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 format_projector_data(self, record):
+ '''
+ Convert a record into a dict
+
+ :param record: Database record
+ :returns: dict
+ '''
+ if record is None:
+ return None
+ return {'id': record.id, 'name': record.name, 'ip': record.ip,
+ 'port': record.port, 'location': record.location,
+ 'notes': record.notes, 'pin': record.pin}
+
+ 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')
+ pjlink_default = {'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')}
+ for source in sources:
+ source_dict[source] = '%s %s' % (pjlink_default[source[0]], source[1])
+ else:
+ # We have a manufacturer entry, see if there's a default
+ pass # Fill in later
+ else:
+ # There's at least one model entry, see if there's more than one manufacturer
+ pass # Fill in later
+ return source_dict
=== added file 'openlp/core/lib/projectorpjlink1.py'
--- openlp/core/lib/projectorpjlink1.py 1970-01-01 00:00:00 +0000
+++ openlp/core/lib/projectorpjlink1.py 2014-09-30 20:09:40 +0000
@@ -0,0 +1,702 @@
+# -*- 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 specifics.
+"""
+
+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.projectorconstants 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
+ }
+ ###################################################################
+ # Initialization routines #
+ ###################################################################
+
+ 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()
+
+ ###################################################################
+ # Routines for the Gui #
+ ###################################################################
+
+ 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 ErrorString:
+ return (ErrorString[status], ErrorMsg[status])
+ elif status in StatusString:
+ return (StatusString[status], ErrorMsg[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 ConnectionErrors:
+ # 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)
+
+ ###################################################################
+ # Projector communications routines #
+ ###################################################################
+
+ 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) # % 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')
+ dontcare = self.readLine(self.maxSize) # Any extraneous characters in buffer
+ 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
+ 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')
+
+ ###################################################################
+ # Process projector return codes #
+ ###################################################################
+
+ 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 PJLinkErrors:
+ # 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 == '0':
+ # Power off/standby
+ self.power = S_STANDBY
+ self.change_status(S_STANDBY)
+ elif data == '1':
+ # Power on and lamps fully lit
+ self.power = S_ON
+ self.change_status(S_ON)
+ elif data == '2':
+ # Power on and lamps in cooldown
+ self.power = S_COOLDOWN
+ self.change_status(S_COOLDOWN)
+ elif data == '3':
+ # Power on and lamps warming up
+ self.power = S_WARMUP
+ self.change_status(S_WARMUP)
+ else:
+ # Unknown status
+ 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] == '1':
+ self.projector_errors[translate('OpenLP.ProjectorPJLink', 'Fan')] = E_WARN
+ elif data[0] == '2':
+ self.projector_errors[translate('OpenLP.ProjectorPJLink', 'Fan')] = E_ERROR
+ # Lamp
+ if data[1] == '1':
+ self.projector_errors[translate('OpenLP.ProjectorPJLink', 'Lamp')] = E_WARN
+ elif data[1] == '2':
+ self.projector_errors[translate('OpenLP.ProjectorPJLink', 'Lamp')] = E_ERROR
+ # Temp
+ if data[2] == '1':
+ self.projector_errors[translate('OpenLP.ProjectorPJLink', 'Temperature')] = E_WARN
+ elif data[2] == '2':
+ self.projector_errors[translate('OpenLP.ProjectorPJLink', 'Temperature')] = E_ERROR
+ # Cover
+ if data[3] == '1':
+ self.projector_errors[translate('OpenLP.ProjectorPJLink', 'Cover')] = E_WARN
+ elif data[3] == '2':
+ self.projector_errors[translate('OpenLP.ProjectorPJLink', 'Cover')] = E_ERROR
+ # Filter
+ if data[4] == '1':
+ self.projector_errors[translate('OpenLP.ProjectorPJLink', 'Filter')] = E_WARN
+ elif data[4] == '2':
+ self.projector_errors[translate('OpenLP.ProjectorPJLink', 'Filter')] = E_ERROR
+ # Other
+ if data[5] == '1':
+ self.projector_errors[translate('OpenLP.ProjectorPJLink', 'Other')] = E_WARN
+ elif data[5] == '2':
+ self.projector_errors[translate('OpenLP.ProjectorPJLink', 'Other')] = E_ERROR
+ return
+
+ ###################################################################
+ # Routines called by the GUI #
+ ###################################################################
+
+ 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)
+
+ @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()
+
+ ###################################################################
+ # Get commands #
+ ###################################################################
+
+ 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')
+
+ ###################################################################
+ # Set commands #
+ ###################################################################
+
+ 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()
=== modified file 'openlp/core/ui/__init__.py'
--- openlp/core/ui/__init__.py 2014-02-27 21:39:44 +0000
+++ openlp/core/ui/__init__.py 2014-09-30 20:09:40 +0000
@@ -124,9 +124,13 @@
from .mediadockmanager import MediaDockManager
from .servicemanager import ServiceManager
from .thememanager import ThemeManager
+from .projectormanager import ProjectorManager
+from .projectorwizard import ProjectorWizard
+from .projectortab 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']
=== modified file 'openlp/core/ui/mainwindow.py'
--- openlp/core/ui/mainwindow.py 2014-09-04 21:38:39 +0000
+++ openlp/core/ui/mainwindow.py 2014-09-30 20:09:40 +0000
@@ -53,6 +53,7 @@
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.projectormanager import ProjectorManager
log = logging.getLogger(__name__)
@@ -178,6 +179,14 @@
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 @@
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 @@
'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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
"""
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
=== added file 'openlp/core/ui/projectormanager.py'
--- openlp/core/ui/projectormanager.py 1970-01-01 00:00:00 +0000
+++ openlp/core/ui/projectormanager.py 2014-09-30 20:09:40 +0000
@@ -0,0 +1,838 @@
+# -*- 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.projectordb import ProjectorDB
+from openlp.core.lib.projectorpjlink1 import PJLink1
+from openlp.core.ui.projectorwizard import ProjectorWizard
+from openlp.core.lib.projectorconstants 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):
+ log.debug('bootstrap_initialise()')
+ 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):
+ log.debug('bootstrap_post_set_up()')
+ 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
+ :returs: 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'),
+ ErrorMsg[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, ErrorMsg[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 projecgtor 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 StatusString:
+ status_code = StatusString[status]
+ message = ErrorMsg[status] if msg is None else msg
+ elif status in ErrorString:
+ status_code = ErrorString[status]
+ message = ErrorMsg[status] if msg is None else msg
+ else:
+ status_code = status
+ message = ErrorMsg[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))
=== added file 'openlp/core/ui/projectortab.py'
--- openlp/core/ui/projectortab.py 1970-01-01 00:00:00 +0000
+++ openlp/core/ui/projectortab.py 2014-09-30 20:09:40 +0000
@@ -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
=== added file 'openlp/core/ui/projectorwizard.py'
--- openlp/core/ui/projectorwizard.py 1970-01-01 00:00:00 +0000
+++ openlp/core/ui/projectorwizard.py 2014-09-30 20:09:40 +0000
@@ -0,0 +1,561 @@
+# -*- 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.projectordb import ProjectorDB, Projector
+from openlp.core.lib.projectorpjlink1 import PJLink1
+from openlp.core.lib.projectorconstants 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, projector=None):
+ log.debug('__init__()')
+ super().__init__(parent)
+ self.db = projectordb
+ self.projector = projector
+ self.setObjectName('projector_wizard')
+ self.setWindowIcon(build_icon(u':/icon/openlp-logo.svg'))
+ self.setModal(True)
+ self.setWizardStyle(QtGui.QWizard.ModernStyle)
+ 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
+ '''
+ if projector is None:
+ log.debug('ProjectorWizard() Adding new projetor')
+ self.setWindowTitle(translate('OpenLP.ProjectorWizard',
+ 'New Projector Wizard'))
+ self.restart()
+ else:
+ log.debug('ProjectorWizard() Editing existing projetor')
+ self.setWindowTitle(translate('OpenLP.ProjectorWizard',
+ 'Edit Projector Wizard'))
+ self.edit_page.projector = projector
+ self.edit_page.db = self.db
+ self.setStartId(ConnectEdit)
+
+ return QtGui.QWizard.exec_(self)
+
+ 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:
+ v = 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.projector = None
+ 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.projector is not None:
+ log.debug('ConnectEditPage.initializePage() Editing existing projector')
+ self.ip_number_text.setText(self.projector.ip)
+ self.pjlink_port_text.setText(str(self.projector.port))
+ self.pjlink_pin_text.setText(self.projector.pin)
+ self.name_text.setText(self.projector.name)
+ self.location_text.setText(self.projector.location)
+ self.notes_text.insertPlainText(self.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.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.projector.ip = ip
+ self.projector.port = port
+ self.projector.name = name
+ self.projector.location = location
+ self.projector.notes = notes
+ self.projector.pin = pin
+ saved = self.db.update_projector(self.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.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.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()
=== modified file 'openlp/core/ui/settingsform.py'
--- openlp/core/ui/settingsform.py 2014-04-12 20:19:22 +0000
+++ openlp/core/ui/settingsform.py 2014-09-30 20:09:40 +0000
@@ -38,6 +38,7 @@
from openlp.core.ui import AdvancedTab, GeneralTab, ThemesTab
from openlp.core.ui.media import PlayerTab
from .settingsdialog import Ui_SettingsDialog
+from openlp.core.ui.projectortab import ProjectorTab
log = logging.getLogger(__name__)
@@ -67,9 +68,10 @@
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 @@
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
=== modified file 'resources/images/openlp-2.qrc'
--- resources/images/openlp-2.qrc 2014-09-05 20:15:44 +0000
+++ resources/images/openlp-2.qrc 2014-09-30 20:09:40 +0000
@@ -99,13 +99,14 @@
<file>export_load.png</file>
</qresource>
<qresource prefix="wizards">
+ <file>wizard_createprojector.png</file>
+ <file>wizard_createtheme.bmp</file>
+ <file>wizard_duplicateremoval.bmp</file>
<file>openlp-osx-wizard.png</file>
<file>wizard_exportsong.bmp</file>
+ <file>wizard_firsttime.bmp</file>
+ <file>wizard_importbible.bmp</file>
<file>wizard_importsong.bmp</file>
- <file>wizard_importbible.bmp</file>
- <file>wizard_firsttime.bmp</file>
- <file>wizard_createtheme.bmp</file>
- <file>wizard_duplicateremoval.bmp</file>
</qresource>
<qresource prefix="services">
<file>service_collapse_all.png</file>
@@ -172,4 +173,24 @@
<qresource prefix="remotes">
<file>android_app_qr.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>
</RCC>
=== added file 'resources/images/projector_blank.png'
Binary files resources/images/projector_blank.png 1970-01-01 00:00:00 +0000 and resources/images/projector_blank.png 2014-09-30 20:09:40 +0000 differ
=== added file 'resources/images/projector_connect.png'
Binary files resources/images/projector_connect.png 1970-01-01 00:00:00 +0000 and resources/images/projector_connect.png 2014-09-30 20:09:40 +0000 differ
=== added file 'resources/images/projector_connectors.png'
Binary files resources/images/projector_connectors.png 1970-01-01 00:00:00 +0000 and resources/images/projector_connectors.png 2014-09-30 20:09:40 +0000 differ
=== added file 'resources/images/projector_cooldown.png'
Binary files resources/images/projector_cooldown.png 1970-01-01 00:00:00 +0000 and resources/images/projector_cooldown.png 2014-09-30 20:09:40 +0000 differ
=== added file 'resources/images/projector_disconnect.png'
Binary files resources/images/projector_disconnect.png 1970-01-01 00:00:00 +0000 and resources/images/projector_disconnect.png 2014-09-30 20:09:40 +0000 differ
=== added file 'resources/images/projector_edit.png'
Binary files resources/images/projector_edit.png 1970-01-01 00:00:00 +0000 and resources/images/projector_edit.png 2014-09-30 20:09:40 +0000 differ
=== added file 'resources/images/projector_error.png'
Binary files resources/images/projector_error.png 1970-01-01 00:00:00 +0000 and resources/images/projector_error.png 2014-09-30 20:09:40 +0000 differ
=== added file 'resources/images/projector_manager.png'
Binary files resources/images/projector_manager.png 1970-01-01 00:00:00 +0000 and resources/images/projector_manager.png 2014-09-30 20:09:40 +0000 differ
=== added file 'resources/images/projector_new.png'
Binary files resources/images/projector_new.png 1970-01-01 00:00:00 +0000 and resources/images/projector_new.png 2014-09-30 20:09:40 +0000 differ
=== added file 'resources/images/projector_not_connected.png'
Binary files resources/images/projector_not_connected.png 1970-01-01 00:00:00 +0000 and resources/images/projector_not_connected.png 2014-09-30 20:09:40 +0000 differ
=== added file 'resources/images/projector_off.png'
Binary files resources/images/projector_off.png 1970-01-01 00:00:00 +0000 and resources/images/projector_off.png 2014-09-30 20:09:40 +0000 differ
=== added file 'resources/images/projector_on.png'
Binary files resources/images/projector_on.png 1970-01-01 00:00:00 +0000 and resources/images/projector_on.png 2014-09-30 20:09:40 +0000 differ
=== added file 'resources/images/projector_power_off.png'
Binary files resources/images/projector_power_off.png 1970-01-01 00:00:00 +0000 and resources/images/projector_power_off.png 2014-09-30 20:09:40 +0000 differ
=== added file 'resources/images/projector_power_on.png'
Binary files resources/images/projector_power_on.png 1970-01-01 00:00:00 +0000 and resources/images/projector_power_on.png 2014-09-30 20:09:40 +0000 differ
=== added file 'resources/images/projector_show.png'
Binary files resources/images/projector_show.png 1970-01-01 00:00:00 +0000 and resources/images/projector_show.png 2014-09-30 20:09:40 +0000 differ
=== added file 'resources/images/projector_status.png'
Binary files resources/images/projector_status.png 1970-01-01 00:00:00 +0000 and resources/images/projector_status.png 2014-09-30 20:09:40 +0000 differ
=== added file 'resources/images/projector_view.png'
Binary files resources/images/projector_view.png 1970-01-01 00:00:00 +0000 and resources/images/projector_view.png 2014-09-30 20:09:40 +0000 differ
=== added file 'resources/images/projector_warmup.png'
Binary files resources/images/projector_warmup.png 1970-01-01 00:00:00 +0000 and resources/images/projector_warmup.png 2014-09-30 20:09:40 +0000 differ
=== added file 'resources/images/wizard_createprojector.png'
Binary files resources/images/wizard_createprojector.png 1970-01-01 00:00:00 +0000 and resources/images/wizard_createprojector.png 2014-09-30 20:09:40 +0000 differ
=== added file 'tests/functional/openlp_core_common/test_projector_utilities.py'
--- tests/functional/openlp_core_common/test_projector_utilities.py 1970-01-01 00:00:00 +0000
+++ tests/functional/openlp_core_common/test_projector_utilities.py 2014-09-30 20:09:40 +0000
@@ -0,0 +1,162 @@
+# -*- 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')
=== modified file 'tests/functional/openlp_core_lib/__init__.py'
--- tests/functional/openlp_core_lib/__init__.py 2014-04-02 18:51:21 +0000
+++ tests/functional/openlp_core_lib/__init__.py 2014-09-30 20:09:40 +0000
@@ -27,5 +27,14 @@
# 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
"""
+
+from tests.functional import patch
+
+
+def tearDown():
+ """
+ Ensure test suite has been cleaned up after tests
+ """
+ patch.stopall()
=== added file 'tests/functional/openlp_core_lib/test_projectordb.py'
--- tests/functional/openlp_core_lib/test_projectordb.py 1970-01-01 00:00:00 +0000
+++ tests/functional/openlp_core_lib/test_projectordb.py 2014-09-30 20:09:40 +0000
@@ -0,0 +1,182 @@
+# -*- 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
+
+test_1 = Projector(ip='111.111.111.111',
+ port='1111',
+ pin='1111',
+ name='___TEST_ONE___',
+ location='location one',
+ notes='notes one'
+ )
+
+test_2 = Projector(ip='222.222.222.222',
+ port='2222',
+ pin='2222',
+ name='___TEST_TWO___',
+ location='location two',
+ notes='notes two'
+ )
+
+test_3 = Projector(ip='333.333.333.333',
+ port='3333',
+ pin='3333',
+ name='___TEST_THREE___',
+ location='location three',
+ notes='notes three'
+ )
+
+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, [test_1, test_2])
+
+ # WHEN: Search for record using IP
+ record = self.projector.get_projector_by_ip(test_2.ip)
+
+ # THEN: Verify proper record returned
+ self.assertTrue(compare_data(test_2, 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, [test_1, test_2])
+
+ # WHEN: Search for record using name
+ record = self.projector.get_projector_by_name(test_2.name)
+
+ # THEN: Verify proper record returned
+ self.assertTrue(compare_data(test_2, 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, [test_3, ])
+ record = self.projector.get_projector_by_ip(test_3.ip)
+
+ # WHEN: Record deleted
+ self.projector.delete_projector(record)
+
+ # THEN: Verify record not retrievable
+ found = self.projector.get_projector_by_ip(test_3.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, [test_1, test_2])
+
+ # WHEN: We retrieve a specific record
+ record = self.projector.get_projector_by_ip(test_1.ip)
+ record_id = record.id
+
+ # WHEN: Data is changed
+ record.ip = test_3.ip
+ record.port = test_3.port
+ record.pin = test_3.pin
+ record.name = test_3.name
+ record.location = test_3.location
+ record.notes = test_3.notes
+ updated = self.projector.update_projector(record)
+ self.assertTrue(updated, 'Save updated record should have returned True')
+ record = self.projector.get_projector_by_ip(test_3.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(test_3, record), 'Edited record should have new data')
=== modified file 'tests/interfaces/openlp_core_ui/__init__.py'
--- tests/interfaces/openlp_core_ui/__init__.py 2014-03-14 22:08:44 +0000
+++ tests/interfaces/openlp_core_ui/__init__.py 2014-09-30 20:09:40 +0000
@@ -26,3 +26,15 @@
# 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
+
+
+def tearDown():
+ """
+ Ensure test suite has been cleaned up after tests
+ """
+ patch.stopall()
=== added file 'tests/interfaces/openlp_core_ui/test_projectormanager.py'
--- tests/interfaces/openlp_core_ui/test_projectormanager.py 1970-01-01 00:00:00 +0000
+++ tests/interfaces/openlp_core_ui/test_projectormanager.py 2014-09-30 20:09:40 +0000
@@ -0,0 +1,123 @@
+# -*- 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
+
+tmpfile = '/tmp/openlp-test-projectormanager.sql'
+
+test_1 = Projector(ip='111.111.111.111',
+ port='1111',
+ pin='1111',
+ name='___TEST_ONE___',
+ location='location one',
+ notes='notes one'
+ )
+
+test_2 = Projector(ip='222.222.222.222',
+ port='2222',
+ pin='2222',
+ name='___TEST_TWO___',
+ location='location two',
+ notes='notes two'
+ )
+
+test_3 = Projector(ip='333.333.333.333',
+ port='3333',
+ pin='3333',
+ name='___TEST_THREE___',
+ location='location three',
+ notes='notes three'
+ )
+
+
+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')
Follow ups