openlp-core team mailing list archive
-
openlp-core team
-
Mailing list archive
-
Message #24497
[Merge] lp:~alisonken1/openlp/projector-2.1-merge into lp:openlp
Ken Roberts has proposed merging lp:~alisonken1/openlp/projector-2.1-merge into lp:openlp.
Requested reviews:
Raoul Snyman (raoul-snyman)
Jonathan Springer (springermac)
Tim Bentley (trb143)
For more details, see:
https://code.launchpad.net/~alisonken1/openlp/projector-2.1-merge/+merge/239913
Changes per springermac
Fix typos in Projector and ProjectorSource tables
Fix regression in source select dialog
Add upstream merges
Add basic remote control of networked projectors using PJLink control format.
See http://pjlink.jbmia.or.jp/english/dl.html section 5-1. PJLink Specifications
Creates ~/.openlp/data/projector/projector.sqlite database
Creates Projector Manager window below Theme Manager
Basic functions:
Create/edit/delete projector entry for control
Connect/disconnect to/from projector
Power on/standby projector
Blank/show projector screen
Edit source input selection with user-defined text
Select source input
lp:~alisonken1/openlp/projector-2.1-merge (revision 2531)
[SUCCESS] http://ci.openlp.org/job/Branch-01-Pull/722/
[SUCCESS] http://ci.openlp.org/job/Branch-02-Functional-Tests/665/
[SUCCESS] http://ci.openlp.org/job/Branch-03-Interface-Tests/609/
[SUCCESS] http://ci.openlp.org/job/Branch-04a-Windows_Functional_Tests/549/
[SUCCESS] http://ci.openlp.org/job/Branch-04b-Windows_Interface_Tests/158/
[SUCCESS] http://ci.openlp.org/job/Branch-05a-Code_Analysis/363/
[SUCCESS] http://ci.openlp.org/job/Branch-05b-Test_Coverage/237/
--
https://code.launchpad.net/~alisonken1/openlp/projector-2.1-merge/+merge/239913
Your team OpenLP Core is subscribed to branch lp:openlp.
=== modified file '.bzrignore'
--- .bzrignore 2014-07-11 11:35:56 +0000
+++ .bzrignore 2014-10-28 21:04:17 +0000
@@ -33,3 +33,10 @@
__pycache__
*.dll
.directory
+*.kate-swp
+# Git files
+.git
+.gitignore
+# Rejected diff's
+*.rej
+*.~\?~
=== 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-10-28 21:04:17 +0000
@@ -30,13 +30,17 @@
The :mod:`common` module contains most of the components and libraries that make
OpenLP work.
"""
+import hashlib
import re
import os
import logging
import sys
import traceback
+from ipaddress import IPv4Address, IPv6Address, AddressValueError
+from codecs import decode, encode
from PyQt4 import QtCore
+from PyQt4.QtCore import QCryptographicHash as QHash
log = logging.getLogger(__name__ + '.__init__')
@@ -154,6 +158,81 @@
"""
return sys.platform.startswith('linux')
+
+def verify_ipv4(addr):
+ """
+ Validate an IPv4 address
+
+ :param addr: Address to validate
+ :returns: bool
+ """
+ try:
+ valid = IPv4Address(addr)
+ return True
+ except AddressValueError:
+ return False
+
+
+def verify_ipv6(addr):
+ """
+ Validate an IPv6 address
+
+ :param addr: Address to validate
+ :returns: bool
+ """
+ try:
+ valid = IPv6Address(addr)
+ return True
+ except AddressValueError:
+ return False
+
+
+def verify_ip_address(addr):
+ """
+ Validate an IP address as either IPv4 or IPv6
+
+ :param addr: Address to validate
+ :returns: bool
+ """
+ return True if verify_ipv4(addr) else verify_ipv6(addr)
+
+
+def md5_hash(salt, data):
+ """
+ Returns the hashed output of md5sum on salt,data
+ using Python3 hashlib
+
+ :param salt: Initial salt
+ :param data: Data to hash
+ :returns: str
+ """
+ log.debug('md5_hash(salt="%s")' % salt)
+ hash_obj = hashlib.new('md5')
+ hash_obj.update(salt.encode('ascii'))
+ hash_obj.update(data.encode('ascii'))
+ hash_value = hash_obj.hexdigest()
+ log.debug('md5_hash() returning "%s"' % hash_value)
+ return hash_value
+
+
+def qmd5_hash(salt, data):
+ """
+ Returns the hashed output of MD5Sum on salt, data
+ using PyQt4.QCryptographicHash.
+
+ :param salt: Initial salt
+ :param data: Data to hash
+ :returns: str
+ """
+ log.debug('qmd5_hash(salt="%s"' % salt)
+ hash_obj = QHash(QHash.Md5)
+ hash_obj.addData(salt)
+ hash_obj.addData(data)
+ hash_value = hash_obj.result().toHex()
+ log.debug('qmd5_hash() returning "%s"' % hash_value)
+ return decode(hash_value.data(), 'ascii')
+
+
from .openlpmixin import OpenLPMixin
from .registry import Registry
from .registrymixin import RegistryMixin
=== 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-10-28 21:04:17 +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-10-14 07:52:59 +0000
+++ openlp/core/common/settings.py 2014-10-28 21:04:17 +0000
@@ -276,6 +276,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')],
@@ -296,7 +297,15 @@
'user interface/main window splitter geometry': QtCore.QByteArray(),
'user interface/main window state': QtCore.QByteArray(),
'user interface/preview panel': True,
- 'user interface/preview splitter geometry': QtCore.QByteArray()
+ 'user interface/preview splitter geometry': QtCore.QByteArray(),
+ 'projector/db type': 'sqlite',
+ 'projector/enable': True,
+ 'projector/connect on start': False,
+ 'projector/last directory import': '',
+ 'projector/last directory export': '',
+ 'projector/poll time': 20, # PJLink timeout is 30 seconds
+ 'projector/socket timeout': 5, # 5 second socket timeout
+ 'projector/source dialog type': 0 # Source select dialog box type
}
__file_path__ = ''
__obsolete_settings__ = [
=== 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-10-28 21:04:17 +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', 'Singular')
+ self.Manufacturers = translate('OpenLP.Ui', 'Manufacturers', 'Plural')
+ self.Model = translate('OpenLP.Ui', 'Model', 'Singular')
+ self.Models = translate('OpenLP.Ui', 'Models', 'Plural')
self.Minutes = translate('OpenLP.Ui', 'm', 'The abbreviated unit for minutes')
self.Middle = translate('OpenLP.Ui', 'Middle')
self.New = translate('OpenLP.Ui', 'New')
@@ -118,6 +122,8 @@
self.PlaySlidesToEnd = translate('OpenLP.Ui', 'Play Slides to End')
self.Preview = translate('OpenLP.Ui', 'Preview')
self.PrintService = translate('OpenLP.Ui', 'Print Service')
+ self.Projector = translate('OpenLP.Ui', 'Projector', 'Singular')
+ self.Projectors = translate('OpenLP.Ui', 'Projectors', 'Plural')
self.ReplaceBG = translate('OpenLP.Ui', 'Replace Background')
self.ReplaceLiveBG = translate('OpenLP.Ui', 'Replace live background.')
self.ResetBG = translate('OpenLP.Ui', 'Reset Background')
=== modified file 'openlp/core/lib/__init__.py'
--- openlp/core/lib/__init__.py 2014-07-11 11:35:56 +0000
+++ openlp/core/lib/__init__.py 2014-10-28 21:04:17 +0000
@@ -334,3 +334,6 @@
from .imagemanager import ImageManager
from .renderer import Renderer
from .mediamanageritem import MediaManagerItem
+from .projector.db import ProjectorDB, Projector
+from .projector.pjlink1 import PJLink1
+from .projector.constants import PJLINK_PORT, ERROR_MSG, ERROR_STRING
=== 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-10-28 21:04:17 +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.bind = engine
+ metadata = None
session = scoped_session(sessionmaker(autoflush=auto_flush, autocommit=auto_commit, bind=engine))
return session, metadata
+def init_url(plugin_name, db_file_name=None):
+ """
+ Return the database URL.
+
+ :param plugin_name: The name of the plugin for the database creation.
+ :param db_file_name: The database file name. Defaults to None resulting in the plugin_name being used.
+ """
+ settings = Settings()
+ settings.beginGroup(plugin_name)
+ db_url = ''
+ db_type = settings.value('db type')
+ if db_type == 'sqlite':
+ if db_file_name is None:
+ db_url = 'sqlite:///%s/%s.sqlite' % (AppLocation.get_section_data_path(plugin_name), plugin_name)
+ else:
+ db_url = 'sqlite:///%s/%s' % (AppLocation.get_section_data_path(plugin_name), db_file_name)
+ else:
+ db_url = '%s://%s:%s@%s/%s' % (db_type, urlquote(settings.value('db username')),
+ urlquote(settings.value('db password')),
+ urlquote(settings.value('db hostname')),
+ urlquote(settings.value('db database')))
+ if db_type == 'mysql':
+ db_encoding = settings.value('db encoding')
+ db_url += '?charset=%s' % urlquote(db_encoding)
+ settings.endGroup()
+ return db_url
+
+
def get_upgrade_op(session):
"""
Create a migration context and an operations object for performing upgrades.
@@ -159,7 +192,7 @@
"""
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 directory 'openlp/core/lib/projector'
=== added file 'openlp/core/lib/projector/__init__.py'
--- openlp/core/lib/projector/__init__.py 1970-01-01 00:00:00 +0000
+++ openlp/core/lib/projector/__init__.py 2014-10-28 21:04:17 +0000
@@ -0,0 +1,41 @@
+# -*- coding: utf-8 -*-
+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
+
+###############################################################################
+# OpenLP - Open Source Lyrics Projection #
+# --------------------------------------------------------------------------- #
+# Copyright (c) 2008-2014 Raoul Snyman #
+# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan #
+# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
+# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
+# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
+# Christian Richter, Philip Ridout, Ken Roberts, Simon Scudder, #
+# Jeffrey Smith, Maikel Stuivenberg, Martin Thompson, Jon Tibble, #
+# Dave Warnock, Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
+# --------------------------------------------------------------------------- #
+# This program is free software; you can redistribute it and/or modify it #
+# under the terms of the GNU General Public License as published by the Free #
+# Software Foundation; version 2 of the License. #
+# #
+# This program is distributed in the hope that it will be useful, but WITHOUT #
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
+# more details. #
+# #
+# You should have received a copy of the GNU General Public License along #
+# with this program; if not, write to the Free Software Foundation, Inc., 59 #
+# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
+###############################################################################
+"""
+ :mod:`openlp.core.ui.projector`
+
+ Initialization for the openlp.core.ui.projector modules.
+"""
+
+
+class DialogSourceStyle(object):
+ """
+ An enumeration for projector dialog box type.
+ """
+ Tabbed = 0
+ Single = 1
=== added file 'openlp/core/lib/projector/constants.py'
--- openlp/core/lib/projector/constants.py 1970-01-01 00:00:00 +0000
+++ openlp/core/lib/projector/constants.py 2014-10-28 21:04:17 +0000
@@ -0,0 +1,366 @@
+# -*- coding: utf-8 -*-
+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
+
+###############################################################################
+# OpenLP - Open Source Lyrics Projection #
+# --------------------------------------------------------------------------- #
+# Copyright (c) 2008-2014 Raoul Snyman #
+# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan #
+# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
+# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
+# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
+# Christian Richter, Philip Ridout, Ken Roberts, Simon Scudder, #
+# Jeffrey Smith, Maikel Stuivenberg, Martin Thompson, Jon Tibble, #
+# Dave Warnock, Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
+# --------------------------------------------------------------------------- #
+# This program is free software; you can redistribute it and/or modify it #
+# under the terms of the GNU General Public License as published by the Free #
+# Software Foundation; version 2 of the License. #
+# #
+# This program is distributed in the hope that it will be useful, but WITHOUT #
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
+# more details. #
+# #
+# You should have received a copy of the GNU General Public License along #
+# with this program; if not, write to the Free Software Foundation, Inc., 59 #
+# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
+###############################################################################
+"""
+ :mod:`openlp.core.lib.projector.constants` module
+
+ Provides the constants used for projector errors/status/defaults
+"""
+
+import logging
+log = logging.getLogger(__name__)
+log.debug('projector_constants loaded')
+
+from openlp.core.common import translate
+
+
+__all__ = ['S_OK', 'E_GENERAL', 'E_NOT_CONNECTED', 'E_FAN', 'E_LAMP', 'E_TEMP',
+ 'E_COVER', 'E_FILTER', 'E_AUTHENTICATION', 'E_NO_AUTHENTICATION',
+ 'E_UNDEFINED', 'E_PARAMETER', 'E_UNAVAILABLE', 'E_PROJECTOR',
+ 'E_INVALID_DATA', 'E_WARN', 'E_ERROR', 'E_CLASS', 'E_PREFIX',
+ 'E_CONNECTION_REFUSED', 'E_REMOTE_HOST_CLOSED_CONNECTION', 'E_HOST_NOT_FOUND',
+ 'E_SOCKET_ACCESS', 'E_SOCKET_RESOURCE', 'E_SOCKET_TIMEOUT', 'E_DATAGRAM_TOO_LARGE',
+ 'E_NETWORK', 'E_ADDRESS_IN_USE', 'E_SOCKET_ADDRESS_NOT_AVAILABLE',
+ 'E_UNSUPPORTED_SOCKET_OPERATION', 'E_PROXY_AUTHENTICATION_REQUIRED',
+ 'E_SLS_HANDSHAKE_FAILED', 'E_UNFINISHED_SOCKET_OPERATION', 'E_PROXY_CONNECTION_REFUSED',
+ 'E_PROXY_CONNECTION_CLOSED', 'E_PROXY_CONNECTION_TIMEOUT', 'E_PROXY_NOT_FOUND',
+ 'E_PROXY_PROTOCOL', 'E_UNKNOWN_SOCKET_ERROR',
+ 'S_NOT_CONNECTED', 'S_CONNECTING', 'S_CONNECTED',
+ 'S_STATUS', 'S_OFF', 'S_INITIALIZE', 'S_STANDBY', 'S_WARMUP', 'S_ON', 'S_COOLDOWN',
+ 'S_INFO', 'S_NETWORK_SENDING', 'S_NETWORK_RECEIVED',
+ 'ERROR_STRING', 'CR', 'LF', 'PJLINK_ERST_STATUS', 'PJLINK_POWR_STATUS',
+ 'PJLINK_PORT', 'PJLINK_MAX_PACKET', 'TIMEOUT', 'ERROR_MSG', 'PJLINK_ERRORS',
+ 'STATUS_STRING', 'PJLINK_VALID_CMD', 'CONNECTION_ERRORS']
+
+# Set common constants.
+CR = chr(0x0D) # \r
+LF = chr(0x0A) # \n
+PJLINK_PORT = 4352
+TIMEOUT = 30.0
+PJLINK_MAX_PACKET = 136
+PJLINK_VALID_CMD = {'1': ['PJLINK', # Initial connection
+ 'POWR', # Power option
+ 'INPT', # Video sources option
+ 'AVMT', # Shutter option
+ 'ERST', # Error status option
+ 'LAMP', # Lamp(s) query (Includes fans)
+ 'INST', # Input sources available query
+ 'NAME', # Projector name query
+ 'INF1', # Manufacturer name query
+ 'INF2', # Product name query
+ 'INFO', # Other information query
+ 'CLSS' # PJLink class support query
+ ]}
+
+# Error and status codes
+S_OK = E_OK = 0 # E_OK included since I sometimes forget
+# Error codes. Start at 200 so we don't duplicate system error codes.
+E_GENERAL = 200 # Unknown error
+E_NOT_CONNECTED = 201
+E_FAN = 202
+E_LAMP = 203
+E_TEMP = 204
+E_COVER = 205
+E_FILTER = 206
+E_NO_AUTHENTICATION = 207 # PIN set and no authentication set on projector
+E_UNDEFINED = 208 # ERR1
+E_PARAMETER = 209 # ERR2
+E_UNAVAILABLE = 210 # ERR3
+E_PROJECTOR = 211 # ERR4
+E_INVALID_DATA = 212
+E_WARN = 213
+E_ERROR = 214
+E_AUTHENTICATION = 215 # ERRA
+E_CLASS = 216
+E_PREFIX = 217
+
+# Remap Qt socket error codes to projector error codes
+E_CONNECTION_REFUSED = 230
+E_REMOTE_HOST_CLOSED_CONNECTION = 231
+E_HOST_NOT_FOUND = 232
+E_SOCKET_ACCESS = 233
+E_SOCKET_RESOURCE = 234
+E_SOCKET_TIMEOUT = 235
+E_DATAGRAM_TOO_LARGE = 236
+E_NETWORK = 237
+E_ADDRESS_IN_USE = 238
+E_SOCKET_ADDRESS_NOT_AVAILABLE = 239
+E_UNSUPPORTED_SOCKET_OPERATION = 240
+E_PROXY_AUTHENTICATION_REQUIRED = 241
+E_SLS_HANDSHAKE_FAILED = 242
+E_UNFINISHED_SOCKET_OPERATION = 243
+E_PROXY_CONNECTION_REFUSED = 244
+E_PROXY_CONNECTION_CLOSED = 245
+E_PROXY_CONNECTION_TIMEOUT = 246
+E_PROXY_NOT_FOUND = 247
+E_PROXY_PROTOCOL = 248
+E_UNKNOWN_SOCKET_ERROR = -1
+
+# Status codes start at 300
+S_NOT_CONNECTED = 300
+S_CONNECTING = 301
+S_CONNECTED = 302
+S_INITIALIZE = 303
+S_STATUS = 304
+S_OFF = 305
+S_STANDBY = 306
+S_WARMUP = 307
+S_ON = 308
+S_COOLDOWN = 309
+S_INFO = 310
+
+# Information that does not affect status
+S_NETWORK_SENDING = 400
+S_NETWORK_RECEIVED = 401
+
+CONNECTION_ERRORS = {E_NOT_CONNECTED, E_NO_AUTHENTICATION, E_AUTHENTICATION, E_CLASS,
+ E_PREFIX, E_CONNECTION_REFUSED, E_REMOTE_HOST_CLOSED_CONNECTION,
+ E_HOST_NOT_FOUND, E_SOCKET_ACCESS, E_SOCKET_RESOURCE, E_SOCKET_TIMEOUT,
+ E_DATAGRAM_TOO_LARGE, E_NETWORK, E_ADDRESS_IN_USE, E_SOCKET_ADDRESS_NOT_AVAILABLE,
+ E_UNSUPPORTED_SOCKET_OPERATION, E_PROXY_AUTHENTICATION_REQUIRED,
+ E_SLS_HANDSHAKE_FAILED, E_UNFINISHED_SOCKET_OPERATION, E_PROXY_CONNECTION_REFUSED,
+ E_PROXY_CONNECTION_CLOSED, E_PROXY_CONNECTION_TIMEOUT, E_PROXY_NOT_FOUND,
+ E_PROXY_PROTOCOL, E_UNKNOWN_SOCKET_ERROR
+ }
+
+PJLINK_ERRORS = {'ERRA': E_AUTHENTICATION, # Authentication error
+ 'ERR1': E_UNDEFINED, # Undefined command error
+ 'ERR2': E_PARAMETER, # Invalid parameter error
+ 'ERR3': E_UNAVAILABLE, # Projector busy
+ 'ERR4': E_PROJECTOR, # Projector or display failure
+ E_AUTHENTICATION: translate('OpenLP.ProjectorConstants', 'ERRA'),
+ E_UNDEFINED: translate('OpenLP.ProjectorConstants', 'ERR1'),
+ E_PARAMETER: translate('OpenLP.ProjectorConstants', 'ERR2'),
+ E_UNAVAILABLE: translate('OpenLP.ProjectorConstants', 'ERR3'),
+ E_PROJECTOR: translate('OpenLP.ProjectorConstants', 'ERR4')}
+
+# Map error/status codes to string
+ERROR_STRING = {0: translate('OpenLP.ProjectorConstants', 'S_OK'),
+ E_GENERAL: translate('OpenLP.ProjectorConstants', 'E_GENERAL'),
+ E_NOT_CONNECTED: translate('OpenLP.ProjectorConstants', 'E_NOT_CONNECTED'),
+ E_FAN: translate('OpenLP.ProjectorConstants', 'E_FAN'),
+ E_LAMP: translate('OpenLP.ProjectorConstants', 'E_LAMP'),
+ E_TEMP: translate('OpenLP.ProjectorConstants', 'E_TEMP'),
+ E_COVER: translate('OpenLP.ProjectorConstants', 'E_COVER'),
+ E_FILTER: translate('OpenLP.ProjectorConstants', 'E_FILTER'),
+ E_AUTHENTICATION: translate('OpenLP.ProjectorConstants', 'E_AUTHENTICATION'),
+ E_NO_AUTHENTICATION: translate('OpenLP.ProjectorConstants', 'E_NO_AUTHENTICATION'),
+ E_UNDEFINED: translate('OpenLP.ProjectorConstants', 'E_UNDEFINED'),
+ E_PARAMETER: translate('OpenLP.ProjectorConstants', 'E_PARAMETER'),
+ E_UNAVAILABLE: translate('OpenLP.ProjectorConstants', 'E_UNAVAILABLE'),
+ E_PROJECTOR: translate('OpenLP.ProjectorConstants', 'E_PROJECTOR'),
+ E_INVALID_DATA: translate('OpenLP.ProjectorConstants', 'E_INVALID_DATA'),
+ E_WARN: translate('OpenLP.ProjectorConstants', 'E_WARN'),
+ E_ERROR: translate('OpenLP.ProjectorConstants', 'E_ERROR'),
+ E_CLASS: translate('OpenLP.ProjectorConstants', 'E_CLASS'),
+ E_PREFIX: translate('OpenLP.ProjectorConstants', 'E_PREFIX'), # Last projector error
+ E_CONNECTION_REFUSED: translate('OpenLP.ProjectorConstants',
+ 'E_CONNECTION_REFUSED'), # First QtSocket error
+ E_REMOTE_HOST_CLOSED_CONNECTION: translate('OpenLP.ProjectorConstants',
+ 'E_REMOTE_HOST_CLOSED_CONNECTION'),
+ E_HOST_NOT_FOUND: translate('OpenLP.ProjectorConstants', 'E_HOST_NOT_FOUND'),
+ E_SOCKET_ACCESS: translate('OpenLP.ProjectorConstants', 'E_SOCKET_ACCESS'),
+ E_SOCKET_RESOURCE: translate('OpenLP.ProjectorConstants', 'E_SOCKET_RESOURCE'),
+ E_SOCKET_TIMEOUT: translate('OpenLP.ProjectorConstants', 'E_SOCKET_TIMEOUT'),
+ E_DATAGRAM_TOO_LARGE: translate('OpenLP.ProjectorConstants', 'E_DATAGRAM_TOO_LARGE'),
+ E_NETWORK: translate('OpenLP.ProjectorConstants', 'E_NETWORK'),
+ E_ADDRESS_IN_USE: translate('OpenLP.ProjectorConstants', 'E_ADDRESS_IN_USE'),
+ E_SOCKET_ADDRESS_NOT_AVAILABLE: translate('OpenLP.ProjectorConstants',
+ 'E_SOCKET_ADDRESS_NOT_AVAILABLE'),
+ E_UNSUPPORTED_SOCKET_OPERATION: translate('OpenLP.ProjectorConstants',
+ 'E_UNSUPPORTED_SOCKET_OPERATION'),
+ E_PROXY_AUTHENTICATION_REQUIRED: translate('OpenLP.ProjectorConstants',
+ 'E_PROXY_AUTHENTICATION_REQUIRED'),
+ E_SLS_HANDSHAKE_FAILED: translate('OpenLP.ProjectorConstants', 'E_SLS_HANDSHAKE_FAILED'),
+ E_UNFINISHED_SOCKET_OPERATION: translate('OpenLP.ProjectorConstants',
+ 'E_UNFINISHED_SOCKET_OPERATION'),
+ E_PROXY_CONNECTION_REFUSED: translate('OpenLP.ProjectorConstants', 'E_PROXY_CONNECTION_REFUSED'),
+ E_PROXY_CONNECTION_CLOSED: translate('OpenLP.ProjectorConstants', 'E_PROXY_CONNECTION_CLOSED'),
+ E_PROXY_CONNECTION_TIMEOUT: translate('OpenLP.ProjectorConstants', 'E_PROXY_CONNECTION_TIMEOUT'),
+ E_PROXY_NOT_FOUND: translate('OpenLP.ProjectorConstants', 'E_PROXY_NOT_FOUND'),
+ E_PROXY_PROTOCOL: translate('OpenLP.ProjectorConstants', 'E_PROXY_PROTOCOL'),
+ E_UNKNOWN_SOCKET_ERROR: translate('OpenLP.ProjectorConstants', 'E_UNKNOWN_SOCKET_ERROR')}
+
+STATUS_STRING = {S_NOT_CONNECTED: translate('OpenLP.ProjectorConstants', 'S_NOT_CONNECTED'),
+ S_CONNECTING: translate('OpenLP.ProjectorConstants', 'S_CONNECTING'),
+ S_CONNECTED: translate('OpenLP.ProjectorConstants', 'S_CONNECTED'),
+ S_STATUS: translate('OpenLP.ProjectorConstants', 'S_STATUS'),
+ S_OFF: translate('OpenLP.ProjectorConstants', 'S_OFF'),
+ S_INITIALIZE: translate('OpenLP.ProjectorConstants', 'S_INITIALIZE'),
+ S_STANDBY: translate('OpenLP.ProjectorConstants', 'S_STANDBY'),
+ S_WARMUP: translate('OpenLP.ProjectorConstants', 'S_WARMUP'),
+ S_ON: translate('OpenLP.ProjectorConstants', 'S_ON'),
+ S_COOLDOWN: translate('OpenLP.ProjectorConstants', 'S_COOLDOWN'),
+ S_INFO: translate('OpenLP.ProjectorConstants', 'S_INFO'),
+ S_NETWORK_SENDING: translate('OpenLP.ProjectorConstants', 'S_NETWORK_SENDING'),
+ S_NETWORK_RECEIVED: translate('OpenLP.ProjectorConstants', 'S_NETWORK_RECEIVED')}
+
+# Map error/status codes to message strings
+ERROR_MSG = {E_OK: translate('OpenLP.ProjectorConstants', 'OK'), # E_OK | S_OK
+ E_GENERAL: translate('OpenLP.ProjectorConstants', 'General projector error'),
+ E_NOT_CONNECTED: translate('OpenLP.ProjectorConstants', 'Not connected error'),
+ E_LAMP: translate('OpenLP.ProjectorConstants', 'Lamp error'),
+ E_FAN: translate('OpenLP.ProjectorConstants', 'Fan error'),
+ E_TEMP: translate('OpenLP.ProjectorConstants', 'High temperature detected'),
+ E_COVER: translate('OpenLP.ProjectorConstants', 'Cover open detected'),
+ E_FILTER: translate('OpenLP.ProjectorConstants', 'Check filter'),
+ E_AUTHENTICATION: translate('OpenLP.ProjectorConstants', 'Authentication Error'),
+ E_UNDEFINED: translate('OpenLP.ProjectorConstants', 'Undefined Command'),
+ E_PARAMETER: translate('OpenLP.ProjectorConstants', 'Invalid Parameter'),
+ E_UNAVAILABLE: translate('OpenLP.ProjectorConstants', 'Projector Busy'),
+ E_PROJECTOR: translate('OpenLP.ProjectorConstants', 'Projector/Display Error'),
+ E_INVALID_DATA: translate('OpenLP.ProjectorConstants', 'Invalid packet received'),
+ E_WARN: translate('OpenLP.ProjectorConstants', 'Warning condition detected'),
+ E_ERROR: translate('OpenLP.ProjectorConstants', 'Error condition detected'),
+ E_CLASS: translate('OpenLP.ProjectorConstants', 'PJLink class not supported'),
+ E_PREFIX: translate('OpenLP.ProjectorConstants', 'Invalid prefix character'),
+ E_CONNECTION_REFUSED: translate('OpenLP.ProjectorConstants',
+ 'The connection was refused by the peer (or timed out)'),
+ E_REMOTE_HOST_CLOSED_CONNECTION: translate('OpenLP.ProjectorConstants',
+ 'The remote host closed the connection'),
+ E_HOST_NOT_FOUND: translate('OpenLP.ProjectorConstants', 'The host address was not found'),
+ E_SOCKET_ACCESS: translate('OpenLP.ProjectorConstants',
+ 'The socket operation failed because the application '
+ 'lacked the required privileges'),
+ E_SOCKET_RESOURCE: translate('OpenLP.ProjectorConstants',
+ 'The local system ran out of resources (e.g., too many sockets)'),
+ E_SOCKET_TIMEOUT: translate('OpenLP.ProjectorConstants',
+ 'The socket operation timed out'),
+ E_DATAGRAM_TOO_LARGE: translate('OpenLP.ProjectorConstants',
+ 'The datagram was larger than the operating system\'s limit'),
+ E_NETWORK: translate('OpenLP.ProjectorConstants',
+ 'An error occurred with the network (Possibly someone pulled the plug?)'),
+ E_ADDRESS_IN_USE: translate('OpenLP.ProjectorConstants',
+ 'The address specified with socket.bind() '
+ 'is already in use and was set to be exclusive'),
+ E_SOCKET_ADDRESS_NOT_AVAILABLE: translate('OpenLP.ProjectorConstants',
+ 'The address specified to socket.bind() '
+ 'does not belong to the host'),
+ E_UNSUPPORTED_SOCKET_OPERATION: translate('OpenLP.ProjectorConstants',
+ 'The requested socket operation is not supported by the local '
+ 'operating system (e.g., lack of IPv6 support)'),
+ E_PROXY_AUTHENTICATION_REQUIRED: translate('OpenLP.ProjectorConstants',
+ 'The socket is using a proxy, '
+ 'and the proxy requires authentication'),
+ E_SLS_HANDSHAKE_FAILED: translate('OpenLP.ProjectorConstants',
+ 'The SSL/TLS handshake failed'),
+ E_UNFINISHED_SOCKET_OPERATION: translate('OpenLP.ProjectorConstants',
+ 'The last operation attempted has not finished yet '
+ '(still in progress in the background)'),
+ E_PROXY_CONNECTION_REFUSED: translate('OpenLP.ProjectorConstants',
+ 'Could not contact the proxy server because the connection '
+ 'to that server was denied'),
+ E_PROXY_CONNECTION_CLOSED: translate('OpenLP.ProjectorConstants',
+ 'The connection to the proxy server was closed unexpectedly '
+ '(before the connection to the final peer was established)'),
+ E_PROXY_CONNECTION_TIMEOUT: translate('OpenLP.ProjectorConstants',
+ 'The connection to the proxy server timed out or the proxy '
+ 'server stopped responding in the authentication phase.'),
+ E_PROXY_NOT_FOUND: translate('OpenLP.ProjectorConstants',
+ 'The proxy address set with setProxy() was not found'),
+ E_PROXY_PROTOCOL: translate('OpenLP.ProjectorConstants',
+ 'The connection negotiation with the proxy server because the response '
+ 'from the proxy server could not be understood'),
+ E_UNKNOWN_SOCKET_ERROR: translate('OpenLP.ProjectorConstants', 'An unidentified error occurred'),
+ S_NOT_CONNECTED: translate('OpenLP.ProjectorConstants', 'Not connected'),
+ S_CONNECTING: translate('OpenLP.ProjectorConstants', 'Connecting'),
+ S_CONNECTED: translate('OpenLP.ProjectorConstants', 'Connected'),
+ S_STATUS: translate('OpenLP.ProjectorConstants', 'Getting status'),
+ S_OFF: translate('OpenLP.ProjectorConstants', 'Off'),
+ S_INITIALIZE: translate('OpenLP.ProjectorConstants', 'Initialize in progress'),
+ S_STANDBY: translate('OpenLP.ProjectorConstants', 'Power in standby'),
+ S_WARMUP: translate('OpenLP.ProjectorConstants', 'Warmup in progress'),
+ S_ON: translate('OpenLP.ProjectorConstants', 'Power is on'),
+ S_COOLDOWN: translate('OpenLP.ProjectorConstants', 'Cooldown in progress'),
+ S_INFO: translate('OpenLP.ProjectorConstants', 'Projector Information available'),
+ S_NETWORK_SENDING: translate('OpenLP.ProjectorConstants', 'Sending data'),
+ S_NETWORK_RECEIVED: translate('OpenLP.ProjectorConstants', 'Received data')}
+
+# Map for ERST return codes to string
+PJLINK_ERST_STATUS = {'0': ERROR_STRING[E_OK],
+ '1': ERROR_STRING[E_WARN],
+ '2': ERROR_STRING[E_ERROR]}
+
+# Map for POWR return codes to status code
+PJLINK_POWR_STATUS = {'0': S_STANDBY,
+ '1': S_ON,
+ '2': S_COOLDOWN,
+ '3': S_WARMUP}
+
+PJLINK_DEFAULT_SOURCES = {'1': translate('OpenLP.DB', 'RGB'),
+ '2': translate('OpenLP.DB', 'Video'),
+ '3': translate('OpenLP.DB', 'Digital'),
+ '4': translate('OpenLP.DB', 'Storage'),
+ '5': translate('OpenLP.DB', 'Network')}
+
+PJLINK_DEFAULT_CODES = {'11': translate('OpenLP.DB', 'RGB 1'),
+ '12': translate('OpenLP.DB', 'RGB 2'),
+ '13': translate('OpenLP.DB', 'RGB 3'),
+ '14': translate('OpenLP.DB', 'RGB 4'),
+ '15': translate('OpenLP.DB', 'RGB 5'),
+ '16': translate('OpenLP.DB', 'RGB 6'),
+ '17': translate('OpenLP.DB', 'RGB 7'),
+ '18': translate('OpenLP.DB', 'RGB 8'),
+ '19': translate('OpenLP.DB', 'RGB 9'),
+ '21': translate('OpenLP.DB', 'Video 1'),
+ '22': translate('OpenLP.DB', 'Video 2'),
+ '23': translate('OpenLP.DB', 'Video 3'),
+ '24': translate('OpenLP.DB', 'Video 4'),
+ '25': translate('OpenLP.DB', 'Video 5'),
+ '26': translate('OpenLP.DB', 'Video 6'),
+ '27': translate('OpenLP.DB', 'Video 7'),
+ '28': translate('OpenLP.DB', 'Video 8'),
+ '29': translate('OpenLP.DB', 'Video 9'),
+ '31': translate('OpenLP.DB', 'Digital 1'),
+ '32': translate('OpenLP.DB', 'Digital 2'),
+ '33': translate('OpenLP.DB', 'Digital 3'),
+ '34': translate('OpenLP.DB', 'Digital 4'),
+ '35': translate('OpenLP.DB', 'Digital 5'),
+ '36': translate('OpenLP.DB', 'Digital 6'),
+ '37': translate('OpenLP.DB', 'Digital 7'),
+ '38': translate('OpenLP.DB', 'Digital 8'),
+ '39': translate('OpenLP.DB', 'Digital 9'),
+ '41': translate('OpenLP.DB', 'Storage 1'),
+ '42': translate('OpenLP.DB', 'Storage 2'),
+ '43': translate('OpenLP.DB', 'Storage 3'),
+ '44': translate('OpenLP.DB', 'Storage 4'),
+ '45': translate('OpenLP.DB', 'Storage 5'),
+ '46': translate('OpenLP.DB', 'Storage 6'),
+ '47': translate('OpenLP.DB', 'Storage 7'),
+ '48': translate('OpenLP.DB', 'Storage 8'),
+ '49': translate('OpenLP.DB', 'Storage 9'),
+ '51': translate('OpenLP.DB', 'Network 1'),
+ '52': translate('OpenLP.DB', 'Network 2'),
+ '53': translate('OpenLP.DB', 'Network 3'),
+ '54': translate('OpenLP.DB', 'Network 4'),
+ '55': translate('OpenLP.DB', 'Network 5'),
+ '56': translate('OpenLP.DB', 'Network 6'),
+ '57': translate('OpenLP.DB', 'Network 7'),
+ '58': translate('OpenLP.DB', 'Network 8'),
+ '59': translate('OpenLP.DB', 'Network 9')
+ }
=== added file 'openlp/core/lib/projector/db.py'
--- openlp/core/lib/projector/db.py 1970-01-01 00:00:00 +0000
+++ openlp/core/lib/projector/db.py 2014-10-28 21:04:17 +0000
@@ -0,0 +1,436 @@
+# -*- coding: utf-8 -*-
+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
+
+###############################################################################
+# OpenLP - Open Source Lyrics Projection #
+# --------------------------------------------------------------------------- #
+# Copyright (c) 2008-2014 Raoul Snyman #
+# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan #
+# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
+# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
+# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
+# Christian Richter, Philip Ridout, Ken Roberts, Simon Scudder, #
+# Jeffrey Smith, Maikel Stuivenberg, Martin Thompson, Jon Tibble, #
+# Dave Warnock, Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
+# --------------------------------------------------------------------------- #
+# This program is free software; you can redistribute it and/or modify it #
+# under the terms of the GNU General Public License as published by the Free #
+# Software Foundation; version 2 of the License. #
+# #
+# This program is distributed in the hope that it will be useful, but WITHOUT #
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
+# more details. #
+# #
+# You should have received a copy of the GNU General Public License along #
+# with this program; if not, write to the Free Software Foundation, Inc., 59 #
+# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
+###############################################################################
+"""
+ :mod:`openlp.core.lib.projector.db` module
+
+ Provides the database functions for the Projector module.
+
+ The Manufacturer, Model, Source tables keep track of the video source
+ strings used for display of input sources. The Source table maps
+ manufacturer-defined or user-defined strings from PJLink default strings
+ to end-user readable strings; ex: PJLink code 11 would map "RGB 1"
+ default string to "RGB PC (analog)" string.
+ (Future feature).
+
+ The Projector table keeps track of entries for controlled projectors.
+"""
+
+import logging
+log = logging.getLogger(__name__)
+log.debug('projector.lib.db module loaded')
+
+from sqlalchemy import Column, ForeignKey, Integer, MetaData, String, and_
+from sqlalchemy.ext.declarative import declarative_base, declared_attr
+from sqlalchemy.orm import backref, relationship
+
+from openlp.core.lib.db import Manager, init_db, init_url
+from openlp.core.lib.projector.constants import PJLINK_DEFAULT_CODES
+
+metadata = MetaData()
+Base = declarative_base(metadata)
+
+
+class CommonBase(object):
+ """
+ Base class to automate table name and ID column.
+ """
+ @declared_attr
+ def __tablename__(cls):
+ return cls.__name__.lower()
+
+ id = Column(Integer, primary_key=True)
+
+
+class Manufacturer(CommonBase, Base):
+ """
+ Projector manufacturer table.
+
+ Manufacturer:
+ name: Column(String(30))
+ models: Relationship(Model.id)
+
+ Model table is related.
+ """
+ def __repr__(self):
+ """
+ Returns a basic representation of a Manufacturer table entry.
+ """
+ return '<Manufacturer(name="%s")>' % self.name
+
+ name = Column(String(30))
+ models = relationship('Model',
+ order_by='Model.name',
+ backref='manufacturer',
+ cascade='all, delete-orphan',
+ primaryjoin='Manufacturer.id==Model.manufacturer_id',
+ lazy='joined')
+
+
+class Model(CommonBase, Base):
+ """
+ Projector model table.
+
+ Model:
+ name: Column(String(20))
+ sources: Relationship(Source.id)
+ manufacturer_id: Foreign_key(Manufacturer.id)
+
+ Manufacturer table links here.
+ Source table is related.
+ """
+ def __repr__(self):
+ """
+ Returns a basic representation of a Model table entry.
+ """
+ return '<Model(name=%s)>' % self.name
+
+ manufacturer_id = Column(Integer, ForeignKey('manufacturer.id'))
+ name = Column(String(20))
+ sources = relationship('Source',
+ order_by='Source.pjlink_name',
+ backref='model',
+ cascade='all, delete-orphan',
+ primaryjoin='Model.id==Source.model_id',
+ lazy='joined')
+
+
+class Source(CommonBase, Base):
+ """
+ Projector video source table.
+
+ Source:
+ pjlink_name: Column(String(15))
+ pjlink_code: Column(String(2))
+ text: Column(String(30))
+ model_id: Foreign_key(Model.id)
+
+ Model table links here.
+
+ These entries map PJLink input video source codes to text strings.
+ """
+ def __repr__(self):
+ """
+ Return basic representation of Source table entry.
+ """
+ return '<Source(pjlink_name="%s", pjlink_code="%s", text="%s")>' % \
+ (self.pjlink_name, self.pjlink_code, self.text)
+ model_id = Column(Integer, ForeignKey('model.id'))
+ pjlink_name = Column(String(15))
+ pjlink_code = Column(String(2))
+ text = Column(String(30))
+
+
+class Projector(CommonBase, Base):
+ """
+ Projector table.
+
+ Projector:
+ ip: Column(String(100)) # Allow for IPv6 or FQDN
+ port: Column(String(8))
+ pin: Column(String(20)) # Allow for test strings
+ name: Column(String(20))
+ location: Column(String(30))
+ notes: Column(String(200))
+ pjlink_name: Column(String(128)) # From projector (future)
+ manufacturer: Column(String(128)) # From projector (future)
+ model: Column(String(128)) # From projector (future)
+ other: Column(String(128)) # From projector (future)
+ sources: Column(String(128)) # From projector (future)
+
+ ProjectorSource relates
+ """
+ def __repr__(self):
+ """
+ Return basic representation of Source table entry.
+ """
+ return '< Projector(id="%s", ip="%s", port="%s", pin="%s", name="%s", location="%s",' \
+ 'notes="%s", pjlink_name="%s", manufacturer="%s", model="%s", other="%s",' \
+ 'sources="%s", source_list="%s") >' % (self.id, self.ip, self.port, self.pin, self.name, self.location,
+ self.notes, self.pjlink_name, self.manufacturer, self.model,
+ self.other, self.sources, self.source_list)
+ ip = Column(String(100))
+ port = Column(String(8))
+ pin = Column(String(20))
+ name = Column(String(20))
+ location = Column(String(30))
+ notes = Column(String(200))
+ pjlink_name = Column(String(128))
+ manufacturer = Column(String(128))
+ model = Column(String(128))
+ other = Column(String(128))
+ sources = Column(String(128))
+ source_list = relationship('ProjectorSource',
+ order_by='ProjectorSource.code',
+ backref='projector',
+ cascade='all, delete-orphan',
+ primaryjoin='Projector.id==ProjectorSource.projector_id',
+ lazy='joined')
+
+
+class ProjectorSource(CommonBase, Base):
+ """
+ Projector local source table
+ This table allows mapping specific projector source input to a local
+ connection; i.e., '11': 'DVD Player'
+
+ Projector Source:
+ projector_id: Foreign_key(Column(Projector.id))
+ code: Column(String(3)) # PJLink source code
+ text: Column(String(20)) # Text to display
+
+ Projector table links here
+ """
+ def __repr__(self):
+ """
+ Return basic representation of Source table entry.
+ """
+ return '<ProjectorSource(id="%s", code="%s", text="%s", projector_id="%s")>' % (self.id,
+ self.code,
+ self.text,
+ self.projector_id)
+ code = Column(String(3))
+ text = Column(String(20))
+ projector_id = Column(Integer, ForeignKey('projector.id'))
+
+
+class ProjectorDB(Manager):
+ """
+ Class to access the projector database.
+ """
+ def __init__(self, *args, **kwargs):
+ log.debug('ProjectorDB().__init__(args="%s", kwargs="%s")' % (args, kwargs))
+ super().__init__(plugin_name='projector',
+ init_schema=self.init_schema)
+ log.debug('ProjectorDB() Initialized using db url %s' % self.db_url)
+
+ def init_schema(*args, **kwargs):
+ """
+ Setup the projector database and initialize the schema.
+
+ Declarative uses table classes to define schema.
+ """
+ url = init_url('projector')
+ session, metadata = init_db(url, base=Base)
+ Base.metadata.create_all(checkfirst=True)
+ return session
+
+ def get_projector_by_id(self, dbid):
+ """
+ Locate a DB record by record ID.
+
+ :param dbid: DB record id
+ :returns: Projector() instance
+ """
+ log.debug('get_projector_by_id(id="%s")' % dbid)
+ projector = self.get_object_filtered(Projector, Projector.id == dbid)
+ if projector is None:
+ # Not found
+ log.warn('get_projector_by_id() did not find %s' % id)
+ return None
+ log.debug('get_projectorby_id() returning 1 entry for "%s" id="%s"' % (dbid, projector.id))
+ return projector
+
+ def get_projector_all(self):
+ """
+ Retrieve all projector entries.
+
+ :returns: List with Projector() instances used in Manager() QListWidget.
+ """
+ log.debug('get_all() called')
+ return_list = []
+ new_list = self.get_all_objects(Projector)
+ if new_list is None or new_list.count == 0:
+ return return_list
+ for new_projector in new_list:
+ return_list.append(new_projector)
+ log.debug('get_all() returning %s item(s)' % len(return_list))
+ return return_list
+
+ def get_projector_by_ip(self, ip):
+ """
+ Locate a projector by host IP/Name.
+
+ :param ip: Host IP/Name
+ :returns: Projector() instance
+ """
+ log.debug('get_projector_by_ip(ip="%s")' % ip)
+ projector = self.get_object_filtered(Projector, Projector.ip == ip)
+ if projector is None:
+ # Not found
+ log.warn('get_projector_by_ip() did not find %s' % ip)
+ return None
+ log.debug('get_projectorby_ip() returning 1 entry for "%s" id="%s"' % (ip, projector.id))
+ return projector
+
+ def get_projector_by_name(self, name):
+ """
+ Locate a projector by name field
+
+ :param name: Name of projector
+ :returns: Projector() instance
+ """
+ log.debug('get_projector_by_name(name="%s")' % name)
+ projector = self.get_object_filtered(Projector, Projector.name == name)
+ if projector is None:
+ # Not found
+ log.warn('get_projector_by_name() did not find "%s"' % name)
+ return None
+ log.debug('get_projector_by_name() returning one entry for "%s" id="%s"' % (name, projector.id))
+ return projector
+
+ def add_projector(self, projector):
+ """
+ Add a new projector entry
+
+ :param projector: Projector() instance to add
+ :returns: bool
+ True if entry added
+ False if entry already in DB or db error
+ """
+ old_projector = self.get_object_filtered(Projector, Projector.ip == projector.ip)
+ if old_projector is not None:
+ log.warn('add_new() skipping entry ip="%s" (Already saved)' % old_projector.ip)
+ return False
+ log.debug('add_new() saving new entry')
+ log.debug('ip="%s", name="%s", location="%s"' % (projector.ip,
+ projector.name,
+ projector.location))
+ log.debug('notes="%s"' % projector.notes)
+ return self.save_object(projector)
+
+ def update_projector(self, projector=None):
+ """
+ Update projector entry
+
+ :param projector: Projector() instance with new information
+ :returns: bool
+ True if DB record updated
+ False if entry not in DB or DB error
+ """
+ if projector is None:
+ log.error('No Projector() instance to update - cancelled')
+ return False
+ old_projector = self.get_object_filtered(Projector, Projector.id == projector.id)
+ if old_projector is None:
+ log.error('Edit called on projector instance not in database - cancelled')
+ return False
+ log.debug('(%s) Updating projector with dbid=%s' % (projector.ip, projector.id))
+ old_projector.ip = projector.ip
+ old_projector.name = projector.name
+ old_projector.location = projector.location
+ old_projector.pin = projector.pin
+ old_projector.port = projector.port
+ old_projector.pjlink_name = projector.pjlink_name
+ old_projector.manufacturer = projector.manufacturer
+ old_projector.model = projector.model
+ old_projector.other = projector.other
+ old_projector.sources = projector.sources
+ return self.save_object(old_projector)
+
+ def delete_projector(self, projector):
+ """
+ Delete an entry by record id
+
+ :param projector: Projector() instance to delete
+ :returns: bool
+ True if record deleted
+ False if DB error
+ """
+ deleted = self.delete_object(Projector, projector.id)
+ if deleted:
+ log.debug('delete_by_id() Removed entry id="%s"' % projector.id)
+ else:
+ log.error('delete_by_id() Entry id="%s" not deleted for some reason' % projector.id)
+ return deleted
+
+ def get_source_list(self, projector):
+ """
+ Retrieves the source inputs pjlink code-to-text if available based on
+ manufacturer and model.
+ If not available, then returns the PJLink code to default text.
+
+ :param projector: Projector instance
+ :returns: dict
+ key: (str) PJLink code for source
+ value: (str) From ProjectorSource, Sources tables or PJLink default code list
+ """
+ source_dict = {}
+ # Get default list first
+ for key in projector.source_available:
+ item = self.get_object_filtered(ProjectorSource,
+ and_(ProjectorSource.code == key,
+ ProjectorSource.projector_id == projector.dbid))
+ if item is None:
+ source_dict[key] = PJLINK_DEFAULT_CODES[key]
+ else:
+ source_dict[key] = item.text
+ return source_dict
+
+ def get_source_by_id(self, source):
+ """
+ Retrieves the ProjectorSource by ProjectorSource.id
+
+ :param source: ProjectorSource id
+ :returns: ProjetorSource instance or None
+ """
+ source_entry = self.get_object_filtered(ProjetorSource, ProjectorSource.id == source)
+ if source_entry is None:
+ # Not found
+ log.warn('get_source_by_id() did not find "%s"' % source)
+ return None
+ log.debug('get_source_by_id() returning one entry for "%s""' % (source))
+ return source_entry
+
+ def get_source_by_code(self, code, projector_id):
+ """
+ Retrieves the ProjectorSource by ProjectorSource.id
+
+ :param source: PJLink ID
+ :param projector_id: Projector.id
+ :returns: ProjetorSource instance or None
+ """
+ source_entry = self.get_object_filtered(ProjectorSource,
+ and_(ProjectorSource.code == code,
+ ProjectorSource.projector_id == projector_id))
+ if source_entry is None:
+ # Not found
+ log.warn('get_source_by_id() did not find code="%s" projector_id="%s"' % (code, projector_id))
+ return None
+ log.debug('get_source_by_id() returning one entry for code="%s" projector_id="%s"' % (code, projector_id))
+ return source_entry
+
+ def add_source(self, source):
+ """
+ Add a new ProjectorSource record
+
+ :param source: ProjectorSource() instance to add
+ """
+ log.debug('Saving ProjectorSource(projector_id="%s" code="%s" text="%s")' % (source.projector_id,
+ source.code, source.text))
+ return self.save_object(source)
=== added file 'openlp/core/lib/projector/pjlink1.py'
--- openlp/core/lib/projector/pjlink1.py 1970-01-01 00:00:00 +0000
+++ openlp/core/lib/projector/pjlink1.py 2014-10-28 21:04:17 +0000
@@ -0,0 +1,913 @@
+# -*- coding: utf-8 -*-
+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
+
+###############################################################################
+# OpenLP - Open Source Lyrics Projection #
+# --------------------------------------------------------------------------- #
+# Copyright (c) 2008-2014 Raoul Snyman #
+# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan #
+# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
+# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
+# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
+# Christian Richter, Philip Ridout, Ken Roberts, Simon Scudder, #
+# Jeffrey Smith, Maikel Stuivenberg, Martin Thompson, Jon Tibble, #
+# Dave Warnock, Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
+# --------------------------------------------------------------------------- #
+# This program is free software; you can redistribute it and/or modify it #
+# under the terms of the GNU General Public License as published by the Free #
+# Software Foundation; version 2 of the License. #
+# #
+# This program is distributed in the hope that it will be useful, but WITHOUT #
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
+# more details. #
+# #
+# You should have received a copy of the GNU General Public License along #
+# with this program; if not, write to the Free Software Foundation, Inc., 59 #
+# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
+###############################################################################
+"""
+ :mod:`openlp.core.lib.projector.pjlink1` module
+ Provides the necessary functions for connecting to a PJLink-capable projector.
+
+ See PJLink Class 1 Specifications for details.
+ http://pjlink.jbmia.or.jp/english/dl.html
+ Section 5-1 PJLink Specifications
+ Section 5-5 Guidelines for Input Terminals
+
+ NOTE:
+ Function names follow the following syntax:
+ def process_CCCC(...):
+ WHERE:
+ CCCC = PJLink command being processed.
+"""
+
+import logging
+log = logging.getLogger(__name__)
+
+log.debug('pjlink1 loaded')
+
+__all__ = ['PJLink1']
+
+from codecs import decode
+
+from PyQt4.QtCore import pyqtSignal, pyqtSlot
+from PyQt4.QtNetwork import QAbstractSocket, QTcpSocket
+
+from openlp.core.common import translate, qmd5_hash
+from openlp.core.lib.projector.constants import *
+
+# Shortcuts
+SocketError = QAbstractSocket.SocketError
+SocketSTate = QAbstractSocket.SocketState
+
+PJLINK_PREFIX = '%'
+PJLINK_CLASS = '1'
+PJLINK_HEADER = '%s%s' % (PJLINK_PREFIX, PJLINK_CLASS)
+PJLINK_SUFFIX = CR
+
+
+class PJLink1(QTcpSocket):
+ """
+ Socket service for connecting to a PJLink-capable projector.
+ """
+ # Signals sent by this module
+ changeStatus = pyqtSignal(str, int, str)
+ projectorNetwork = pyqtSignal(int) # Projector network activity
+ projectorStatus = pyqtSignal(int) # Status update
+ projectorAuthentication = pyqtSignal(str) # Authentication error
+ projectorNoAuthentication = pyqtSignal(str) # PIN set and no authentication needed
+ projectorReceivedData = pyqtSignal() # Notify when received data finished processing
+ projectorUpdateIcons = pyqtSignal() # Update the status icons on toolbar
+
+ def __init__(self, name=None, ip=None, port=PJLINK_PORT, pin=None, *args, **kwargs):
+ """
+ Setup for instance.
+
+ :param name: Display name
+ :param ip: IP address to connect to
+ :param port: Port to use. Default to PJLINK_PORT
+ :param pin: Access pin (if needed)
+
+ Optional parameters
+ :param dbid: Database ID number
+ :param location: Location where projector is physically located
+ :param notes: Extra notes about the projector
+ :param poll_time: Time (in seconds) to poll connected projector
+ :param socket_timeout: Time (in seconds) to abort the connection if no response
+ """
+ log.debug('PJlink(args="%s" kwargs="%s")' % (args, kwargs))
+ self.name = name
+ self.ip = ip
+ self.port = port
+ self.pin = pin
+ super(PJLink1, self).__init__()
+ self.dbid = None
+ self.location = None
+ self.notes = None
+ self.dbid = None if 'dbid' not in kwargs else kwargs['dbid']
+ self.location = None if 'location' not in kwargs else kwargs['notes']
+ self.notes = None if 'notes' not in kwargs else kwargs['notes']
+ # Poll time 20 seconds unless called with something else
+ self.poll_time = 20000 if 'poll_time' not in kwargs else kwargs['poll_time'] * 1000
+ # Timeout 5 seconds unless called with something else
+ self.socket_timeout = 5000 if 'socket_timeout' not in kwargs else kwargs['socket_timeout'] * 1000
+ # In case we're called from somewhere that only wants information
+ self.no_poll = 'no_poll' in kwargs
+ self.i_am_running = False
+ self.status_connect = S_NOT_CONNECTED
+ self.last_command = ''
+ self.projector_status = S_NOT_CONNECTED
+ self.error_status = S_OK
+ # Socket information
+ # Add enough space to input buffer for extraneous \n \r
+ self.maxSize = PJLINK_MAX_PACKET + 2
+ self.setReadBufferSize(self.maxSize)
+ # PJLink information
+ self.pjlink_class = '1' # Default class
+ self.reset_information()
+ # Set from ProjectorManager.add_projector()
+ self.widget = None # QListBox entry
+ self.timer = None # Timer that calls the poll_loop
+ self.send_queue = []
+ self.send_busy = False
+ # Socket timer for some possible brain-dead projectors or network cable pulled
+ self.socket_timer = None
+ # Map command to function
+ self.PJLINK1_FUNC = {'AVMT': self.process_avmt,
+ 'CLSS': self.process_clss,
+ 'ERST': self.process_erst,
+ 'INFO': self.process_info,
+ 'INF1': self.process_inf1,
+ 'INF2': self.process_inf2,
+ 'INPT': self.process_inpt,
+ 'INST': self.process_inst,
+ 'LAMP': self.process_lamp,
+ 'NAME': self.process_name,
+ 'PJLINK': self.check_login,
+ 'POWR': self.process_powr
+ }
+
+ def reset_information(self):
+ """
+ Reset projector-specific information to default
+ """
+ log.debug('(%s) reset_information() connect status is %s' % (self.ip, self.state()))
+ self.power = S_OFF
+ self.pjlink_name = None
+ self.manufacturer = None
+ self.model = None
+ self.shutter = None
+ self.mute = None
+ self.lamp = None
+ self.fan = None
+ self.source_available = None
+ self.source = None
+ self.other_info = None
+ if hasattr(self, 'timer'):
+ self.timer.stop()
+ if hasattr(self, 'socket_timer'):
+ self.socket_timer.stop()
+ self.send_queue = []
+ self.send_busy = False
+
+ def thread_started(self):
+ """
+ Connects signals to methods when thread is started.
+ """
+ log.debug('(%s) Thread starting' % self.ip)
+ self.i_am_running = True
+ self.connected.connect(self.check_login)
+ self.disconnected.connect(self.disconnect_from_host)
+ self.error.connect(self.get_error)
+
+ def thread_stopped(self):
+ """
+ Cleanups when thread is stopped.
+ """
+ log.debug('(%s) Thread stopped' % self.ip)
+ try:
+ self.connected.disconnect(self.check_login)
+ except TypeError:
+ pass
+ try:
+ self.disconnected.disconnect(self.disconnect_from_host)
+ except TypeError:
+ pass
+ try:
+ self.error.disconnect(self.get_error)
+ except TypeError:
+ pass
+ try:
+ self.projectorReceivedData.disconnect(self._send_command)
+ except TypeError:
+ pass
+ self.disconnect_from_host()
+ self.deleteLater()
+ self.i_am_running = False
+
+ def socket_abort(self):
+ """
+ Aborts connection and closes socket in case of brain-dead projectors.
+ Should normally be called by socket_timer().
+ """
+ log.debug('(%s) socket_abort() - Killing connection' % self.ip)
+ self.disconnect_from_host(abort=True)
+
+ def poll_loop(self):
+ """
+ Retrieve information from projector that changes.
+ Normally called by timer().
+ """
+ if self.state() != self.ConnectedState:
+ return
+ log.debug('(%s) Updating projector status' % self.ip)
+ # Reset timer in case we were called from a set command
+ if self.timer.interval() < self.poll_time:
+ # Reset timer to 5 seconds
+ self.timer.setInterval(self.poll_time)
+ # Restart timer
+ self.timer.start()
+ # These commands may change during connetion
+ for command in ['POWR', 'ERST', 'LAMP', 'AVMT', 'INPT']:
+ self.send_command(command, queue=True)
+ # The following commands do not change, so only check them once
+ if self.power == S_ON and self.source_available is None:
+ self.send_command('INST', queue=True)
+ if self.other_info is None:
+ self.send_command('INFO', queue=True)
+ if self.manufacturer is None:
+ self.send_command('INF1', queue=True)
+ if self.model is None:
+ self.send_command('INF2', queue=True)
+ if self.pjlink_name is None:
+ self.send_command('NAME', queue=True)
+ if self.power == S_ON and self.source_available is None:
+ self.send_command('INST', queue=True)
+
+ def _get_status(self, status):
+ """
+ Helper to retrieve status/error codes and convert to strings.
+
+ :param status: Status/Error code
+ :returns: (Status/Error code, String)
+ """
+ if status in ERROR_STRING:
+ return ERROR_STRING[status], ERROR_MSG[status]
+ elif status in STATUS_STRING:
+ return STATUS_STRING[status], ERROR_MSG[status]
+ else:
+ return status, translate('OpenLP.PJLink1', 'Unknown status')
+
+ def change_status(self, status, msg=None):
+ """
+ Check connection/error status, set status for projector, then emit status change signal
+ for gui to allow changing the icons.
+
+ :param status: Status code
+ :param msg: Optional message
+ """
+ message = translate('OpenLP.PJLink1', 'No message') if msg is None else msg
+ (code, message) = self._get_status(status)
+ if msg is not None:
+ message = msg
+ if status in CONNECTION_ERRORS:
+ # Projector, connection state
+ self.projector_status = self.error_status = self.status_connect = E_NOT_CONNECTED
+ elif status >= S_NOT_CONNECTED and status < S_STATUS:
+ self.status_connect = status
+ self.projector_status = S_NOT_CONNECTED
+ elif status < S_NETWORK_SENDING:
+ self.status_connect = S_CONNECTED
+ self.projector_status = status
+ (status_code, status_message) = self._get_status(self.status_connect)
+ log.debug('(%s) status_connect: %s: %s' % (self.ip, status_code, status_message if msg is None else msg))
+ (status_code, status_message) = self._get_status(self.projector_status)
+ log.debug('(%s) projector_status: %s: %s' % (self.ip, status_code, status_message if msg is None else msg))
+ (status_code, status_message) = self._get_status(self.error_status)
+ log.debug('(%s) error_status: %s: %s' % (self.ip, status_code, status_message if msg is None else msg))
+ self.changeStatus.emit(self.ip, status, message)
+
+ @pyqtSlot()
+ def check_login(self, data=None):
+ """
+ Processes the initial connection and authentication (if needed).
+ Starts poll timer if connection is established.
+
+ :param data: Optional data if called from another routine
+ """
+ log.debug('(%s) check_login(data="%s")' % (self.ip, data))
+ if data is None:
+ # Reconnected setup?
+ if not self.waitForReadyRead(2000):
+ # Possible timeout issue
+ log.error('(%s) Socket timeout waiting for login' % self.ip)
+ self.change_status(E_SOCKET_TIMEOUT)
+ return
+ read = self.readLine(self.maxSize)
+ dontcare = self.readLine(self.maxSize) # Clean out the trailing \r\n
+ if read is None:
+ log.warn('(%s) read is None - socket error?' % self.ip)
+ return
+ elif len(read) < 8:
+ log.warn('(%s) Not enough data read)' % self.ip)
+ return
+ data = decode(read, 'ascii')
+ # Possibility of extraneous data on input when reading.
+ # Clean out extraneous characters in buffer.
+ dontcare = self.readLine(self.maxSize)
+ log.debug('(%s) check_login() read "%s"' % (self.ip, data.strip()))
+ # At this point, we should only have the initial login prompt with
+ # possible authentication
+ # PJLink initial login will be:
+ # 'PJLink 0' - Unauthenticated login - no extra steps required.
+ # 'PJLink 1 XXXXXX' Authenticated login - extra processing required.
+ if not data.upper().startswith('PJLINK'):
+ # Invalid response
+ return self.disconnect_from_host()
+ if '=' in data:
+ # Processing a login reply
+ data_check = data.strip().split('=')
+ else:
+ # Process initial connection
+ data_check = data.strip().split(' ')
+ log.debug('(%s) data_check="%s"' % (self.ip, data_check))
+ # Check for projector reporting an error
+ if data_check[1].upper() == 'ERRA':
+ # Authentication error
+ self.disconnect_from_host()
+ self.change_status(E_AUTHENTICATION)
+ log.debug('(%s) emitting projectorAuthentication() signal' % self.name)
+ return
+ elif data_check[1] == '0' and self.pin is not None:
+ # Pin set and no authentication needed
+ self.disconnect_from_host()
+ self.change_status(E_AUTHENTICATION)
+ log.debug('(%s) emitting projectorNoAuthentication() signal' % self.name)
+ self.projectorNoAuthentication.emit(self.name)
+ return
+ elif data_check[1] == '1':
+ # Authenticated login with salt
+ log.debug('(%s) Setting hash with salt="%s"' % (self.ip, data_check[2]))
+ log.debug('(%s) pin="%s"' % (self.ip, self.pin))
+ salt = qmd5_hash(salt=data_check[2], data=self.pin)
+ else:
+ salt = None
+ # We're connected at this point, so go ahead and do regular I/O
+ self.readyRead.connect(self.get_data)
+ self.projectorReceivedData.connect(self._send_command)
+ # Initial data we should know about
+ self.send_command(cmd='CLSS', salt=salt)
+ self.waitForReadyRead()
+ if (not self.no_poll) and (self.state() == self.ConnectedState):
+ log.debug('(%s) Starting timer' % self.ip)
+ self.timer.setInterval(2000) # Set 2 seconds for initial information
+ self.timer.start()
+
+ @pyqtSlot()
+ def get_data(self):
+ """
+ Socket interface to retrieve data.
+ """
+ log.debug('(%s) get_data(): Reading data' % self.ip)
+ if self.state() != self.ConnectedState:
+ log.debug('(%s) get_data(): Not connected - returning' % self.ip)
+ self.send_busy = False
+ return
+ read = self.readLine(self.maxSize)
+ if read == -1:
+ # No data available
+ log.debug('(%s) get_data(): No data available (-1)' % self.ip)
+ self.send_busy = False
+ self.projectorReceivedData.emit()
+ return
+ self.socket_timer.stop()
+ self.projectorNetwork.emit(S_NETWORK_RECEIVED)
+ data_in = decode(read, 'ascii')
+ data = data_in.strip()
+ if len(data) < 7:
+ # Not enough data for a packet
+ log.debug('(%s) get_data(): Packet length < 7: "%s"' % (self.ip, data))
+ self.send_busy = False
+ self.projectorReceivedData.emit()
+ return
+ log.debug('(%s) get_data(): Checking new data "%s"' % (self.ip, data))
+ if data.upper().startswith('PJLINK'):
+ # Reconnected from remote host disconnect ?
+ self.check_login(data)
+ self.send_busy = False
+ self.projectorReceivedData.emit()
+ return
+ elif '=' not in data:
+ log.warn('(%s) get_data(): Invalid packet received' % self.ip)
+ self.send_busy = False
+ self.projectorReceivedData.emit()
+ return
+ data_split = data.split('=')
+ try:
+ (prefix, class_, cmd, data) = (data_split[0][0], data_split[0][1], data_split[0][2:], data_split[1])
+ except ValueError as e:
+ log.warn('(%s) get_data(): Invalid packet - expected header + command + data' % self.ip)
+ log.warn('(%s) get_data(): Received data: "%s"' % (self.ip, read))
+ self.change_status(E_INVALID_DATA)
+ self.send_busy = False
+ self.projectorReceivedData.emit()
+ return
+
+ if not (self.pjlink_class in PJLINK_VALID_CMD and cmd in PJLINK_VALID_CMD[self.pjlink_class]):
+ log.warn('(%s) get_data(): Invalid packet - unknown command "%s"' % (self.ip, cmd))
+ self.send_busy = False
+ self.projectorReceivedData.emit()
+ return
+ return self.process_command(cmd, data)
+
+ @pyqtSlot(int)
+ def get_error(self, err):
+ """
+ Process error from SocketError signal.
+ Remaps system error codes to projector error codes.
+
+ :param err: Error code
+ """
+ log.debug('(%s) get_error(err=%s): %s' % (self.ip, err, self.errorString()))
+ if err <= 18:
+ # QSocket errors. Redefined in projector.constants so we don't mistake
+ # them for system errors
+ check = err + E_CONNECTION_REFUSED
+ self.timer.stop()
+ else:
+ check = err
+ if check < E_GENERAL:
+ # Some system error?
+ self.change_status(err, self.errorString())
+ else:
+ self.change_status(E_NETWORK, self.errorString())
+ self.projectorUpdateIcons.emit()
+ if self.status_connect == E_NOT_CONNECTED:
+ self.abort()
+ self.reset_information()
+ return
+
+ def send_command(self, cmd, opts='?', salt=None, queue=False):
+ """
+ Add command to output queue if not already in queue.
+
+ :param cmd: Command to send
+ :param opts: Command option (if any) - defaults to '?' (get information)
+ :param salt: Optional salt for md5 hash initial authentication
+ :param queue: Option to force add to queue rather than sending directly
+ """
+ if self.state() != self.ConnectedState:
+ log.warn('(%s) send_command(): Not connected - returning' % self.ip)
+ self.send_queue = []
+ return
+ self.projectorNetwork.emit(S_NETWORK_SENDING)
+ log.debug('(%s) send_command(): Building cmd="%s" opts="%s" %s' % (self.ip,
+ cmd,
+ opts,
+ '' if salt is None else 'with hash'))
+ if salt is None:
+ out = '%s%s %s%s' % (PJLINK_HEADER, cmd, opts, CR)
+ else:
+ out = '%s%s%s %s%s' % (salt, PJLINK_HEADER, cmd, opts, CR)
+ if out in self.send_queue:
+ # Already there, so don't add
+ log.debug('(%s) send_command(out="%s") Already in queue - skipping' % (self.ip, out.strip()))
+ elif not queue and len(self.send_queue) == 0:
+ # Nothing waiting to send, so just send it
+ log.debug('(%s) send_command(out="%s") Sending data' % (self.ip, out.strip()))
+ return self._send_command(data=out)
+ else:
+ log.debug('(%s) send_command(out="%s") adding to queue' % (self.ip, out.strip()))
+ self.send_queue.append(out)
+ self.projectorReceivedData.emit()
+ log.debug('(%s) send_command(): send_busy is %s' % (self.ip, self.send_busy))
+ if not self.send_busy:
+ log.debug('(%s) send_command() calling _send_string()')
+ self._send_command()
+
+ @pyqtSlot()
+ def _send_command(self, data=None):
+ """
+ Socket interface to send data. If data=None, then check queue.
+
+ :param data: Immediate data to send
+ """
+ log.debug('(%s) _send_string()' % self.ip)
+ log.debug('(%s) _send_string(): Connection status: %s' % (self.ip, self.state()))
+ if self.state() != self.ConnectedState:
+ log.debug('(%s) _send_string() Not connected - abort' % self.ip)
+ self.send_queue = []
+ self.send_busy = False
+ return
+ if self.send_busy:
+ # Still waiting for response from last command sent
+ return
+ if data is not None:
+ out = data
+ log.debug('(%s) _send_string(data=%s)' % (self.ip, out.strip()))
+ elif len(self.send_queue) != 0:
+ out = self.send_queue.pop(0)
+ log.debug('(%s) _send_string(queued data=%s)' % (self.ip, out.strip()))
+ else:
+ # No data to send
+ log.debug('(%s) _send_string(): No data to send' % self.ip)
+ self.send_busy = False
+ return
+ self.send_busy = True
+ log.debug('(%s) _send_string(): Sending "%s"' % (self.ip, out.strip()))
+ log.debug('(%s) _send_string(): Queue = %s' % (self.ip, self.send_queue))
+ self.socket_timer.start()
+ try:
+ self.projectorNetwork.emit(S_NETWORK_SENDING)
+ sent = self.write(out)
+ self.waitForBytesWritten(2000) # 2 seconds should be enough
+ if sent == -1:
+ # Network error?
+ self.change_status(E_NETWORK,
+ translate('OpenLP.PJLink1', 'Error while sending data to projector'))
+ except SocketError as e:
+ self.disconnect_from_host(abort=True)
+ self.changeStatus(E_NETWORK, '%s : %s' % (e.error(), e.errorString()))
+
+ def process_command(self, cmd, data):
+ """
+ Verifies any return error code. Calls the appropriate command handler.
+
+ :param cmd: Command to process
+ :param data: Data being processed
+ """
+ log.debug('(%s) Processing command "%s"' % (self.ip, cmd))
+ if data in PJLINK_ERRORS:
+ # Oops - projector error
+ if data.upper() == 'ERRA':
+ # Authentication error
+ self.disconnect_from_host()
+ self.change_status(E_AUTHENTICATION)
+ log.debug('(%s) emitting projectorAuthentication() signal' % self.ip)
+ self.projectorAuthentication.emit(self.name)
+ elif data.upper() == 'ERR1':
+ # Undefined command
+ self.change_status(E_UNDEFINED, '%s "%s"' %
+ (translate('OpenLP.PJLink1', 'Undefined command:'), cmd))
+ elif data.upper() == 'ERR2':
+ # Invalid parameter
+ self.change_status(E_PARAMETER)
+ elif data.upper() == 'ERR3':
+ # Projector busy
+ self.change_status(E_UNAVAILABLE)
+ elif data.upper() == 'ERR4':
+ # Projector/display error
+ self.change_status(E_PROJECTOR)
+ self.send_busy = False
+ self.projectorReceivedData.emit()
+ return
+ # Command succeeded - no extra information
+ elif data.upper() == 'OK':
+ log.debug('(%s) Command returned OK' % self.ip)
+ # A command returned successfully, recheck data
+ self.send_busy = False
+ self.projectorReceivedData.emit()
+ return
+
+ if cmd in self.PJLINK1_FUNC:
+ self.PJLINK1_FUNC[cmd](data)
+ else:
+ log.warn('(%s) Invalid command %s' % (self.ip, cmd))
+ self.send_busy = False
+ self.projectorReceivedData.emit()
+
+ def process_lamp(self, data):
+ """
+ Lamp(s) status. See PJLink Specifications for format.
+ Data may have more than 1 lamp to process.
+ Update self.lamp dictionary with lamp status.
+
+ :param data: Lamp(s) status.
+ """
+ lamps = []
+ data_dict = data.split()
+ while data_dict:
+ try:
+ fill = {'Hours': int(data_dict[0]), 'On': False if data_dict[1] == '0' else True}
+ except ValueError:
+ # In case of invalid entry
+ log.warn('(%s) process_lamp(): Invalid data "%s"' % (self.ip, data))
+ return
+ lamps.append(fill)
+ data_dict.pop(0) # Remove lamp hours
+ data_dict.pop(0) # Remove lamp on/off
+ self.lamp = lamps
+ return
+
+ def process_powr(self, data):
+ """
+ Power status. See PJLink specification for format.
+ Update self.power with status. Update icons if change from previous setting.
+
+ :param data: Power status
+ """
+ if data in PJLINK_POWR_STATUS:
+ power = PJLINK_POWR_STATUS[data]
+ update_icons = self.power != power
+ self.power = power
+ self.change_status(PJLINK_POWR_STATUS[data])
+ if update_icons:
+ self.projectorUpdateIcons.emit()
+ # Update the input sources available
+ if power == S_ON:
+ self.send_command('INST')
+ else:
+ # Log unknown status response
+ log.warn('Unknown power response: %s' % data)
+ return
+
+ def process_avmt(self, data):
+ """
+ Process shutter and speaker status. See PJLink specification for format.
+ Update self.mute (audio) and self.shutter (video shutter).
+
+ :param data: Shutter and audio status
+ """
+ shutter = self.shutter
+ mute = self.mute
+ if data == '11':
+ shutter = True
+ mute = False
+ elif data == '21':
+ shutter = False
+ mute = True
+ elif data == '30':
+ shutter = False
+ mute = False
+ elif data == '31':
+ shutter = True
+ mute = True
+ else:
+ log.warn('Unknown shutter response: %s' % data)
+ update_icons = shutter != self.shutter
+ update_icons = update_icons or mute != self.mute
+ self.shutter = shutter
+ self.mute = mute
+ if update_icons:
+ self.projectorUpdateIcons.emit()
+ return
+
+ def process_inpt(self, data):
+ """
+ Current source input selected. See PJLink specification for format.
+ Update self.source
+
+ :param data: Currently selected source
+ """
+ self.source = data
+ return
+
+ def process_clss(self, data):
+ """
+ PJLink class that this projector supports. See PJLink specification for format.
+ Updates self.class.
+
+ :param data: Class that projector supports.
+ """
+ self.pjlink_class = data
+ log.debug('(%s) Setting pjlink_class for this projector to "%s"' % (self.ip, self.pjlink_class))
+ return
+
+ def process_name(self, data):
+ """
+ Projector name set in projector.
+ Updates self.pjlink_name
+
+ :param data: Projector name
+ """
+ self.pjlink_name = data
+ return
+
+ def process_inf1(self, data):
+ """
+ Manufacturer name set in projector.
+ Updates self.manufacturer
+
+ :param data: Projector manufacturer
+ """
+ self.manufacturer = data
+ return
+
+ def process_inf2(self, data):
+ """
+ Projector Model set in projector.
+ Updates self.model.
+
+ :param data: Model name
+ """
+ self.model = data
+ return
+
+ def process_info(self, data):
+ """
+ Any extra info set in projector.
+ Updates self.other_info.
+
+ :param data: Projector other info
+ """
+ self.other_info = data
+ return
+
+ def process_inst(self, data):
+ """
+ Available source inputs. See PJLink specification for format.
+ Updates self.source_available
+
+ :param data: Sources list
+ """
+ sources = []
+ check = data.split()
+ for source in check:
+ sources.append(source)
+ sources.sort()
+ self.source_available = sources
+ self.projectorUpdateIcons.emit()
+ return
+
+ def process_erst(self, data):
+ """
+ Error status. See PJLink Specifications for format.
+ Updates self.projector_errors
+
+ :param data: Error status
+ """
+ try:
+ datacheck = int(data)
+ except ValueError:
+ # Bad data - ignore
+ return
+ if datacheck == 0:
+ self.projector_errors = None
+ else:
+ self.projector_errors = {}
+ # Fan
+ if data[0] != '0':
+ self.projector_errors[translate('OpenLP.ProjectorPJLink', 'Fan')] = \
+ PJLINK_ERST_STATUS[data[0]]
+ # Lamp
+ if data[1] != '0':
+ self.projector_errors[translate('OpenLP.ProjectorPJLink', 'Lamp')] = \
+ PJLINK_ERST_STATUS[data[1]]
+ # Temp
+ if data[2] != '0':
+ self.projector_errors[translate('OpenLP.ProjectorPJLink', 'Temperature')] = \
+ PJLINK_ERST_STATUS[data[2]]
+ # Cover
+ if data[3] != '0':
+ self.projector_errors[translate('OpenLP.ProjectorPJLink', 'Cover')] = \
+ PJLINK_ERST_STATUS[data[3]]
+ # Filter
+ if data[4] != '0':
+ self.projector_errors[translate('OpenLP.ProjectorPJLink', 'Filter')] = \
+ PJLINK_ERST_STATUS[data[4]]
+ # Other
+ if data[5] != '0':
+ self.projector_errors[translate('OpenLP.ProjectorPJLink', 'Other')] = \
+ PJLINK_ERST_STATUS[data[5]]
+ return
+
+ def connect_to_host(self):
+ """
+ Initiate connection to projector.
+ """
+ if self.state() == self.ConnectedState:
+ log.warn('(%s) connect_to_host(): Already connected - returning' % self.ip)
+ return
+ self.change_status(S_CONNECTING)
+ self.connectToHost(self.ip, self.port if type(self.port) is int else int(self.port))
+
+ @pyqtSlot()
+ def disconnect_from_host(self, abort=False):
+ """
+ Close socket and cleanup.
+ """
+ if abort or self.state() != self.ConnectedState:
+ if abort:
+ log.warn('(%s) disconnect_from_host(): Aborting connection' % self.ip)
+ else:
+ log.warn('(%s) disconnect_from_host(): Not connected - returning' % self.ip)
+ self.reset_information()
+ self.disconnectFromHost()
+ try:
+ self.readyRead.disconnect(self.get_data)
+ except TypeError:
+ pass
+ if abort:
+ self.change_status(E_NOT_CONNECTED)
+ else:
+ log.debug('(%s) disconnect_from_host() Current status %s' % (self.ip,
+ self._get_status(self.status_connect)[0]))
+ if self.status_connect != E_NOT_CONNECTED:
+ self.change_status(S_NOT_CONNECTED)
+ self.reset_information()
+ self.projectorUpdateIcons.emit()
+
+ def get_available_inputs(self):
+ """
+ Send command to retrieve available source inputs.
+ """
+ return self.send_command(cmd='INST')
+
+ def get_error_status(self):
+ """
+ Send command to retrieve currently known errors.
+ """
+ return self.send_command(cmd='ERST')
+
+ def get_input_source(self):
+ """
+ Send command to retrieve currently selected source input.
+ """
+ return self.send_command(cmd='INPT')
+
+ def get_lamp_status(self):
+ """
+ Send command to return the lap status.
+ """
+ return self.send_command(cmd='LAMP')
+
+ def get_manufacturer(self):
+ """
+ Send command to retrieve manufacturer name.
+ """
+ return self.send_command(cmd='INF1')
+
+ def get_model(self):
+ """
+ Send command to retrieve the model name.
+ """
+ return self.send_command(cmd='INF2')
+
+ def get_name(self):
+ """
+ Send command to retrieve name as set by end-user (if set).
+ """
+ return self.send_command(cmd='NAME')
+
+ def get_other_info(self):
+ """
+ Send command to retrieve extra info set by manufacturer.
+ """
+ return self.send_command(cmd='INFO')
+
+ def get_power_status(self):
+ """
+ Send command to retrieve power status.
+ """
+ return self.send_command(cmd='POWR')
+
+ def get_shutter_status(self):
+ """
+ Send command to retrieve shutter status.
+ """
+ return self.send_command(cmd='AVMT')
+
+ def set_input_source(self, src=None):
+ """
+ Verify input source available as listed in 'INST' command,
+ then send the command to select the input source.
+
+ :param src: Video source to select in projector
+ """
+ log.debug('(%s) set_input_source(src=%s)' % (self.ip, src))
+ if self.source_available is None:
+ return
+ elif src not in self.source_available:
+ return
+ log.debug('(%s) Setting input source to %s' % (self.ip, src))
+ self.send_command(cmd='INPT', opts=src)
+ self.poll_loop()
+
+ def set_power_on(self):
+ """
+ Send command to turn power to on.
+ """
+ self.send_command(cmd='POWR', opts='1')
+ self.poll_loop()
+
+ def set_power_off(self):
+ """
+ Send command to turn power to standby.
+ """
+ self.send_command(cmd='POWR', opts='0')
+ self.poll_loop()
+
+ def set_shutter_closed(self):
+ """
+ Send command to set shutter to closed position.
+ """
+ self.send_command(cmd='AVMT', opts='11')
+ self.poll_loop()
+
+ def set_shutter_open(self):
+ """
+ Send command to set shutter to open position.
+ """
+ self.send_command(cmd='AVMT', opts='10')
+ self.poll_loop()
=== modified file 'openlp/core/lib/toolbar.py'
--- openlp/core/lib/toolbar.py 2014-06-30 20:59:22 +0000
+++ openlp/core/lib/toolbar.py 2014-10-28 21:04:17 +0000
@@ -82,3 +82,16 @@
self.actions[handle].setVisible(visible)
else:
log.warning('No handle "%s" in actions list.', str(handle))
+
+ def set_widget_enabled(self, widgets, enabled=True):
+ """
+ Set the enabled state for a widget or a list of widgets.
+
+ :param widgets: A list of string with widget object names.
+ :param enabled: The new state as bool.
+ """
+ for handle in widgets:
+ if handle in self.actions:
+ self.actions[handle].setEnabled(enabled)
+ else:
+ log.warning('No handle "%s" in actions list.', str(handle))
=== 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-10-28 21:04:17 +0000
@@ -124,9 +124,13 @@
from .mediadockmanager import MediaDockManager
from .servicemanager import ServiceManager
from .thememanager import ThemeManager
+from .projector.manager import ProjectorManager
+from .projector.tab import ProjectorTab
+from .projector.editform import ProjectorEditForm
__all__ = ['SplashScreen', 'AboutForm', 'SettingsForm', 'MainDisplay', 'SlideController', 'ServiceManager', 'ThemeForm',
'ThemeManager', 'MediaDockManager', 'ServiceItemEditForm', 'FirstTimeForm', 'FirstTimeLanguageForm',
'Display', 'ServiceNoteForm', 'ThemeLayoutForm', 'FileRenameForm', 'StartTimeForm', 'MainDisplay',
'SlideController', 'DisplayController', 'GeneralTab', 'ThemesTab', 'AdvancedTab', 'PluginForm',
- 'FormattingTagForm', 'ShortcutListForm', 'FormattingTagController', 'SingleColumnTableWidget']
+ 'FormattingTagForm', 'ShortcutListForm', 'FormattingTagController', 'SingleColumnTableWidget',
+ 'ProjectorManager', 'ProjectorTab', 'ProjectorEditForm']
=== modified file 'openlp/core/ui/mainwindow.py'
--- openlp/core/ui/mainwindow.py 2014-10-22 20:47:47 +0000
+++ openlp/core/ui/mainwindow.py 2014-10-28 21:04:17 +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.projector.manager 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', 'Toggle Projector Manager'))
+ self.view_projector_manager_item.setStatusTip(translate('OpenLP.MainWindow',
+ 'Toggle the visibility of the Projector Manager'))
self.view_media_manager_item.setText(translate('OpenLP.MainWindow', '&Media Manager'))
self.view_media_manager_item.setToolTip(translate('OpenLP.MainWindow', 'Toggle Media Manager'))
self.view_media_manager_item.setStatusTip(translate('OpenLP.MainWindow',
@@ -485,6 +514,7 @@
self.service_manager_settings_section = 'servicemanager'
self.songs_settings_section = 'songs'
self.themes_settings_section = 'themes'
+ self.projector_settings_section = 'projector'
self.players_settings_section = 'players'
self.display_tags_section = 'displayTags'
self.header_section = 'SettingsImport'
@@ -514,6 +544,7 @@
self.media_manager_dock.visibilityChanged.connect(self.view_media_manager_item.setChecked)
self.service_manager_dock.visibilityChanged.connect(self.view_service_manager_item.setChecked)
self.theme_manager_dock.visibilityChanged.connect(self.view_theme_manager_item.setChecked)
+ self.projector_manager_dock.visibilityChanged.connect(self.view_projector_manager_item.setChecked)
self.import_theme_item.triggered.connect(self.theme_manager_contents.on_import_theme)
self.export_theme_item.triggered.connect(self.theme_manager_contents.on_export_theme)
self.web_site_item.triggered.connect(self.on_help_web_site_clicked)
@@ -826,6 +857,7 @@
setting_sections.extend([self.shortcuts_settings_section])
setting_sections.extend([self.service_manager_settings_section])
setting_sections.extend([self.themes_settings_section])
+ setting_sections.extend([self.projector_settings_section])
setting_sections.extend([self.players_settings_section])
setting_sections.extend([self.display_tags_section])
setting_sections.extend([self.header_section])
@@ -1115,6 +1147,12 @@
"""
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 directory 'openlp/core/ui/projector'
=== added file 'openlp/core/ui/projector/editform.py'
--- openlp/core/ui/projector/editform.py 1970-01-01 00:00:00 +0000
+++ openlp/core/ui/projector/editform.py 2014-10-28 21:04:17 +0000
@@ -0,0 +1,269 @@
+
+# -*- coding: utf-8 -*-
+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
+
+###############################################################################
+# OpenLP - Open Source Lyrics Projection #
+# --------------------------------------------------------------------------- #
+# Copyright (c) 2008-2014 Raoul Snyman #
+# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan #
+# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
+# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
+# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
+# Christian Richter, Philip Ridout, Ken Roberts, Simon Scudder, #
+# Jeffrey Smith, Maikel Stuivenberg, Martin Thompson, Jon Tibble, #
+# Dave Warnock, Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
+# --------------------------------------------------------------------------- #
+# This program is free software; you can redistribute it and/or modify it #
+# under the terms of the GNU General Public License as published by the Free #
+# Software Foundation; version 2 of the License. #
+# #
+# This program is distributed in the hope that it will be useful, but WITHOUT #
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
+# more details. #
+# #
+# You should have received a copy of the GNU General Public License along #
+# with this program; if not, write to the Free Software Foundation, Inc., 59 #
+# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
+###############################################################################
+"""
+ :mod: `openlp.core.ui.projector.editform` module
+
+ Provides the functions for adding/editing entries in the projector database.
+"""
+
+import logging
+log = logging.getLogger(__name__)
+log.debug('editform loaded')
+
+from PyQt4 import QtCore, QtGui
+from PyQt4.QtCore import pyqtSlot, pyqtSignal
+from PyQt4.QtGui import QDialog, QPlainTextEdit, QLineEdit, QDialogButtonBox, QLabel, QGridLayout
+
+from openlp.core.common import translate, verify_ip_address
+from openlp.core.lib import build_icon
+from openlp.core.lib.projector.db import Projector
+from openlp.core.lib.projector.constants import PJLINK_PORT
+
+
+class Ui_ProjectorEditForm(object):
+ """
+ The :class:`~openlp.core.lib.ui.projector.editform.Ui_ProjectorEditForm` class defines
+ the user interface for the ProjectorEditForm dialog.
+ """
+ def setupUi(self, edit_projector_dialog):
+ """
+ Create the interface layout.
+ """
+ edit_projector_dialog.setObjectName('edit_projector_dialog')
+ edit_projector_dialog.setWindowIcon(build_icon(u':/icon/openlp-logo-32x32.png'))
+ edit_projector_dialog.setMinimumWidth(400)
+ edit_projector_dialog.setModal(True)
+ # Define the basic layout
+ self.dialog_layout = QGridLayout(edit_projector_dialog)
+ self.dialog_layout.setObjectName('dialog_layout')
+ self.dialog_layout.setSpacing(8)
+ self.dialog_layout.setContentsMargins(8, 8, 8, 8)
+ # IP Address
+ self.ip_label = QLabel(edit_projector_dialog)
+ self.ip_label.setObjectName('projector_edit_ip_label')
+ self.ip_text = QLineEdit(edit_projector_dialog)
+ self.ip_text.setObjectName('projector_edit_ip_text')
+ self.dialog_layout.addWidget(self.ip_label, 0, 0)
+ self.dialog_layout.addWidget(self.ip_text, 0, 1)
+ # Port number
+ self.port_label = QLabel(edit_projector_dialog)
+ self.port_label.setObjectName('projector_edit_ip_label')
+ self.port_text = QLineEdit(edit_projector_dialog)
+ self.port_text.setObjectName('projector_edit_port_text')
+ self.dialog_layout.addWidget(self.port_label, 1, 0)
+ self.dialog_layout.addWidget(self.port_text, 1, 1)
+ # PIN
+ self.pin_label = QLabel(edit_projector_dialog)
+ self.pin_label.setObjectName('projector_edit_pin_label')
+ self.pin_text = QLineEdit(edit_projector_dialog)
+ self.pin_label.setObjectName('projector_edit_pin_text')
+ self.dialog_layout.addWidget(self.pin_label, 2, 0)
+ self.dialog_layout.addWidget(self.pin_text, 2, 1)
+ # Name
+ self.name_label = QLabel(edit_projector_dialog)
+ self.name_label.setObjectName('projector_edit_name_label')
+ self.name_text = QLineEdit(edit_projector_dialog)
+ self.name_text.setObjectName('projector_edit_name_text')
+ self.dialog_layout.addWidget(self.name_label, 3, 0)
+ self.dialog_layout.addWidget(self.name_text, 3, 1)
+ # Location
+ self.location_label = QLabel(edit_projector_dialog)
+ self.location_label.setObjectName('projector_edit_location_label')
+ self.location_text = QLineEdit(edit_projector_dialog)
+ self.location_text.setObjectName('projector_edit_location_text')
+ self.dialog_layout.addWidget(self.location_label, 4, 0)
+ self.dialog_layout.addWidget(self.location_text, 4, 1)
+ # Notes
+ self.notes_label = QLabel(edit_projector_dialog)
+ self.notes_label.setObjectName('projector_edit_notes_label')
+ self.notes_text = QPlainTextEdit(edit_projector_dialog)
+ self.notes_text.setObjectName('projector_edit_notes_text')
+ self.dialog_layout.addWidget(self.notes_label, 5, 0, alignment=QtCore.Qt.AlignTop)
+ self.dialog_layout.addWidget(self.notes_text, 5, 1)
+ # Time for the buttons
+ self.button_box = QDialogButtonBox(QDialogButtonBox.Help |
+ QDialogButtonBox.Save |
+ QDialogButtonBox.Cancel)
+ self.dialog_layout.addWidget(self.button_box, 8, 0, 1, 2)
+
+ def retranslateUi(self, edit_projector_dialog):
+ if self.new_projector:
+ title = translate('OpenLP.ProjectorEditForm', 'Add New Projector')
+ self.projector.port = PJLINK_PORT
+ else:
+ title = translate('OpenLP.ProjectorEditForm', 'Edit Projector')
+ edit_projector_dialog.setWindowTitle(title)
+ self.ip_label.setText(translate('OpenLP.ProjectorEditForm', 'IP Address'))
+ self.ip_text.setText(self.projector.ip)
+ self.ip_text.setFocus()
+ self.port_label.setText(translate('OpenLP.ProjectorEditForm', 'Port Number'))
+ self.port_text.setText(str(self.projector.port))
+ self.pin_label.setText(translate('OpenLP.ProjectorEditForm', 'PIN'))
+ self.pin_text.setText(self.projector.pin)
+ self.name_label.setText(translate('OpenLP.ProjectorEditForm', 'Name'))
+ self.name_text.setText(self.projector.name)
+ self.location_label.setText(translate('OpenLP.ProjectorEditForm', 'Location'))
+ self.location_text.setText(self.projector.location)
+ self.notes_label.setText(translate('OpenLP.ProjectorEditForm', 'Notes'))
+ self.notes_text.insertPlainText(self.projector.notes)
+
+
+class ProjectorEditForm(QDialog, Ui_ProjectorEditForm):
+ """
+ Class to add or edit a projector entry in the database.
+
+ Fields that are editable:
+ ip = Column(String(100))
+ port = Column(String(8))
+ pin = Column(String(20))
+ name = Column(String(20))
+ location = Column(String(30))
+ notes = Column(String(200))
+ """
+ newProjector = pyqtSignal(str)
+ editProjector = pyqtSignal(object)
+
+ def __init__(self, parent=None, projectordb=None):
+ super(ProjectorEditForm, self).__init__(parent=parent)
+ self.projectordb = projectordb
+ self.setupUi(self)
+ self.button_box.accepted.connect(self.accept_me)
+ self.button_box.helpRequested.connect(self.help_me)
+ self.button_box.rejected.connect(self.cancel_me)
+
+ def exec_(self, projector=None):
+ if projector is None:
+ self.projector = Projector()
+ self.new_projector = True
+ else:
+ self.projector = projector
+ self.new_projector = False
+ self.retranslateUi(self)
+ reply = QDialog.exec_(self)
+ self.projector = None
+ return reply
+
+ @pyqtSlot()
+ def accept_me(self):
+ """
+ Validate input before accepting input.
+ """
+ log.debug('accept_me() signal received')
+ if len(self.name_text.text().strip()) < 1:
+ QtGui.QMessageBox.warning(self,
+ translate('OpenLP.ProjectorEdit', 'Name Not Set'),
+ translate('OpenLP.ProjectorEdit',
+ 'You must enter a name for this entry.<br />'
+ 'Please enter a new name for this entry.'))
+ valid = False
+ return
+ name = self.name_text.text().strip()
+ record = self.projectordb.get_projector_by_name(name)
+ if record is not None and record.id != self.projector.id:
+ QtGui.QMessageBox.warning(self,
+ translate('OpenLP.ProjectorEdit', 'Duplicate Name'),
+ translate('OpenLP.ProjectorEdit',
+ 'There is already an entry with name "%s" in '
+ 'the database as ID "%s". <br />'
+ 'Please enter a different name.' % (name, record.id)))
+ valid = False
+ return
+ adx = self.ip_text.text()
+ valid = verify_ip_address(adx)
+ if valid:
+ ip = self.projectordb.get_projector_by_ip(adx)
+ if ip is None:
+ valid = True
+ self.new_projector = True
+ elif ip.id != self.projector.id:
+ QtGui.QMessageBox.warning(self,
+ translate('OpenLP.ProjectorWizard', 'Duplicate IP Address'),
+ translate('OpenLP.ProjectorWizard',
+ 'IP address "%s"<br />is already in the database as ID %s.'
+ '<br /><br />Please Enter a different IP address.' % (adx, ip.id)))
+ valid = False
+ return
+ else:
+ QtGui.QMessageBox.warning(self,
+ translate('OpenLP.ProjectorWizard', 'Invalid IP Address'),
+ translate('OpenLP.ProjectorWizard',
+ 'IP address "%s"<br>is not a valid IP address.'
+ '<br /><br />Please enter a valid IP address.' % adx))
+ valid = False
+ return
+ port = int(self.port_text.text())
+ if port < 1000 or port > 32767:
+ QtGui.QMessageBox.warning(self,
+ translate('OpenLP.ProjectorWizard', 'Invalid Port Number'),
+ translate('OpenLP.ProjectorWizard',
+ 'Port numbers below 1000 are reserved for admin use only, '
+ '<br />and port numbers above 32767 are not currently usable.'
+ '<br /><br />Please enter a valid port number between '
+ ' 1000 and 32767.'
+ '<br /><br />Default PJLink port is %s' % PJLINK_PORT))
+ valid = False
+ if valid:
+ self.projector.ip = self.ip_text.text()
+ self.projector.pin = self.pin_text.text()
+ self.projector.port = int(self.port_text.text())
+ self.projector.name = self.name_text.text()
+ self.projector.location = self.location_text.text()
+ self.projector.notes = self.notes_text.toPlainText()
+ if self.new_projector:
+ saved = self.projectordb.add_projector(self.projector)
+ else:
+ saved = self.projectordb.update_projector(self.projector)
+ if not saved:
+ QtGui.QMessageBox.warning(self,
+ translate('OpenLP.ProjectorEditForm', 'Database Error'),
+ translate('OpenLP.ProjectorEditForm',
+ 'There was an error saving projector '
+ 'information. See the log for the error'))
+ return saved
+ if self.new_projector:
+ self.newProjector.emit(adx)
+ else:
+ self.editProjector.emit(self.projector)
+ self.close()
+
+ @pyqtSlot()
+ def help_me(self):
+ """
+ Show a help message about the input fields.
+ """
+ log.debug('help_me() signal received')
+
+ @pyqtSlot()
+ def cancel_me(self):
+ """
+ Cancel button clicked - just close.
+ """
+ log.debug('cancel_me() signal received')
+ self.close()
=== added file 'openlp/core/ui/projector/manager.py'
--- openlp/core/ui/projector/manager.py 1970-01-01 00:00:00 +0000
+++ openlp/core/ui/projector/manager.py 2014-10-28 21:04:17 +0000
@@ -0,0 +1,983 @@
+# -*- coding: utf-8 -*-
+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
+
+###############################################################################
+# OpenLP - Open Source Lyrics Projection #
+# --------------------------------------------------------------------------- #
+# Copyright (c) 2008-2014 Raoul Snyman #
+# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan #
+# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
+# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
+# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
+# Christian Richter, Philip Ridout, Ken Roberts, Simon Scudder, #
+# Jeffrey Smith, Maikel Stuivenberg, Martin Thompson, Jon Tibble, #
+# Dave Warnock, Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
+# --------------------------------------------------------------------------- #
+# This program is free software; you can redistribute it and/or modify it #
+# under the terms of the GNU General Public License as published by the Free #
+# Software Foundation; version 2 of the License. #
+# #
+# This program is distributed in the hope that it will be useful, but WITHOUT #
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
+# more details. #
+# #
+# You should have received a copy of the GNU General Public License along #
+# with this program; if not, write to the Free Software Foundation, Inc., 59 #
+# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
+###############################################################################
+"""
+ :mod: openlp.core.ui.projector.manager` module
+
+ Provides the functions for the display/control of Projectors.
+"""
+
+import logging
+log = logging.getLogger(__name__)
+log.debug('projectormanager loaded')
+
+from PyQt4 import QtCore, QtGui
+from PyQt4.QtCore import QObject, QThread, pyqtSlot
+from PyQt4.QtGui import QWidget
+
+from openlp.core.common import RegistryProperties, Settings, OpenLPMixin, \
+ RegistryMixin, translate
+from openlp.core.lib import OpenLPToolbar
+from openlp.core.lib.ui import create_widget_action
+from openlp.core.lib.projector import DialogSourceStyle
+from openlp.core.lib.projector.constants import *
+from openlp.core.lib.projector.db import ProjectorDB
+from openlp.core.lib.projector.pjlink1 import PJLink1
+from openlp.core.ui.projector.editform import ProjectorEditForm
+from openlp.core.ui.projector.sourceselectform import SourceSelectTabs, SourceSelectSingle
+
+# Dict for matching projector status to display icon
+STATUS_ICONS = {S_NOT_CONNECTED: ':/projector/projector_item_disconnect.png',
+ S_CONNECTING: ':/projector/projector_item_connect.png',
+ S_CONNECTED: ':/projector/projector_off.png',
+ S_OFF: ':/projector/projector_off.png',
+ S_INITIALIZE: ':/projector/projector_off.png',
+ S_STANDBY: ':/projector/projector_off.png',
+ S_WARMUP: ':/projector/projector_warmup.png',
+ S_ON: ':/projector/projector_on.png',
+ S_COOLDOWN: ':/projector/projector_cooldown.png',
+ E_ERROR: ':/projector/projector_error.png',
+ E_NETWORK: ':/projector/projector_not_connected_error.png',
+ E_AUTHENTICATION: ':/projector/projector_not_connected_error.png',
+ E_UNKNOWN_SOCKET_ERROR: ':/projector/projector_not_connected_error.png',
+ E_NOT_CONNECTED: ':/projector/projector_not_connected_error.png'
+ }
+
+
+class Ui_ProjectorManager(object):
+ """
+ UI part of the Projector Manager
+ """
+ def setup_ui(self, widget):
+ """
+ Define the UI
+
+ :param widget: The screen object the dialog is to be attached to.
+ """
+ log.debug('setup_ui()')
+ # Create ProjectorManager box
+ self.layout = QtGui.QVBoxLayout(widget)
+ self.layout.setSpacing(0)
+ self.layout.setMargin(0)
+ self.layout.setObjectName('layout')
+ # Add one selection toolbar
+ self.one_toolbar = OpenLPToolbar(widget)
+ self.one_toolbar.add_toolbar_action('new_projector',
+ text=translate('OpenLP.ProjectorManager', 'Add Projector'),
+ icon=':/projector/projector_new.png',
+ tooltip=translate('OpenLP.ProjectorManager', 'Add a new projector'),
+ triggers=self.on_add_projector)
+ # Show edit/delete when projector not connected
+ self.one_toolbar.add_toolbar_action('edit_projector',
+ text=translate('OpenLP.ProjectorManager', 'Edit Projector'),
+ icon=':/general/general_edit.png',
+ tooltip=translate('OpenLP.ProjectorManager', 'Edit selected projector'),
+ triggers=self.on_edit_projector)
+ self.one_toolbar.add_toolbar_action('delete_projector',
+ text=translate('OpenLP.ProjectorManager', 'Delete Projector'),
+ icon=':/general/general_delete.png',
+ tooltip=translate('OpenLP.ProjectorManager', 'Delete selected projector'),
+ triggers=self.on_delete_projector)
+ # Show source/view when projector connected
+ self.one_toolbar.add_toolbar_action('source_view_projector',
+ text=translate('OpenLP.ProjectorManager', 'Select Input Source'),
+ icon=':/projector/projector_hdmi.png',
+ tooltip=translate('OpenLP.ProjectorManager',
+ 'Choose input source on selected projector'),
+ triggers=self.on_select_input)
+ self.one_toolbar.add_toolbar_action('view_projector',
+ text=translate('OpenLP.ProjectorManager', 'View Projector'),
+ icon=':/system/system_about.png',
+ tooltip=translate('OpenLP.ProjectorManager',
+ 'View selected projector information'),
+ triggers=self.on_status_projector)
+ self.one_toolbar.addSeparator()
+ self.one_toolbar.add_toolbar_action('connect_projector',
+ text=translate('OpenLP.ProjectorManager',
+ 'Connect to selected projector'),
+ icon=':/projector/projector_connect.png',
+ tootip=translate('OpenLP.ProjectorManager',
+ 'Connect to selected projector'),
+ triggers=self.on_connect_projector)
+ self.one_toolbar.add_toolbar_action('connect_projector_multiple',
+ text=translate('OpenLP.ProjectorManager',
+ 'Connect to selected projectors'),
+ icon=':/projector/projector_connect_tiled.png',
+ tootip=translate('OpenLP.ProjectorManager',
+ 'Connect to selected projector'),
+ triggers=self.on_connect_projector)
+ self.one_toolbar.add_toolbar_action('disconnect_projector',
+ text=translate('OpenLP.ProjectorManager',
+ 'Disconnect from selected projectors'),
+ icon=':/projector/projector_disconnect.png',
+ tooltip=translate('OpenLP.ProjectorManager',
+ 'Disconnect from selected projector'),
+ triggers=self.on_disconnect_projector)
+ self.one_toolbar.add_toolbar_action('disconnect_projector_multiple',
+ text=translate('OpenLP.ProjectorManager',
+ 'Disconnect from selected projector'),
+ icon=':/projector/projector_disconnect_tiled.png',
+ tooltip=translate('OpenLP.ProjectorManager',
+ 'Disconnect from selected projector'),
+ triggers=self.on_disconnect_projector)
+ self.one_toolbar.addSeparator()
+ self.one_toolbar.add_toolbar_action('poweron_projector',
+ text=translate('OpenLP.ProjectorManager',
+ 'Power on selected projector'),
+ icon=':/projector/projector_power_on.png',
+ tooltip=translate('OpenLP.ProjectorManager',
+ 'Power on selected projector'),
+ triggers=self.on_poweron_projector)
+ self.one_toolbar.add_toolbar_action('poweron_projector_multiple',
+ text=translate('OpenLP.ProjectorManager',
+ 'Power on selected projector'),
+ icon=':/projector/projector_power_on_tiled.png',
+ tooltip=translate('OpenLP.ProjectorManager',
+ 'Power on selected projector'),
+ triggers=self.on_poweron_projector)
+ self.one_toolbar.add_toolbar_action('poweroff_projector',
+ text=translate('OpenLP.ProjectorManager', 'Standby selected projector'),
+ icon=':/projector/projector_power_off.png',
+ tooltip=translate('OpenLP.ProjectorManager',
+ 'Put selected projector in standby'),
+ triggers=self.on_poweroff_projector)
+ self.one_toolbar.add_toolbar_action('poweroff_projector_multiple',
+ text=translate('OpenLP.ProjectorManager', 'Standby selected projector'),
+ icon=':/projector/projector_power_off_tiled.png',
+ tooltip=translate('OpenLP.ProjectorManager',
+ 'Put selected projector in standby'),
+ triggers=self.on_poweroff_projector)
+ self.one_toolbar.addSeparator()
+ self.one_toolbar.add_toolbar_action('blank_projector',
+ text=translate('OpenLP.ProjectorManager',
+ 'Blank selected projector screen'),
+ icon=':/projector/projector_blank.png',
+ tooltip=translate('OpenLP.ProjectorManager',
+ 'Blank selected projector screen'),
+ triggers=self.on_blank_projector)
+ self.one_toolbar.add_toolbar_action('blank_projector_multiple',
+ text=translate('OpenLP.ProjectorManager',
+ 'Blank selected projector screen'),
+ icon=':/projector/projector_blank_tiled.png',
+ tooltip=translate('OpenLP.ProjectorManager',
+ 'Blank selected projector screen'),
+ triggers=self.on_blank_projector)
+ self.one_toolbar.add_toolbar_action('show_projector',
+ ext=translate('OpenLP.ProjectorManager',
+ 'Show selected projector screen'),
+ icon=':/projector/projector_show.png',
+ tooltip=translate('OpenLP.ProjectorManager',
+ 'Show selected projector screen'),
+ triggers=self.on_show_projector)
+ self.one_toolbar.add_toolbar_action('show_projector_multiple',
+ ext=translate('OpenLP.ProjectorManager',
+ 'Show selected projector screen'),
+ icon=':/projector/projector_show_tiled.png',
+ tooltip=translate('OpenLP.ProjectorManager',
+ 'Show selected projector screen'),
+ triggers=self.on_show_projector)
+ self.layout.addWidget(self.one_toolbar)
+ self.projector_one_widget = QtGui.QWidgetAction(self.one_toolbar)
+ self.projector_one_widget.setObjectName('projector_one_toolbar_widget')
+ # Create projector manager list
+ self.projector_list_widget = QtGui.QListWidget(widget)
+ self.projector_list_widget.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection)
+ self.projector_list_widget.setAlternatingRowColors(True)
+ self.projector_list_widget.setIconSize(QtCore.QSize(90, 50))
+ self.projector_list_widget.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
+ self.projector_list_widget.setObjectName('projector_list_widget')
+ self.layout.addWidget(self.projector_list_widget)
+ self.projector_list_widget.customContextMenuRequested.connect(self.context_menu)
+ self.projector_list_widget.itemDoubleClicked.connect(self.on_doubleclick_item)
+ # Build the context menu
+ self.menu = QtGui.QMenu()
+ self.status_action = create_widget_action(self.menu,
+ text=translate('OpenLP.ProjectorManager',
+ '&View Projector Information'),
+ icon=':/system/system_about.png',
+ triggers=self.on_status_projector)
+ self.edit_action = create_widget_action(self.menu,
+ text=translate('OpenLP.ProjectorManager',
+ '&Edit Projector'),
+ icon=':/projector/projector_edit.png',
+ triggers=self.on_edit_projector)
+ self.menu.addSeparator()
+ self.connect_action = create_widget_action(self.menu,
+ text=translate('OpenLP.ProjectorManager',
+ '&Connect Projector'),
+ icon=':/projector/projector_connect.png',
+ triggers=self.on_connect_projector)
+ self.disconnect_action = create_widget_action(self.menu,
+ text=translate('OpenLP.ProjectorManager',
+ 'D&isconnect Projector'),
+ icon=':/projector/projector_disconnect.png',
+ triggers=self.on_disconnect_projector)
+ self.menu.addSeparator()
+ self.poweron_action = create_widget_action(self.menu,
+ text=translate('OpenLP.ProjectorManager',
+ 'Power &On Projector'),
+ icon=':/projector/projector_power_on.png',
+ triggers=self.on_poweron_projector)
+ self.poweroff_action = create_widget_action(self.menu,
+ text=translate('OpenLP.ProjectorManager',
+ 'Power O&ff Projector'),
+ icon=':/projector/projector_power_off.png',
+ triggers=self.on_poweroff_projector)
+ self.menu.addSeparator()
+ self.select_input_action = create_widget_action(self.menu,
+ text=translate('OpenLP.ProjectorManager',
+ 'Select &Input'),
+ icon=':/projector/projector_hdmi.png',
+ triggers=self.on_select_input)
+ self.edit_input_action = create_widget_action(self.menu,
+ text=translate('OpenLP.ProjectorManager',
+ 'Edit Input Source'),
+ icon=':/general/general_edit.png',
+ triggers=self.on_edit_input)
+ self.blank_action = create_widget_action(self.menu,
+ text=translate('OpenLP.ProjectorManager',
+ '&Blank Projector Screen'),
+ icon=':/projector/projector_blank.png',
+ triggers=self.on_blank_projector)
+ self.show_action = create_widget_action(self.menu,
+ text=translate('OpenLP.ProjectorManager',
+ '&Show Projector Screen'),
+ icon=':/projector/projector_show.png',
+ triggers=self.on_show_projector)
+ self.menu.addSeparator()
+ self.delete_action = create_widget_action(self.menu,
+ text=translate('OpenLP.ProjectorManager',
+ '&Delete Projector'),
+ icon=':/general/general_delete.png',
+ triggers=self.on_delete_projector)
+ self.update_icons()
+
+
+class ProjectorManager(OpenLPMixin, RegistryMixin, QWidget, Ui_ProjectorManager, RegistryProperties):
+ """
+ Manage the projectors.
+ """
+ def __init__(self, parent=None, projectordb=None):
+ """
+ Basic initialization.
+
+ :param parent: Who I belong to.
+ :param projectordb: Database session inherited from superclass.
+ """
+ log.debug('__init__()')
+ super().__init__(parent)
+ self.settings_section = 'projector'
+ self.projectordb = projectordb
+ self.projector_list = []
+ self.source_select_form = None
+
+ def bootstrap_initialise(self):
+ """
+ Pre-initialize setups.
+ """
+ self.setup_ui(self)
+ if self.projectordb is None:
+ # Work around for testing creating a ~/.openlp.data.projector.projector.sql file
+ log.debug('Creating new ProjectorDB() instance')
+ self.projectordb = ProjectorDB()
+ else:
+ log.debug('Using existing ProjectorDB() instance')
+ self.get_settings()
+
+ def bootstrap_post_set_up(self):
+ """
+ Post-initialize setups.
+ """
+ # Set 1.5 second delay before loading all projectors
+ if self.autostart:
+ log.debug('Delaying 1.5 seconds before loading all projectors')
+ QtCore.QTimer().singleShot(1500, self._load_projectors)
+ else:
+ log.debug('Loading all projectors')
+ self._load_projectors()
+ self.projector_form = ProjectorEditForm(self, projectordb=self.projectordb)
+ self.projector_form.newProjector.connect(self.add_projector_from_wizard)
+ self.projector_form.editProjector.connect(self.edit_projector_from_wizard)
+ self.projector_list_widget.itemSelectionChanged.connect(self.update_icons)
+
+ def get_settings(self):
+ """
+ Retrieve the saved settings
+ """
+ settings = Settings()
+ settings.beginGroup(self.settings_section)
+ self.autostart = settings.value('connect on start')
+ self.poll_time = settings.value('poll time')
+ self.socket_timeout = settings.value('socket timeout')
+ self.source_select_dialog_type = settings.value('source dialog type')
+ settings.endGroup()
+ del settings
+
+ def context_menu(self, point):
+ """
+ Build the Right Click Context menu and set state.
+
+ :param point: The position of the mouse so the correct item can be found.
+ """
+ # QListWidgetItem to build menu for.
+ item = self.projector_list_widget.itemAt(point)
+ if item is None:
+ return
+ real_projector = item.data(QtCore.Qt.UserRole)
+ projector_name = str(item.text())
+ visible = real_projector.link.status_connect >= S_CONNECTED
+ log.debug('(%s) Building menu - visible = %s' % (projector_name, visible))
+ self.delete_action.setVisible(True)
+ self.edit_action.setVisible(True)
+ self.connect_action.setVisible(not visible)
+ self.disconnect_action.setVisible(visible)
+ self.status_action.setVisible(visible)
+ if visible:
+ self.select_input_action.setVisible(real_projector.link.power == S_ON)
+ self.edit_input_action.setVisible(real_projector.link.power == S_ON)
+ self.poweron_action.setVisible(real_projector.link.power == S_STANDBY)
+ self.poweroff_action.setVisible(real_projector.link.power == S_ON)
+ self.blank_action.setVisible(real_projector.link.power == S_ON and
+ not real_projector.link.shutter)
+ self.show_action.setVisible(real_projector.link.power == S_ON and
+ real_projector.link.shutter)
+ else:
+ self.select_input_action.setVisible(False)
+ self.edit_input_action.setVisible(False)
+ self.poweron_action.setVisible(False)
+ self.poweroff_action.setVisible(False)
+ self.blank_action.setVisible(False)
+ self.show_action.setVisible(False)
+ self.menu.projector = real_projector
+ self.menu.exec_(self.projector_list_widget.mapToGlobal(point))
+
+ def on_edit_input(self, opt=None):
+ self.on_select_input(opt=opt, edit=True)
+
+ def on_select_input(self, opt=None, edit=False):
+ """
+ Builds menu for 'Select Input' option, then calls the selected projector
+ item to change input source.
+
+ :param opt: Needed by PyQt4
+ """
+ self.get_settings() # In case the dialog interface setting was changed
+ list_item = self.projector_list_widget.item(self.projector_list_widget.currentRow())
+ projector = list_item.data(QtCore.Qt.UserRole)
+ # QTabwidget for source select
+ source = 100
+ while source > 99:
+ if self.source_select_dialog_type == DialogSourceStyle.Tabbed:
+ source_select_form = SourceSelectTabs(parent=self,
+ projectordb=self.projectordb,
+ edit=edit)
+ else:
+ source_select_form = SourceSelectSingle(parent=self,
+ projectordb=self.projectordb,
+ edit=edit)
+ source = source_select_form.exec_(projector.link)
+ log.debug('(%s) source_select_form() returned %s' % (projector.link.ip, source))
+ if source is not None and source > 0:
+ projector.link.set_input_source(str(source))
+ return
+
+ def on_add_projector(self, opt=None):
+ """
+ Calls edit dialog to add a new projector to the database
+
+ :param opt: Needed by PyQt4
+ """
+ self.projector_form.exec_()
+
+ def on_blank_projector(self, opt=None):
+ """
+ Calls projector thread to send blank screen command
+
+ :param opt: Needed by PyQt4
+ """
+ try:
+ ip = opt.link.ip
+ projector = opt
+ projector.link.set_shutter_closed()
+ except AttributeError:
+ for list_item in self.projector_list_widget.selectedItems():
+ if list_item is None:
+ return
+ projector = list_item.data(QtCore.Qt.UserRole)
+ try:
+ projector.link.set_shutter_closed()
+ except:
+ continue
+
+ def on_doubleclick_item(self, item, opt=None):
+ """
+ When item is doubleclicked, will connect to projector.
+
+ :param item: List widget item for connection.
+ :param opt: Needed by PyQt4
+ """
+ projector = item.data(QtCore.Qt.UserRole)
+ if projector.link.state() != projector.link.ConnectedState:
+ try:
+ projector.link.connect_to_host()
+ except:
+ pass
+ return
+
+ def on_connect_projector(self, opt=None):
+ """
+ Calls projector thread to connect to projector
+
+ :param opt: Needed by PyQt4
+ """
+ try:
+ ip = opt.link.ip
+ projector = opt
+ projector.link.connect_to_host()
+ except AttributeError:
+ for list_item in self.projector_list_widget.selectedItems():
+ if list_item is None:
+ return
+ projector = list_item.data(QtCore.Qt.UserRole)
+ try:
+ projector.link.connect_to_host()
+ except:
+ continue
+
+ def on_delete_projector(self, opt=None):
+ """
+ Deletes a projector from the list and the database
+
+ :param opt: Needed by PyQt4
+ """
+ list_item = self.projector_list_widget.item(self.projector_list_widget.currentRow())
+ if list_item is None:
+ return
+ projector = list_item.data(QtCore.Qt.UserRole)
+ msg = QtGui.QMessageBox()
+ msg.setText('Delete projector (%s) %s?' % (projector.link.ip, projector.link.name))
+ msg.setInformativeText('Are you sure you want to delete this projector?')
+ msg.setStandardButtons(msg.Cancel | msg.Ok)
+ msg.setDefaultButton(msg.Cancel)
+ ans = msg.exec_()
+ if ans == msg.Cancel:
+ return
+ try:
+ projector.link.projectorNetwork.disconnect(self.update_status)
+ except (AttributeError, TypeError):
+ pass
+ try:
+ projector.link.changeStatus.disconnect(self.update_status)
+ except (AttributeError, TypeError):
+ pass
+ try:
+ projector.link.authentication_error.disconnect(self.authentication_error)
+ except (AttributeError, TypeError):
+ pass
+ try:
+ projector.link.no_authentication_error.disconnect(self.no_authentication_error)
+ except (AttributeError, TypeError):
+ pass
+ try:
+ projector.link.projectorUpdateIcons.disconnect(self.update_icons)
+ except (AttributeError, TypeError):
+ pass
+ try:
+ projector.timer.stop()
+ projector.timer.timeout.disconnect(projector.link.poll_loop)
+ except (AttributeError, TypeError):
+ pass
+ try:
+ projector.socket_timer.stop()
+ projector.socket_timer.timeout.disconnect(projector.link.socket_abort)
+ except (AttributeError, TypeError):
+ pass
+ projector.thread.quit()
+ new_list = []
+ for item in self.projector_list:
+ if item.link.dbid == projector.link.dbid:
+ continue
+ new_list.append(item)
+ self.projector_list = new_list
+ list_item = self.projector_list_widget.takeItem(self.projector_list_widget.currentRow())
+ list_item = None
+ deleted = self.projectordb.delete_projector(projector.db_item)
+ for item in self.projector_list:
+ log.debug('New projector list - item: %s %s' % (item.link.ip, item.link.name))
+
+ def on_disconnect_projector(self, opt=None):
+ """
+ Calls projector thread to disconnect from projector
+
+ :param opt: Needed by PyQt4
+ """
+ try:
+ ip = opt.link.ip
+ projector = opt
+ projector.link.disconnect_from_host()
+ except AttributeError:
+ for list_item in self.projector_list_widget.selectedItems():
+ if list_item is None:
+ return
+ projector = list_item.data(QtCore.Qt.UserRole)
+ try:
+ projector.link.disconnect_from_host()
+ except:
+ continue
+
+ def on_edit_projector(self, opt=None):
+ """
+ Calls edit dialog with selected projector to edit information
+
+ :param opt: Needed by PyQt4
+ """
+ list_item = self.projector_list_widget.item(self.projector_list_widget.currentRow())
+ projector = list_item.data(QtCore.Qt.UserRole)
+ if projector is None:
+ return
+ self.old_projector = projector
+ projector.link.disconnect_from_host()
+ record = self.projectordb.get_projector_by_ip(projector.link.ip)
+ self.projector_form.exec_(record)
+ new_record = self.projectordb.get_projector_by_id(record.id)
+
+ def on_poweroff_projector(self, opt=None):
+ """
+ Calls projector link to send Power Off command
+
+ :param opt: Needed by PyQt4
+ """
+ try:
+ ip = opt.link.ip
+ projector = opt
+ projector.link.set_power_off()
+ except AttributeError:
+ for list_item in self.projector_list_widget.selectedItems():
+ if list_item is None:
+ return
+ projector = list_item.data(QtCore.Qt.UserRole)
+ try:
+ projector.link.set_power_off()
+ except:
+ continue
+
+ def on_poweron_projector(self, opt=None):
+ """
+ Calls projector link to send Power On command
+
+ :param opt: Needed by PyQt4
+ """
+ try:
+ ip = opt.link.ip
+ projector = opt
+ projector.link.set_power_on()
+ except AttributeError:
+ for list_item in self.projector_list_widget.selectedItems():
+ if list_item is None:
+ return
+ projector = list_item.data(QtCore.Qt.UserRole)
+ try:
+ projector.link.set_power_on()
+ except:
+ continue
+
+ def on_show_projector(self, opt=None):
+ """
+ Calls projector thread to send open shutter command
+
+ :param opt: Needed by PyQt4
+ """
+ try:
+ ip = opt.link.ip
+ projector = opt
+ projector.link.set_shutter_open()
+ except AttributeError:
+ for list_item in self.projector_list_widget.selectedItems():
+ if list_item is None:
+ return
+ projector = list_item.data(QtCore.Qt.UserRole)
+ try:
+ projector.link.set_shutter_open()
+ except:
+ continue
+
+ def on_status_projector(self, opt=None):
+ """
+ Builds message box with projector status information
+
+ :param opt: Needed by PyQt4
+ """
+ lwi = self.projector_list_widget.item(self.projector_list_widget.currentRow())
+ projector = lwi.data(QtCore.Qt.UserRole)
+ message = '<b>%s</b>: %s<BR />' % (translate('OpenLP.ProjectorManager', 'Name'),
+ projector.link.name)
+ message = '%s<b>%s</b>: %s<br />' % (message, translate('OpenLP.ProjectorManager', 'IP'),
+ projector.link.ip)
+ message = '%s<b>%s</b>: %s<br />' % (message, translate('OpenLP.ProjectorManager', 'Port'),
+ projector.link.port)
+ message = '%s<b>%s</b>: %s<br />' % (message, translate('OpenLP.ProjectorManager', 'Notes'),
+ projector.link.notes)
+ message = '%s<hr /><br >' % message
+ if projector.link.manufacturer is None:
+ message = '%s%s' % (message, translate('OpenLP.ProjectorManager',
+ 'Projector information not available at this time.'))
+ else:
+ message = '%s<b>%s</b>: %s<BR />' % (message, translate('OpenLP.ProjectorManager', 'Projector Name'),
+ projector.link.pjlink_name)
+ message = '%s<b>%s</b>: %s<br />' % (message, translate('OpenLP.ProjectorManager', 'Manufacturer'),
+ projector.link.manufacturer)
+ message = '%s<b>%s</b>: %s<br />' % (message, translate('OpenLP.ProjectorManager', 'Model'),
+ projector.link.model)
+ message = '%s<b>%s</b>: %s<br /><br />' % (message, translate('OpenLP.ProjectorManager', 'Other info'),
+ projector.link.other_info)
+ message = '%s<b>%s</b>: %s<br />' % (message, translate('OpenLP.ProjectorManager', 'Power status'),
+ ERROR_MSG[projector.link.power])
+ message = '%s<b>%s</b>: %s<br />' % (message, translate('OpenLP.ProjectorManager', 'Shutter is'),
+ translate('OpenLP.ProjectorManager', 'Closed')
+ if projector.link.shutter else translate('OpenLP', 'Open'))
+ message = '%s<b>%s</b>: %s<br />' % (message,
+ translate('OpenLP.ProjectorManager', 'Current source input is'),
+ projector.link.source)
+ count = 1
+ for item in projector.link.lamp:
+ message = '%s <b>%s %s</b> (%s) %s: %s<br />' % (message,
+ translate('OpenLP.ProjectorManager', 'Lamp'),
+ count,
+ translate('OpenLP.ProjectorManager', 'On')
+ if item['On']
+ else translate('OpenLP.ProjectorManager', 'Off'),
+ translate('OpenLP.ProjectorManager', 'Hours'),
+ item['Hours'])
+ count = count + 1
+ message = '%s<hr /><br />' % message
+ if projector.link.projector_errors is None:
+ message = '%s%s' % (message, translate('OpenLP.ProjectorManager', 'No current errors or warnings'))
+ else:
+ message = '%s<b>%s</b>' % (message, translate('OpenLP.ProjectorManager', 'Current errors/warnings'))
+ for (key, val) in projector.link.projector_errors.items():
+ message = '%s<b>%s</b>: %s<br />' % (message, key, ERROR_MSG[val])
+ QtGui.QMessageBox.information(self, translate('OpenLP.ProjectorManager', 'Projector Information'), message)
+
+ def _add_projector(self, projector):
+ """
+ Helper app to build a projector instance
+
+ :param projector: Dict of projector database information
+ :returns: PJLink1() instance
+ """
+ log.debug('_add_projector()')
+ return PJLink1(dbid=projector.id,
+ ip=projector.ip,
+ port=int(projector.port),
+ name=projector.name,
+ location=projector.location,
+ notes=projector.notes,
+ pin=None if projector.pin == '' else projector.pin,
+ poll_time=self.poll_time,
+ socket_timeout=self.socket_timeout
+ )
+
+ def add_projector(self, projector, start=False):
+ """
+ Builds manager list item, projector thread, and timer for projector instance.
+
+
+ :param projector: Projector instance to add
+ :param start: Start projector if True
+ """
+ item = ProjectorItem(link=self._add_projector(projector))
+ item.db_item = projector
+ icon = QtGui.QIcon(QtGui.QPixmap(STATUS_ICONS[S_NOT_CONNECTED]))
+ item.icon = icon
+ widget = QtGui.QListWidgetItem(icon,
+ item.link.name,
+ self.projector_list_widget
+ )
+ widget.setData(QtCore.Qt.UserRole, item)
+ item.link.db_item = item.db_item
+ item.widget = widget
+ thread = QThread(parent=self)
+ thread.my_parent = self
+ item.moveToThread(thread)
+ thread.started.connect(item.link.thread_started)
+ thread.finished.connect(item.link.thread_stopped)
+ thread.finished.connect(thread.deleteLater)
+ item.link.projectorNetwork.connect(self.update_status)
+ item.link.changeStatus.connect(self.update_status)
+ item.link.projectorAuthentication.connect(self.authentication_error)
+ item.link.projectorNoAuthentication.connect(self.no_authentication_error)
+ item.link.projectorUpdateIcons.connect(self.update_icons)
+ timer = QtCore.QTimer(self)
+ timer.setInterval(self.poll_time)
+ timer.timeout.connect(item.link.poll_loop)
+ item.timer = timer
+ # Timeout in case of brain-dead projectors or cable disconnected
+ socket_timer = QtCore.QTimer(self)
+ socket_timer.setInterval(11000)
+ socket_timer.timeout.connect(item.link.socket_abort)
+ item.socket_timer = socket_timer
+ thread.start()
+ item.thread = thread
+ item.link.timer = timer
+ item.link.socket_timer = socket_timer
+ item.link.widget = item.widget
+ self.projector_list.append(item)
+ if start:
+ item.link.connect_to_host()
+ for item in self.projector_list:
+ log.debug('New projector list - item: (%s) %s' % (item.link.ip, item.link.name))
+
+ @pyqtSlot(str)
+ def add_projector_from_wizard(self, ip, opts=None):
+ """
+ Add a projector from the edit dialog
+
+ :param ip: IP address of new record item to find
+ :param opts: Needed by PyQt4
+ """
+ log.debug('add_projector_from_wizard(ip=%s)' % ip)
+ item = self.projectordb.get_projector_by_ip(ip)
+ self.add_projector(item)
+
+ @pyqtSlot(object)
+ def edit_projector_from_wizard(self, projector):
+ """
+ Update projector from the wizard edit page
+
+ :param projector: Projector() instance of projector with updated information
+ """
+ log.debug('edit_projector_from_wizard(ip=%s)' % projector.ip)
+ self.old_projector.link.name = projector.name
+ self.old_projector.link.ip = projector.ip
+ self.old_projector.link.pin = None if projector.pin == '' else projector.pin
+ self.old_projector.link.port = projector.port
+ self.old_projector.link.location = projector.location
+ self.old_projector.link.notes = projector.notes
+ self.old_projector.widget.setText(projector.name)
+
+ def _load_projectors(self):
+ """'
+ Load projectors - only call when initializing
+ """
+ log.debug('_load_projectors()')
+ self.projector_list_widget.clear()
+ for item in self.projectordb.get_projector_all():
+ self.add_projector(projector=item, start=self.autostart)
+
+ def get_projector_list(self):
+ """
+ Return the list of active projectors
+
+ :returns: list
+ """
+ return self.projector_list
+
+ @pyqtSlot(str, int, str)
+ def update_status(self, ip, status=None, msg=None):
+ """
+ Update the status information/icon for selected list item
+
+ :param ip: IP address of projector
+ :param status: Optional status code
+ :param msg: Optional status message
+ """
+ if status is None:
+ return
+ item = None
+ for list_item in self.projector_list:
+ if ip == list_item.link.ip:
+ item = list_item
+ break
+ message = translate('OpenLP.ProjectorManager', 'No message') if msg is None else msg
+ if status in STATUS_STRING:
+ status_code = STATUS_STRING[status]
+ message = ERROR_MSG[status] if msg is None else msg
+ elif status in ERROR_STRING:
+ status_code = ERROR_STRING[status]
+ message = ERROR_MSG[status] if msg is None else msg
+ else:
+ status_code = status
+ message = ERROR_MSG[status] if msg is None else msg
+ log.debug('(%s) updateStatus(status=%s) message: "%s"' % (item.link.name, status_code, message))
+ if status in STATUS_ICONS:
+ if item.status == status:
+ return
+ item.status = status
+ item.icon = QtGui.QIcon(QtGui.QPixmap(STATUS_ICONS[status]))
+ if status in ERROR_STRING:
+ status_code = ERROR_STRING[status]
+ elif status in STATUS_STRING:
+ status_code = STATUS_STRING[status]
+ log.debug('(%s) Updating icon with %s' % (item.link.name, status_code))
+ item.widget.setIcon(item.icon)
+ self.update_icons()
+
+ def get_toolbar_item(self, name, enabled=False, hidden=False):
+ item = self.one_toolbar.findChild(QtGui.QAction, name)
+ if item == 0:
+ log.debug('No item found with name "%s"' % name)
+ return
+ item.setVisible(False if hidden else True)
+ item.setEnabled(True if enabled else False)
+
+ @pyqtSlot()
+ def update_icons(self):
+ """
+ Update the icons when the selected projectors change
+ """
+ count = len(self.projector_list_widget.selectedItems())
+ projector = None
+ if count == 0:
+ self.get_toolbar_item('edit_projector')
+ self.get_toolbar_item('delete_projector')
+ self.get_toolbar_item('view_projector', hidden=True)
+ self.get_toolbar_item('source_view_projector', hidden=True)
+ self.get_toolbar_item('connect_projector')
+ self.get_toolbar_item('disconnect_projector')
+ self.get_toolbar_item('poweron_projector')
+ self.get_toolbar_item('poweroff_projector')
+ self.get_toolbar_item('blank_projector')
+ self.get_toolbar_item('show_projector')
+ self.get_toolbar_item('connect_projector_multiple', hidden=True)
+ self.get_toolbar_item('disconnect_projector_multiple', hidden=True)
+ self.get_toolbar_item('poweron_projector_multiple', hidden=True)
+ self.get_toolbar_item('poweroff_projector_multiple', hidden=True)
+ self.get_toolbar_item('blank_projector_multiple', hidden=True)
+ self.get_toolbar_item('show_projector_multiple', hidden=True)
+ elif count == 1:
+ projector = self.projector_list_widget.selectedItems()[0].data(QtCore.Qt.UserRole)
+ connected = projector.link.state() == projector.link.ConnectedState
+ power = projector.link.power == S_ON
+ self.get_toolbar_item('connect_projector_multiple', hidden=True)
+ self.get_toolbar_item('disconnect_projector_multiple', hidden=True)
+ self.get_toolbar_item('poweron_projector_multiple', hidden=True)
+ self.get_toolbar_item('poweroff_projector_multiple', hidden=True)
+ self.get_toolbar_item('blank_projector_multiple', hidden=True)
+ self.get_toolbar_item('show_projector_multiple', hidden=True)
+ if connected:
+ self.get_toolbar_item('view_projector', enabled=True)
+ self.get_toolbar_item('source_view_projector',
+ enabled=connected and power and projector.link.source_available is not None)
+ self.get_toolbar_item('edit_projector', hidden=True)
+ self.get_toolbar_item('delete_projector', hidden=True)
+ else:
+ self.get_toolbar_item('view_projector', hidden=True)
+ self.get_toolbar_item('source_view_projector', hidden=True)
+ self.get_toolbar_item('edit_projector', enabled=True)
+ self.get_toolbar_item('delete_projector', enabled=True)
+ self.get_toolbar_item('connect_projector', enabled=not connected)
+ self.get_toolbar_item('disconnect_projector', enabled=connected)
+ self.get_toolbar_item('poweron_projector', enabled=connected and (projector.link.power == S_STANDBY))
+ self.get_toolbar_item('poweroff_projector', enabled=connected and (projector.link.power == S_ON))
+ if projector.link.shutter is not None:
+ self.get_toolbar_item('blank_projector', enabled=(connected and power and not projector.link.shutter))
+ self.get_toolbar_item('show_projector', enabled=(connected and power and projector.link.shutter))
+ else:
+ self.get_toolbar_item('blank_projector', enabled=False)
+ self.get_toolbar_item('show_projector', enabled=False)
+ else:
+ self.get_toolbar_item('edit_projector', enabled=False)
+ self.get_toolbar_item('delete_projector', enabled=False)
+ self.get_toolbar_item('view_projector', hidden=True)
+ self.get_toolbar_item('source_view_projector', hidden=True)
+ self.get_toolbar_item('connect_projector', hidden=True)
+ self.get_toolbar_item('disconnect_projector', hidden=True)
+ self.get_toolbar_item('poweron_projector', hidden=True)
+ self.get_toolbar_item('poweroff_projector', hidden=True)
+ self.get_toolbar_item('blank_projector', hidden=True)
+ self.get_toolbar_item('show_projector', hidden=True)
+ self.get_toolbar_item('connect_projector_multiple', hidden=False, enabled=True)
+ self.get_toolbar_item('disconnect_projector_multiple', hidden=False, enabled=True)
+ self.get_toolbar_item('poweron_projector_multiple', hidden=False, enabled=True)
+ self.get_toolbar_item('poweroff_projector_multiple', hidden=False, enabled=True)
+ self.get_toolbar_item('blank_projector_multiple', hidden=False, enabled=True)
+ self.get_toolbar_item('show_projector_multiple', hidden=False, enabled=True)
+
+ @pyqtSlot(str)
+ def authentication_error(self, name):
+ """
+ Display warning dialog when attempting to connect with invalid pin
+
+ :param name: Name from QListWidgetItem
+ """
+ QtGui.QMessageBox.warning(self, translate('OpenLP.ProjectorManager',
+ '"%s" Authentication Error' % name),
+ '<br />There was an authentication error while trying to connect.'
+ '<br /><br />Please verify your PIN setting '
+ 'for projector item "%s"' % name)
+
+ @pyqtSlot(str)
+ def no_authentication_error(self, name):
+ """
+ Display warning dialog when pin saved for item but projector does not
+ require pin.
+
+ :param name: Name from QListWidgetItem
+ """
+ QtGui.QMessageBox.warning(self, translate('OpenLP.ProjectorManager',
+ '"%s" No Authentication Error' % name),
+ '<br />PIN is set and projector does not require authentication.'
+ '<br /><br />Please verify your PIN setting '
+ 'for projector item "%s"' % name)
+
+
+class ProjectorItem(QObject):
+ """
+ Class for the projector list widget item.
+ NOTE: Actual PJLink class instance should be saved as self.link
+ """
+ def __init__(self, link=None):
+ """
+ Initialization for ProjectorItem instance
+
+ :param link: PJLink1 instance for QListWidgetItem
+ """
+ self.link = link
+ self.thread = None
+ self.icon = None
+ self.widget = None
+ self.my_parent = None
+ self.timer = None
+ self.projectordb_item = None
+ self.poll_time = None
+ self.socket_timeout = None
+ self.status = S_NOT_CONNECTED
+ super(ProjectorItem, self).__init__()
+
+
+def not_implemented(function):
+ """
+ Temporary function to build an information message box indicating function not implemented yet
+
+ :param func: Function name
+ """
+ QtGui.QMessageBox.information(None,
+ translate('OpenLP.ProjectorManager', 'Not Implemented Yet'),
+ translate('OpenLP.ProjectorManager',
+ 'Function "%s"<br />has not been implemented yet.'
+ '<br />Please check back again later.' % function))
=== added file 'openlp/core/ui/projector/sourceselectform.py'
--- openlp/core/ui/projector/sourceselectform.py 1970-01-01 00:00:00 +0000
+++ openlp/core/ui/projector/sourceselectform.py 2014-10-28 21:04:17 +0000
@@ -0,0 +1,500 @@
+# -*- coding: utf-8 -*-
+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
+
+###############################################################################
+# OpenLP - Open Source Lyrics Projection #
+# --------------------------------------------------------------------------- #
+# Copyright (c) 2008-2014 Raoul Snyman #
+# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan #
+# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
+# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
+# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
+# Christian Richter, Philip Ridout, Ken Roberts, Simon Scudder, #
+# Jeffrey Smith, Maikel Stuivenberg, Martin Thompson, Jon Tibble, #
+# Dave Warnock, Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
+# --------------------------------------------------------------------------- #
+# This program is free software; you can redistribute it and/or modify it #
+# under the terms of the GNU General Public License as published by the Free #
+# Software Foundation; version 2 of the License. #
+# #
+# This program is distributed in the hope that it will be useful, but WITHOUT #
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
+# more details. #
+# #
+# You should have received a copy of the GNU General Public License along #
+# with this program; if not, write to the Free Software Foundation, Inc., 59 #
+# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
+###############################################################################
+"""
+ :mod: `openlp.core.ui.projector.sourceselectform` module
+
+ Provides the dialog window for selecting video source for projector.
+"""
+import logging
+log = logging.getLogger(__name__)
+log.debug('editform loaded')
+
+from PyQt4 import QtCore, QtGui
+from PyQt4.QtCore import pyqtSlot, QSize
+from PyQt4.QtGui import QDialog, QButtonGroup, QDialogButtonBox, QFormLayout, QLineEdit, QRadioButton, \
+ QStyle, QStylePainter, QStyleOptionTab, QTabBar, QTabWidget, QVBoxLayout, QWidget
+
+from openlp.core.common import translate, is_macosx
+from openlp.core.lib import build_icon
+from openlp.core.lib.projector.db import ProjectorSource
+from openlp.core.lib.projector.constants import PJLINK_DEFAULT_SOURCES, PJLINK_DEFAULT_CODES
+
+
+def source_group(inputs, source_text):
+ """
+ Return a dictionary where key is source[0] and values are inputs
+ grouped by source[0].
+
+ source_text = dict{"key1": "key1-text",
+ "key2": "key2-text",
+ ...}
+ return:
+ dict{ key1[0]: { "key11": "key11-text",
+ "key12": "key12-text",
+ "key13": "key13-text",
+ ... }
+ key2[0]: {"key21": "key21-text",
+ "key22": "key22-text",
+ ... }
+
+ :param inputs: List of inputs
+ :param source_text: Dictionary of {code: text} values to display
+ :returns: dict
+ """
+ groupdict = {}
+ keydict = {}
+ checklist = inputs
+ key = checklist[0][0]
+ for item in checklist:
+ if item[0] == key:
+ groupdict[item] = source_text[item]
+ continue
+ else:
+ keydict[key] = groupdict
+ key = item[0]
+ groupdict = {item: source_text[item]}
+ keydict[key] = groupdict
+ return keydict
+
+
+def Build_Tab(group, source_key, default, projector, projectordb, edit=False):
+ """
+ Create the radio button page for a tab.
+ Dictionary will be a 1-key entry where key=tab to setup, val=list of inputs.
+
+ source_key: {"groupkey1": {"key11": "key11-text",
+ "key12": "key12-text",
+ ...
+ },
+ "groupkey2": {"key21": "key21-text",
+ "key22": "key22-text",
+ ....
+ },
+ ...
+ }
+
+ :param group: Button group widget to add buttons to
+ :param source_key: Dictionary of sources for radio buttons
+ :param default: Default radio button to check
+ :param projector: Projector instance
+ :param projectordb: ProjectorDB instance for session
+ :param edit: If we're editing the source text
+ """
+ buttonchecked = False
+ widget = QWidget()
+ layout = QFormLayout() if edit else QVBoxLayout()
+ layout.setSpacing(10)
+ widget.setLayout(layout)
+ tempkey = list(source_key.keys())[0] # Should only be 1 key
+ sourcelist = list(source_key[tempkey])
+ sourcelist.sort()
+ button_count = len(sourcelist)
+ if edit:
+ for key in sourcelist:
+ item = QLineEdit()
+ item.setObjectName('source_key_%s' % key)
+ source_item = projectordb.get_source_by_code(code=key, projector_id=projector.db_item.id)
+ if source_item is None:
+ item.setText(PJLINK_DEFAULT_CODES[key])
+ else:
+ item.setText(source_item.text)
+ layout.addRow(PJLINK_DEFAULT_CODES[key], item)
+ group.append(item)
+ else:
+ for key in sourcelist:
+ source_item = projectordb.get_source_by_code(code=key, projector_id=projector.db_item.id)
+ if source_item is None:
+ text = source_key[tempkey][key]
+ else:
+ text = source_item.text
+ itemwidget = QRadioButton(text)
+ itemwidget.setAutoExclusive(True)
+ if default == key:
+ itemwidget.setChecked(True)
+ buttonchecked = itemwidget.isChecked() or buttonchecked
+ group.addButton(itemwidget, int(key))
+ layout.addWidget(itemwidget)
+ layout.addStretch()
+ return widget, button_count, buttonchecked
+
+
+def set_button_tooltip(bar):
+ """
+ Set the toolip for the standard buttons used
+
+ :param bar: QDialogButtonBar instance to update
+ """
+ for button in bar.buttons():
+ if bar.standardButton(button) == QDialogButtonBox.Cancel:
+ tip = "Ignoring current changes and return to OpenLP"
+ elif bar.standardButton(button) == QDialogButtonBox.Reset:
+ tip = "Delete all user-defined text and revert to PJLink default text"
+ elif bar.standardButton(button) == QDialogButtonBox.Discard:
+ tip = "Discard changes and reset to previous user-defined text"
+ elif bar.standardButton(button) == QDialogButtonBox.Ok:
+ tip = "Save changes and return to OpenLP"
+ else:
+ tip = ""
+ button.setToolTip(tip)
+
+
+class FingerTabBarWidget(QTabBar):
+ """
+ Realign west -orientation tabs to left-right text rather than south-north text
+ Borrowed from
+ http://www.kidstrythisathome.com/2013/03/fingertabs-horizontal-tabs-with-horizontal-text-in-pyqt/
+ """
+ def __init__(self, parent=None, *args, **kwargs):
+ """
+ Reset tab text orientation on initialization
+
+ :param width: Remove default width parameter in kwargs
+ :param height: Remove default height parameter in kwargs
+ """
+ self.tabSize = QSize(kwargs.pop('width', 100), kwargs.pop('height', 25))
+ QTabBar.__init__(self, parent, *args, **kwargs)
+
+ def paintEvent(self, event):
+ """
+ Repaint tab in left-right text orientation.
+
+ :param event: Repaint event signal
+ """
+ painter = QStylePainter(self)
+ option = QStyleOptionTab()
+
+ for index in range(self.count()):
+ self.initStyleOption(option, index)
+ tabRect = self.tabRect(index)
+ tabRect.moveLeft(10)
+ painter.drawControl(QStyle.CE_TabBarTabShape, option)
+ painter.drawText(tabRect, QtCore.Qt.AlignVCenter |
+ QtCore.Qt.TextDontClip,
+ self.tabText(index))
+ painter.end()
+
+ def tabSizeHint(self, index):
+ """
+ Return tabsize
+
+ :param index: Tab index to fetch tabsize from
+ :returns: instance tabSize
+ """
+ return self.tabSize
+
+
+class FingerTabWidget(QTabWidget):
+ """
+ A QTabWidget equivalent which uses our FingerTabBarWidget
+
+ Based on thread discussion
+ http://www.riverbankcomputing.com/pipermail/pyqt/2005-December/011724.html
+ """
+ def __init__(self, parent, *args):
+ """
+ Initialize FingerTabWidget instance
+ """
+ QTabWidget.__init__(self, parent, *args)
+ self.setTabBar(FingerTabBarWidget(self))
+
+
+class SourceSelectTabs(QDialog):
+ """
+ Class for handling selecting the source for the projector to use.
+ Uses tabbed interface.
+ """
+ def __init__(self, parent, projectordb, edit=False):
+ """
+ Build the source select dialog using tabbed interface.
+
+ :param projectordb: ProjectorDB session to use
+ """
+ log.debug('Initializing SourceSelectTabs()')
+ super(SourceSelectTabs, self).__init__(parent)
+ self.projectordb = projectordb
+ self.edit = edit
+ if self.edit:
+ title = translate('OpenLP.SourceSelectForm', 'Select Projector Source')
+ else:
+ title = translate('OpenLP.SourceSelectForm', 'Edit Projector Source Text')
+ self.setWindowTitle(title)
+ self.setObjectName('source_select_tabs')
+ self.setWindowIcon(build_icon(':/icon/openlp-log-32x32.png'))
+ self.setModal(True)
+ self.layout = QVBoxLayout()
+ self.layout.setObjectName('source_select_tabs_layout')
+ if is_macosx():
+ self.tabwidget = QTabWidget(self)
+ else:
+ self.tabwidget = FingerTabWidget(self)
+ self.tabwidget.setObjectName('source_select_tabs_tabwidget')
+ self.tabwidget.setUsesScrollButtons(False)
+ if is_macosx():
+ self.tabwidget.setTabPosition(QTabWidget.North)
+ else:
+ self.tabwidget.setTabPosition(QTabWidget.West)
+ self.layout.addWidget(self.tabwidget)
+ self.setLayout(self.layout)
+
+ def exec_(self, projector):
+ """
+ Override initial method so we can build the tabs.
+
+ :param projector: Projector instance to build source list from
+ """
+ self.projector = projector
+ self.source_text = self.projectordb.get_source_list(projector=projector)
+ self.source_group = source_group(projector.source_available, self.source_text)
+ # self.source_group = {'4': {'41': 'Storage 1'}, '5': {"51": 'Network 1'}}
+ self.button_group = [] if self.edit else QButtonGroup()
+ keys = list(self.source_group.keys())
+ keys.sort()
+ if self.edit:
+ for key in keys:
+ (tab, button_count, buttonchecked) = Build_Tab(group=self.button_group,
+ source_key={key: self.source_group[key]},
+ default=self.projector.source,
+ projector=self.projector,
+ projectordb=self.projectordb,
+ edit=self.edit)
+ thistab = self.tabwidget.addTab(tab, PJLINK_DEFAULT_SOURCES[key])
+ if buttonchecked:
+ self.tabwidget.setCurrentIndex(thistab)
+ else:
+ for key in keys:
+ (tab, button_count, buttonchecked) = Build_Tab(group=self.button_group,
+ source_key={key: self.source_group[key]},
+ default=self.projector.source,
+ projector=self.projector,
+ projectordb=self.projectordb,
+ edit=self.edit)
+ thistab = self.tabwidget.addTab(tab, PJLINK_DEFAULT_SOURCES[key])
+ if buttonchecked:
+ self.tabwidget.setCurrentIndex(thistab)
+ self.button_box = QDialogButtonBox(QtGui.QDialogButtonBox.Reset |
+ QtGui.QDialogButtonBox.Discard |
+ QtGui.QDialogButtonBox.Ok |
+ QtGui.QDialogButtonBox.Cancel)
+ self.button_box.clicked.connect(self.button_clicked)
+ self.layout.addWidget(self.button_box)
+ set_button_tooltip(self.button_box)
+ selected = super(SourceSelectTabs, self).exec_()
+ return selected
+
+ @pyqtSlot(object)
+ def button_clicked(self, button):
+ """
+ Checks which button was clicked
+
+ :param button: Button that was clicked
+ :returns: Ok: Saves current edits
+ Delete: Resets text to last-saved text
+ Reset: Reset all text to PJLink default text
+ Cancel: Cancel text edit
+ """
+ if self.button_box.standardButton(button) == self.button_box.Cancel:
+ self.done(0)
+ elif self.button_box.standardButton(button) == self.button_box.Reset:
+ self.delete_sources()
+ elif self.button_box.standardButton(button) == self.button_box.Discard:
+ self.done(100)
+ elif self.button_box.standardButton(button) == self.button_box.Ok:
+ return self.accept_me()
+ else:
+ return 100
+
+ def delete_sources(self):
+ msg = QtGui.QMessageBox()
+ msg.setText('Delete entries for this projector')
+ msg.setInformativeText('Are you sure you want to delete ALL user-defined '
+ 'source input text for this projector?')
+ msg.setStandardButtons(msg.Cancel | msg.Ok)
+ msg.setDefaultButton(msg.Cancel)
+ ans = msg.exec_()
+ if ans == msg.Cancel:
+ return
+ self.projectordb.delete_all_objects(ProjectorSource, ProjectorSource.projector_id == self.projector.db_item.id)
+ self.done(100)
+
+ def accept_me(self):
+ """
+ Slot to accept 'OK' button
+ """
+ projector = self.projector.db_item
+ if self.edit:
+ for key in self.button_group:
+ code = key.objectName().split("_")[-1]
+ text = key.text().strip()
+ if key.text().strip().lower() == PJLINK_DEFAULT_CODES[code].strip().lower():
+ continue
+ item = self.projectordb.get_source_by_code(code=code, projector_id=projector.id)
+ if item is None:
+ log.debug("(%s) Adding new source text %s: %s" % (projector.ip, code, text))
+ item = ProjectorSource(projector_id=projector.id, code=code, text=text)
+ else:
+ item.text = text
+ log.debug('(%s) Updating source code %s with text="%s"' % (projector.ip, item.code, item.text))
+ self.projectordb.add_source(item)
+ selected = 0
+ else:
+ selected = self.button_group.checkedId()
+ log.debug('SourceSelectTabs().accepted() Setting source to %s' % selected)
+ self.done(selected)
+
+
+class SourceSelectSingle(QDialog):
+ """
+ Class for handling selecting the source for the projector to use.
+ Uses single dialog interface.
+ """
+ def __init__(self, parent, projectordb, edit=False):
+ """
+ Build the source select dialog.
+
+ :param projectordb: ProjectorDB session to use
+ """
+ log.debug('Initializing SourceSelectSingle()')
+ self.projectordb = projectordb
+ super(SourceSelectSingle, self).__init__(parent)
+ self.setWindowTitle(translate('OpenLP.SourceSelectSingle', 'Select Projector Source'))
+ self.setObjectName('source_select_single')
+ self.setWindowIcon(build_icon(':/icon/openlp-log-32x32.png'))
+ self.setModal(True)
+ self.edit = edit
+
+ def exec_(self, projector, edit=False):
+ """
+ Override initial method so we can build the tabs.
+
+ :param projector: Projector instance to build source list from
+ """
+ self.projector = projector
+ self.layout = QFormLayout() if self.edit else QVBoxLayout()
+ self.layout.setObjectName('source_select_tabs_layout')
+ self.layout.setSpacing(10)
+ self.setLayout(self.layout)
+ self.setMinimumWidth(350)
+ self.button_group = [] if self.edit else QButtonGroup()
+ self.source_text = self.projectordb.get_source_list(projector=projector)
+ keys = list(self.source_text.keys())
+ keys.sort()
+ key_count = len(keys)
+ button_list = []
+ if self.edit:
+ for key in keys:
+ item = QLineEdit()
+ item.setObjectName('source_key_%s' % key)
+ source_item = self.projectordb.get_source_by_code(code=key, projector_id=self.projector.db_item.id)
+ if source_item is None:
+ item.setText(PJLINK_DEFAULT_CODES[key])
+ else:
+ item.old_text = item.text()
+ item.setText(source_item.text)
+ self.layout.addRow(PJLINK_DEFAULT_CODES[key], item)
+ self.button_group.append(item)
+ else:
+ for key in keys:
+ source_text = self.projectordb.get_source_by_code(code=key, projector_id=self.projector.db_item.id)
+ text = self.source_text[key] if source_text is None else source_text.text
+ button = QtGui.QRadioButton(text)
+ button.setChecked(True if key == projector.source else False)
+ self.layout.addWidget(button)
+ self.button_group.addButton(button, int(key))
+ button_list.append(key)
+ self.button_box = QDialogButtonBox(QtGui.QDialogButtonBox.Reset |
+ QtGui.QDialogButtonBox.Discard |
+ QtGui.QDialogButtonBox.Ok |
+ QtGui.QDialogButtonBox.Cancel)
+ self.button_box.clicked.connect(self.button_clicked)
+ self.layout.addWidget(self.button_box)
+ self.setMinimumHeight(key_count*25)
+ set_button_tooltip(self.button_box)
+ selected = super(SourceSelectSingle, self).exec_()
+ return selected
+
+ @pyqtSlot(object)
+ def button_clicked(self, button):
+ """
+ Checks which button was clicked
+
+ :param button: Button that was clicked
+ :returns: Ok: Saves current edits
+ Delete: Resets text to last-saved text
+ Reset: Reset all text to PJLink default text
+ Cancel: Cancel text edit
+ """
+ if self.button_box.standardButton(button) == self.button_box.Cancel:
+ self.done(0)
+ elif self.button_box.standardButton(button) == self.button_box.Reset:
+ self.delete_sources()
+ elif self.button_box.standardButton(button) == self.button_box.Discard:
+ self.done(100)
+ elif self.button_box.standardButton(button) == self.button_box.Ok:
+ return self.accept_me()
+ else:
+ return 100
+
+ def delete_sources(self):
+ msg = QtGui.QMessageBox()
+ msg.setText('Delete entries for this projector')
+ msg.setInformativeText('Are you sure you want to delete ALL user-defined '
+ 'source input text for this projector?')
+ msg.setStandardButtons(msg.Cancel | msg.Ok)
+ msg.setDefaultButton(msg.Cancel)
+ ans = msg.exec_()
+ if ans == msg.Cancel:
+ return
+ self.projectordb.delete_all_objects(ProjectorSource, ProjectorSource.projector_id == self.projector.db_item.id)
+ self.done(100)
+
+ @pyqtSlot()
+ def accept_me(self):
+ """
+ Slot to accept 'OK' button
+ """
+ projector = self.projector.db_item
+ if self.edit:
+ for key in self.button_group:
+ code = key.objectName().split("_")[-1]
+ text = key.text().strip()
+ if key.text().strip().lower() == PJLINK_DEFAULT_CODES[code].strip().lower():
+ continue
+ item = self.projectordb.get_source_by_code(code=code, projector_id=projector.id)
+ if item is None:
+ log.debug("(%s) Adding new source text %s: %s" % (projector.ip, code, text))
+ item = ProjectorSource(projector_id=projector.id, code=code, text=text)
+ else:
+ item.text = text
+ log.debug('(%s) Updating source code %s with text="%s"' % (projector.ip, item.code, item.text))
+ self.projectordb.add_source(item)
+ selected = 0
+ else:
+ selected = self.button_group.checkedId()
+ log.debug('SourceSelectDialog().accepted() Setting source to %s' % selected)
+ self.done(selected)
=== added file 'openlp/core/ui/projector/tab.py'
--- openlp/core/ui/projector/tab.py 1970-01-01 00:00:00 +0000
+++ openlp/core/ui/projector/tab.py 2014-10-28 21:04:17 +0000
@@ -0,0 +1,146 @@
+# -*- coding: utf-8 -*-
+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
+
+###############################################################################
+# OpenLP - Open Source Lyrics Projection #
+# --------------------------------------------------------------------------- #
+# Copyright (c) 2008-2014 Raoul Snyman #
+# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan #
+# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
+# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
+# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
+# Christian Richter, Philip Ridout, Ken Roberts, Simon Scudder, #
+# Jeffrey Smith, Maikel Stuivenberg, Martin Thompson, Jon Tibble, #
+# Dave Warnock, Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
+# --------------------------------------------------------------------------- #
+# This program is free software; you can redistribute it and/or modify it #
+# under the terms of the GNU General Public License as published by the Free #
+# Software Foundation; version 2 of the License. #
+# #
+# This program is distributed in the hope that it will be useful, but WITHOUT #
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
+# more details. #
+# #
+# You should have received a copy of the GNU General Public License along #
+# with this program; if not, write to the Free Software Foundation, Inc., 59 #
+# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
+###############################################################################
+"""
+ :mod:`openlp.core.ui.projector.tab`
+
+ Provides the settings tab in the settings dialog.
+"""
+
+import logging
+log = logging.getLogger(__name__)
+log.debug('projectortab module loaded')
+
+from PyQt4 import QtCore, QtGui
+
+from openlp.core.common import Settings, UiStrings, translate
+from openlp.core.lib import SettingsTab
+from openlp.core.lib.projector import DialogSourceStyle
+
+
+class ProjectorTab(SettingsTab):
+ """
+ Openlp Settings -> Projector settings
+ """
+ def __init__(self, parent):
+ """
+ ProjectorTab initialization
+
+ :param parent: Parent widget
+ """
+ self.icon_path = ':/projector/projector_manager.png'
+ projector_translated = translate('OpenLP.ProjectorTab', 'Projector')
+ super(ProjectorTab, self).__init__(parent, 'Projector', projector_translated)
+
+ def setupUi(self):
+ """
+ Setup the UI
+ """
+ self.setObjectName('ProjectorTab')
+ super(ProjectorTab, self).setupUi()
+ self.connect_box = QtGui.QGroupBox(self.left_column)
+ self.connect_box.setObjectName('connect_box')
+ self.connect_box_layout = QtGui.QFormLayout(self.connect_box)
+ self.connect_box_layout.setObjectName('connect_box_layout')
+ # Start comms with projectors on startup
+ self.connect_on_startup = QtGui.QCheckBox(self.connect_box)
+ self.connect_on_startup.setObjectName('connect_on_startup')
+ self.connect_box_layout.addRow(self.connect_on_startup)
+ # Socket timeout
+ self.socket_timeout_label = QtGui.QLabel(self.connect_box)
+ self.socket_timeout_label.setObjectName('socket_timeout_label')
+ self.socket_timeout_spin_box = QtGui.QSpinBox(self.connect_box)
+ self.socket_timeout_spin_box.setObjectName('socket_timeout_spin_box')
+ self.socket_timeout_spin_box.setMinimum(2)
+ self.socket_timeout_spin_box.setMaximum(10)
+ self.connect_box_layout.addRow(self.socket_timeout_label, self.socket_timeout_spin_box)
+ # Poll interval
+ self.socket_poll_label = QtGui.QLabel(self.connect_box)
+ self.socket_poll_label.setObjectName('socket_poll_label')
+ self.socket_poll_spin_box = QtGui.QSpinBox(self.connect_box)
+ self.socket_poll_spin_box.setObjectName('socket_timeout_spin_box')
+ self.socket_poll_spin_box.setMinimum(5)
+ self.socket_poll_spin_box.setMaximum(60)
+ self.connect_box_layout.addRow(self.socket_poll_label, self.socket_poll_spin_box)
+ self.left_layout.addWidget(self.connect_box)
+ # Source input select dialog box type
+ self.dialog_type_label = QtGui.QLabel(self.connect_box)
+ self.dialog_type_label.setObjectName('dialog_type_label')
+ self.dialog_type_combo_box = QtGui.QComboBox(self.connect_box)
+ self.dialog_type_combo_box.setObjectName('dialog_type_combo_box')
+ self.dialog_type_combo_box.addItems(['', ''])
+ self.connect_box_layout.addRow(self.dialog_type_label, self.dialog_type_combo_box)
+ self.left_layout.addStretch()
+ self.dialog_type_combo_box.activated.connect(self.on_dialog_type_combo_box_changed)
+
+ def retranslateUi(self):
+ """
+ Translate the UI on the fly
+ """
+ self.tab_title_visible = UiStrings().Projectors
+ self.connect_box.setTitle(
+ translate('OpenLP.ProjectorTab', 'Communication Options'))
+ self.connect_on_startup.setText(
+ translate('OpenLP.ProjectorTab', 'Connect to projectors on startup'))
+ self.socket_timeout_label.setText(
+ translate('OpenLP.ProjectorTab', 'Socket timeout (seconds)'))
+ self.socket_poll_label.setText(
+ translate('OpenLP.ProjectorTab', 'Poll time (seconds)'))
+ self.dialog_type_label.setText(
+ translate('Openlp.ProjectorTab', 'Source select dialog interface'))
+ self.dialog_type_combo_box.setItemText(DialogSourceStyle.Tabbed,
+ translate('OpenLP.ProjectorTab', 'Tabbed dialog box'))
+ self.dialog_type_combo_box.setItemText(DialogSourceStyle.Single,
+ translate('OpenLP.ProjectorTab', 'Single dialog box'))
+
+ def load(self):
+ """
+ Load the projector settings on startup
+ """
+ settings = Settings()
+ settings.beginGroup(self.settings_section)
+ self.connect_on_startup.setChecked(settings.value('connect on start'))
+ self.socket_timeout_spin_box.setValue(settings.value('socket timeout'))
+ self.socket_poll_spin_box.setValue(settings.value('poll time'))
+ self.dialog_type_combo_box.setCurrentIndex(settings.value('source dialog type'))
+ settings.endGroup()
+
+ def save(self):
+ """
+ Save the projector settings
+ """
+ settings = Settings()
+ settings.beginGroup(self.settings_section)
+ settings.setValue('connect on start', self.connect_on_startup.isChecked())
+ settings.setValue('socket timeout', self.socket_timeout_spin_box.value())
+ settings.setValue('poll time', self.socket_poll_spin_box.value())
+ settings.setValue('source dialog type', self.dialog_type_combo_box.currentIndex())
+ settings.endGroup
+
+ def on_dialog_type_combo_box_changed(self):
+ self.dialog_type = self.dialog_type_combo_box.currentIndex()
=== modified file 'openlp/core/ui/settingsform.py'
--- openlp/core/ui/settingsform.py 2014-10-28 20:27:09 +0000
+++ openlp/core/ui/settingsform.py 2014-10-28 21:04:17 +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.projector.tab import ProjectorTab
log = logging.getLogger(__name__)
@@ -70,6 +71,7 @@
self.insert_tab(self.themes_tab)
self.insert_tab(self.advanced_tab)
self.insert_tab(self.player_tab)
+ self.insert_tab(self.projector_tab)
for plugin in self.plugin_manager.plugins:
if plugin.settings_tab:
self.insert_tab(plugin.settings_tab, plugin.is_active())
@@ -135,6 +137,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-10-28 21:04:17 +0000
@@ -106,6 +106,7 @@
<file>wizard_firsttime.bmp</file>
<file>wizard_createtheme.bmp</file>
<file>wizard_duplicateremoval.bmp</file>
+ <file>wizard_createprojector.png</file>
</qresource>
<qresource prefix="services">
<file>service_collapse_all.png</file>
@@ -169,6 +170,34 @@
<file>theme_new.png</file>
<file>theme_edit.png</file>
</qresource>
+ <qresource prefix="projector">
+ <file>projector_blank.png</file>
+ <file>projector_blank_tiled.png</file>
+ <file>projector_connect.png</file>
+ <file>projector_connect_tiled.png</file>
+ <file>projector_hdmi.png</file>
+ <file>projector_cooldown.png</file>
+ <file>projector_disconnect.png</file>
+ <file>projector_disconnect_tiled.png</file>
+ <file>projector_edit.png</file>
+ <file>projector_error.png</file>
+ <file>projector_item_connect.png</file>
+ <file>projector_item_disconnect.png</file>
+ <file>projector_manager.png</file>
+ <file>projector_new.png</file>
+ <file>projector_not_connected_error.png</file>
+ <file>projector_off.png</file>
+ <file>projector_on.png</file>
+ <file>projector_power_off.png</file>
+ <file>projector_power_off_tiled.png</file>
+ <file>projector_power_on.png</file>
+ <file>projector_power_on_tiled.png</file>
+ <file>projector_show.png</file>
+ <file>projector_show_tiled.png</file>
+ <file>projector_spacer.png</file>
+ <file>projector_warmup.png</file>
+ <file>projector_view.png</file>
+ </qresource>
<qresource prefix="remotes">
<file>android_app_qr.png</file>
</qresource>
=== 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-10-28 21:04:17 +0000 differ
=== added file 'resources/images/projector_blank_tiled.png'
Binary files resources/images/projector_blank_tiled.png 1970-01-01 00:00:00 +0000 and resources/images/projector_blank_tiled.png 2014-10-28 21:04:17 +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-10-28 21:04:17 +0000 differ
=== added file 'resources/images/projector_connect_tiled.png'
Binary files resources/images/projector_connect_tiled.png 1970-01-01 00:00:00 +0000 and resources/images/projector_connect_tiled.png 2014-10-28 21:04:17 +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-10-28 21:04:17 +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-10-28 21:04:17 +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-10-28 21:04:17 +0000 differ
=== added file 'resources/images/projector_disconnect_tiled.png'
Binary files resources/images/projector_disconnect_tiled.png 1970-01-01 00:00:00 +0000 and resources/images/projector_disconnect_tiled.png 2014-10-28 21:04:17 +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-10-28 21:04:17 +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-10-28 21:04:17 +0000 differ
=== added file 'resources/images/projector_hdmi.png'
Binary files resources/images/projector_hdmi.png 1970-01-01 00:00:00 +0000 and resources/images/projector_hdmi.png 2014-10-28 21:04:17 +0000 differ
=== added file 'resources/images/projector_item_connect.png'
Binary files resources/images/projector_item_connect.png 1970-01-01 00:00:00 +0000 and resources/images/projector_item_connect.png 2014-10-28 21:04:17 +0000 differ
=== added file 'resources/images/projector_item_disconnect.png'
Binary files resources/images/projector_item_disconnect.png 1970-01-01 00:00:00 +0000 and resources/images/projector_item_disconnect.png 2014-10-28 21:04:17 +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-10-28 21:04:17 +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-10-28 21:04:17 +0000 differ
=== added file 'resources/images/projector_not_connected_error.png'
Binary files resources/images/projector_not_connected_error.png 1970-01-01 00:00:00 +0000 and resources/images/projector_not_connected_error.png 2014-10-28 21:04:17 +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-10-28 21:04:17 +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-10-28 21:04:17 +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-10-28 21:04:17 +0000 differ
=== added file 'resources/images/projector_power_off_tiled.png'
Binary files resources/images/projector_power_off_tiled.png 1970-01-01 00:00:00 +0000 and resources/images/projector_power_off_tiled.png 2014-10-28 21:04:17 +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-10-28 21:04:17 +0000 differ
=== added file 'resources/images/projector_power_on_tiled.png'
Binary files resources/images/projector_power_on_tiled.png 1970-01-01 00:00:00 +0000 and resources/images/projector_power_on_tiled.png 2014-10-28 21:04:17 +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-10-28 21:04:17 +0000 differ
=== added file 'resources/images/projector_show_tiled.png'
Binary files resources/images/projector_show_tiled.png 1970-01-01 00:00:00 +0000 and resources/images/projector_show_tiled.png 2014-10-28 21:04:17 +0000 differ
=== added file 'resources/images/projector_spacer.png'
Binary files resources/images/projector_spacer.png 1970-01-01 00:00:00 +0000 and resources/images/projector_spacer.png 2014-10-28 21:04:17 +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-10-28 21:04:17 +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-10-28 21:04:17 +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-10-28 21:04:17 +0000 differ
=== modified file 'scripts/generate_resources.sh'
--- scripts/generate_resources.sh 2014-09-08 20:43:21 +0000
+++ scripts/generate_resources.sh 2014-10-28 21:04:17 +0000
@@ -53,5 +53,6 @@
patch --posix -s openlp/core/resources.py scripts/resources.patch
# Remove temporary file
-rm openlp/core/resources.py.new
-
+rm openlp/core/resources.py.new 2>/dev/null
+rm openlp/core/resources.py.old 2>/dev/null
+rm openlp/core/resources.py.orig 2>/dev/null
=== 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-10-28 21:04:17 +0000
@@ -0,0 +1,163 @@
+# -*- coding: utf-8 -*-
+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
+
+###############################################################################
+# OpenLP - Open Source Lyrics Projection #
+# --------------------------------------------------------------------------- #
+# Copyright (c) 2008-2014 Raoul Snyman #
+# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan #
+# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
+# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
+# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
+# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
+# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, #
+# Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
+# --------------------------------------------------------------------------- #
+# This program is free software; you can redistribute it and/or modify it #
+# under the terms of the GNU General Public License as published by the Free #
+# Software Foundation; version 2 of the License. #
+# #
+# This program is distributed in the hope that it will be useful, but WITHOUT #
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
+# more details. #
+# #
+# You should have received a copy of the GNU General Public License along #
+# with this program; if not, write to the Free Software Foundation, Inc., 59 #
+# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
+###############################################################################
+"""
+Package to test the openlp.core.ui.projector.networkutils package.
+"""
+
+from unittest import TestCase
+
+from openlp.core.common import verify_ip_address, md5_hash, qmd5_hash
+
+salt = '498e4a67'
+pin = 'JBMIAProjectorLink'
+test_hash = '5d8409bc1c3fa39749434aa3a5c38682'
+
+ip4_loopback = '127.0.0.1'
+ip4_local = '192.168.1.1'
+ip4_broadcast = '255.255.255.255'
+ip4_bad = '192.168.1.256'
+
+ip6_loopback = '::1'
+ip6_link_local = 'fe80::223:14ff:fe99:d315'
+ip6_bad = 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff'
+
+
+class testProjectorUtilities(TestCase):
+ """
+ Validate functions in the projector utilities module
+ """
+ def test_ip4_loopback_valid(self):
+ """
+ Test IPv4 loopbackvalid
+ """
+ # WHEN: Test with a local loopback test
+ valid = verify_ip_address(addr=ip4_loopback)
+
+ # THEN: Verify we received True
+ self.assertTrue(valid, 'IPv4 loopback address should have been valid')
+
+ def test_ip4_local_valid(self):
+ """
+ Test IPv4 local valid
+ """
+ # WHEN: Test with a local loopback test
+ valid = verify_ip_address(addr=ip4_local)
+
+ # THEN: Verify we received True
+ self.assertTrue(valid, 'IPv4 local address should have been valid')
+
+ def test_ip4_broadcast_valid(self):
+ """
+ Test IPv4 broadcast valid
+ """
+ # WHEN: Test with a local loopback test
+ valid = verify_ip_address(addr=ip4_broadcast)
+
+ # THEN: Verify we received True
+ self.assertTrue(valid, 'IPv4 broadcast address should have been valid')
+
+ def test_ip4_address_invalid(self):
+ """
+ Test IPv4 address invalid
+ """
+ # WHEN: Test with a local loopback test
+ valid = verify_ip_address(addr=ip4_bad)
+
+ # THEN: Verify we received True
+ self.assertFalse(valid, 'Bad IPv4 address should not have been valid')
+
+ def test_ip6_loopback_valid(self):
+ """
+ Test IPv6 loopback valid
+ """
+ # WHEN: Test IPv6 loopback address
+ valid = verify_ip_address(addr=ip6_loopback)
+
+ # THEN: Validate return
+ self.assertTrue(valid, 'IPv6 loopback address should have been valid')
+
+ def test_ip6_local_valid(self):
+ """
+ Test IPv6 link-local valid
+ """
+ # WHEN: Test IPv6 link-local address
+ valid = verify_ip_address(addr=ip6_link_local)
+
+ # THEN: Validate return
+ self.assertTrue(valid, 'IPv6 link-local address should have been valid')
+
+ def test_ip6_address_invalid(self):
+ """
+ Test NetworkUtils IPv6 address invalid
+ """
+ # WHEN: Given an invalid IPv6 address
+ valid = verify_ip_address(addr=ip6_bad)
+
+ # THEN: Validate bad return
+ self.assertFalse(valid, 'IPv6 bad address should have been invalid')
+
+ def test_md5_hash(self):
+ """
+ Test MD5 hash from salt+data pass (python)
+ """
+ # WHEN: Given a known salt+data
+ hash_ = md5_hash(salt=salt, data=pin)
+
+ # THEN: Validate return has is same
+ self.assertEquals(hash_, test_hash, 'MD5 should have returned a good hash')
+
+ def test_md5_hash_bad(self):
+ """
+ Test MD5 hash from salt+data fail (python)
+ """
+ # WHEN: Given a different salt+hash
+ hash_ = md5_hash(salt=pin, data=salt)
+
+ # THEN: return data is different
+ self.assertNotEquals(hash_, test_hash, 'MD5 should have returned a bad hash')
+
+ def test_qmd5_hash(self):
+ """
+ Test MD5 hash from salt+data pass (Qt)
+ """
+ # WHEN: Given a known salt+data
+ hash_ = qmd5_hash(salt=salt, data=pin)
+
+ # THEN: Validate return has is same
+ self.assertEquals(hash_, test_hash, 'Qt-MD5 should have returned a good hash')
+
+ def test_qmd5_hash_bad(self):
+ """
+ Test MD5 hash from salt+hash fail (Qt)
+ """
+ # WHEN: Given a different salt+hash
+ hash_ = qmd5_hash(salt=pin, data=salt)
+
+ # THEN: return data is different
+ self.assertNotEquals(hash_, test_hash, 'Qt-MD5 should have returned a bad hash')
=== 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-10-28 21:04:17 +0000
@@ -27,5 +27,28 @@
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
-Package to test the openlp.core.lib package.
+Module-level functions for the functional test suite
"""
+
+import os
+from tests.functional import patch
+
+from openlp.core.common import is_win
+
+from .test_projectordb import tmpfile
+
+
+def setUp():
+ if not is_win():
+ # Wine creates a sharing violation during tests. Ignore.
+ try:
+ os.remove(tmpfile)
+ except:
+ pass
+
+
+def tearDown():
+ """
+ Ensure test suite has been cleaned up after tests
+ """
+ patch.stopall()
=== 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-10-28 21:04:17 +0000
@@ -0,0 +1,160 @@
+# -*- coding: utf-8 -*-
+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
+
+###############################################################################
+# OpenLP - Open Source Lyrics Projection #
+# --------------------------------------------------------------------------- #
+# Copyright (c) 2008-2014 Raoul Snyman #
+# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan #
+# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
+# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
+# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
+# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
+# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, #
+# Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
+# --------------------------------------------------------------------------- #
+# This program is free software; you can redistribute it and/or modify it #
+# under the terms of the GNU General Public License as published by the Free #
+# Software Foundation; version 2 of the License. #
+# #
+# This program is distributed in the hope that it will be useful, but WITHOUT #
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
+# more details. #
+# #
+# You should have received a copy of the GNU General Public License along #
+# with this program; if not, write to the Free Software Foundation, Inc., 59 #
+# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
+###############################################################################
+"""
+Package to test the openlp.core.ui.projectordb find, edit, delete
+record functions.
+
+PREREQUISITE: add_record() and get_all() functions validated.
+"""
+
+from unittest import TestCase
+from tests.functional import MagicMock, patch
+
+from openlp.core.lib.projector.db import Projector, ProjectorDB
+
+from tests.resources.projector.data import TEST1_DATA, TEST2_DATA, TEST3_DATA
+
+tmpfile = '/tmp/openlp-test-projectordb.sql'
+
+
+def compare_data(one, two):
+ """
+ Verify two Projector() instances contain the same data
+ """
+ return one is not None and \
+ two is not None and \
+ one.ip == two.ip and \
+ one.port == two.port and \
+ one.name == two.name and \
+ one.location == two.location and \
+ one.notes == two.notes
+
+
+def add_records(self, test):
+ """
+ Add record if not in database
+ """
+ record_list = self.projector.get_projector_all()
+ if len(record_list) < 1:
+ added = False
+ for record in test:
+ added = self.projector.add_projector(record) or added
+ return added
+
+ for new_record in test:
+ added = None
+ for record in record_list:
+ if compare_data(record, new_record):
+ break
+ added = self.projector.add_projector(new_record)
+ return added
+
+
+class TestProjectorDB(TestCase):
+ """
+ Test case for ProjectorDB
+ """
+ def setUp(self):
+ """
+ Set up anything necessary for all tests
+ """
+ if not hasattr(self, 'projector'):
+ with patch('openlp.core.lib.projector.db.init_url') as mocked_init_url:
+ mocked_init_url.start()
+ mocked_init_url.return_value = 'sqlite:///%s' % tmpfile
+ self.projector = ProjectorDB()
+
+ def find_record_by_ip_test(self):
+ """
+ Test find record by IP
+ """
+ # GIVEN: Record entries in database
+ add_records(self, [TEST1_DATA, TEST2_DATA])
+
+ # WHEN: Search for record using IP
+ record = self.projector.get_projector_by_ip(TEST2_DATA.ip)
+
+ # THEN: Verify proper record returned
+ self.assertTrue(compare_data(TEST2_DATA, record),
+ 'Record found should have been test_2 data')
+
+ def find_record_by_name_test(self):
+ """
+ Test find record by name
+ """
+ # GIVEN: Record entries in database
+ add_records(self, [TEST1_DATA, TEST2_DATA])
+
+ # WHEN: Search for record using name
+ record = self.projector.get_projector_by_name(TEST2_DATA.name)
+
+ # THEN: Verify proper record returned
+ self.assertTrue(compare_data(TEST2_DATA, record),
+ 'Record found should have been test_2 data')
+
+ def record_delete_test(self):
+ """
+ Test record can be deleted
+ """
+ # GIVEN: Record in database
+ add_records(self, [TEST3_DATA, ])
+ record = self.projector.get_projector_by_ip(TEST3_DATA.ip)
+
+ # WHEN: Record deleted
+ self.projector.delete_projector(record)
+
+ # THEN: Verify record not retrievable
+ found = self.projector.get_projector_by_ip(TEST3_DATA.ip)
+ self.assertFalse(found, 'test_3 record should have been deleted')
+
+ def record_edit_test(self):
+ """
+ Test edited record returns the same record ID with different data
+ """
+ # GIVEN: Record entries in database
+ add_records(self, [TEST1_DATA, TEST2_DATA])
+
+ # WHEN: We retrieve a specific record
+ record = self.projector.get_projector_by_ip(TEST1_DATA.ip)
+ record_id = record.id
+
+ # WHEN: Data is changed
+ record.ip = TEST3_DATA.ip
+ record.port = TEST3_DATA.port
+ record.pin = TEST3_DATA.pin
+ record.name = TEST3_DATA.name
+ record.location = TEST3_DATA.location
+ record.notes = TEST3_DATA.notes
+ updated = self.projector.update_projector(record)
+ self.assertTrue(updated, 'Save updated record should have returned True')
+ record = self.projector.get_projector_by_ip(TEST3_DATA.ip)
+
+ # THEN: Record ID should remain the same, but data should be changed
+ self.assertEqual(record_id, record.id, 'Edited record should have the same ID')
+ self.assertTrue(compare_data(TEST3_DATA, record), 'Edited record should have new data')
=== 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-10-28 21:04:17 +0000
@@ -26,3 +26,35 @@
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
+"""
+Module-level functions for the functional test suite
+"""
+
+import os
+from tests.interfaces import patch
+
+from openlp.core.common import is_win
+
+from .test_projectormanager import tmpfile
+
+
+def setUp():
+ if not is_win():
+ # Wine creates a sharing violation during tests. Ignore.
+ try:
+ os.remove(tmpfile)
+ except:
+ pass
+
+
+def tearDown():
+ """
+ Ensure test suite has been cleaned up after tests
+ """
+ patch.stopall()
+ if not is_win():
+ try:
+ # In case of changed schema, remove old test file
+ os.remove(tmpfile)
+ except FileNotFoundError:
+ pass
=== 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-10-28 21:04:17 +0000
@@ -0,0 +1,106 @@
+# -*- coding: utf-8 -*-
+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
+
+###############################################################################
+# OpenLP - Open Source Lyrics Projection #
+# --------------------------------------------------------------------------- #
+# Copyright (c) 2008-2014 Raoul Snyman #
+# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan #
+# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
+# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
+# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
+# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
+# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, #
+# Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
+# --------------------------------------------------------------------------- #
+# This program is free software; you can redistribute it and/or modify it #
+# under the terms of the GNU General Public License as published by the Free #
+# Software Foundation; version 2 of the License. #
+# #
+# This program is distributed in the hope that it will be useful, but WITHOUT #
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
+# more details. #
+# #
+# You should have received a copy of the GNU General Public License along #
+# with this program; if not, write to the Free Software Foundation, Inc., 59 #
+# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
+###############################################################################
+"""
+Interface tests to test the themeManager class and related methods.
+"""
+
+import os
+from unittest import TestCase
+
+from openlp.core.common import Registry, Settings
+from tests.functional import patch, MagicMock
+from tests.helpers.testmixin import TestMixin
+
+from openlp.core.ui import ProjectorManager, ProjectorEditForm
+from openlp.core.lib.projector.db import Projector, ProjectorDB
+
+from tests.resources.projector.data import TEST1_DATA, TEST2_DATA, TEST3_DATA
+
+tmpfile = '/tmp/openlp-test-projectormanager.sql'
+
+
+class TestProjectorManager(TestCase, TestMixin):
+ """
+ Test the functions in the ProjectorManager module
+ """
+ def setUp(self):
+ """
+ Create the UI and setup necessary options
+ """
+ self.build_settings()
+ self.setup_application()
+ Registry.create()
+ if not hasattr(self, 'projector_manager'):
+ with patch('openlp.core.lib.projector.db.init_url') as mocked_init_url:
+ mocked_init_url.start()
+ mocked_init_url.return_value = 'sqlite:///%s' % tmpfile
+ self.projectordb = ProjectorDB()
+ if not hasattr(self, 'projector_manager'):
+ self.projector_manager = ProjectorManager(projectordb=self.projectordb)
+
+ def tearDown(self):
+ """
+ Remove test database.
+ Delete all the C++ objects at the end so that we don't have a segfault.
+ """
+ self.projectordb.session.close()
+ del self.projector_manager
+ self.destroy_settings()
+
+ def bootstrap_initialise_test(self):
+ """
+ Test initialize calls correct startup functions
+ """
+ # WHEN: we call bootstrap_initialise
+ self.projector_manager.bootstrap_initialise()
+ # THEN: ProjectorDB is setup
+ self.assertEqual(type(self.projector_manager.projectordb), ProjectorDB,
+ 'Initialization should have created a ProjectorDB() instance')
+
+ def bootstrap_post_set_up_test(self):
+ """
+ Test post-initialize calls proper setups
+ """
+ # GIVEN: setup mocks
+ self.projector_manager._load_projectors = MagicMock()
+
+ # WHEN: Call to initialize is run
+ self.projector_manager.bootstrap_initialise()
+ self.projector_manager.bootstrap_post_set_up()
+
+ # THEN: verify calls to retrieve saved projectors
+ self.assertEqual(1, self.projector_manager._load_projectors.call_count,
+ 'Initialization should have called load_projectors()')
+
+ # THEN: Verify edit page is initialized
+ self.assertEqual(type(self.projector_manager.projector_form), ProjectorEditForm,
+ 'Initialization should have created a Projector Edit Form')
+ self.assertIs(self.projector_manager.projectordb,
+ self.projector_manager.projector_form.projectordb,
+ 'ProjectorEditForm should be using same ProjectorDB() instance as ProjectorManager')
=== added directory 'tests/resources/projector'
=== added file 'tests/resources/projector/data.py'
--- tests/resources/projector/data.py 1970-01-01 00:00:00 +0000
+++ tests/resources/projector/data.py 2014-10-28 21:04:17 +0000
@@ -0,0 +1,55 @@
+# -*- coding: utf-8 -*-
+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
+
+###############################################################################
+# OpenLP - Open Source Lyrics Projection #
+# --------------------------------------------------------------------------- #
+# Copyright (c) 2008-2014 Raoul Snyman #
+# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan #
+# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
+# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
+# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
+# Christian Richter, Philip Ridout, Ken Roberts, Simon Scudder, #
+# Jeffrey Smith, Maikel Stuivenberg, Martin Thompson, Jon Tibble, #
+# Dave Warnock, Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
+# --------------------------------------------------------------------------- #
+# This program is free software; you can redistribute it and/or modify it #
+# under the terms of the GNU General Public License as published by the Free #
+# Software Foundation; version 2 of the License. #
+# #
+# This program is distributed in the hope that it will be useful, but WITHOUT #
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
+# more details. #
+# #
+# You should have received a copy of the GNU General Public License along #
+# with this program; if not, write to the Free Software Foundation, Inc., 59 #
+# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
+###############################################################################
+"""
+The :mod:`tests.resources.projector.data file contains test data
+"""
+
+from openlp.core.lib.projector.db import Projector
+
+# Test data
+TEST1_DATA = Projector(ip='111.111.111.111',
+ port='1111',
+ pin='1111',
+ name='___TEST_ONE___',
+ location='location one',
+ notes='notes one')
+
+TEST2_DATA = Projector(ip='222.222.222.222',
+ port='2222',
+ pin='2222',
+ name='___TEST_TWO___',
+ location='location two',
+ notes='notes two')
+
+TEST3_DATA = Projector(ip='333.333.333.333',
+ port='3333',
+ pin='3333',
+ name='___TEST_THREE___',
+ location='location three',
+ notes='notes three')
Follow ups