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