forked from openlp/openlp
HEAD?
This commit is contained in:
commit
e1d2c67f33
|
@ -7,6 +7,7 @@ cover
|
||||||
.coverage
|
.coverage
|
||||||
coverage
|
coverage
|
||||||
.directory
|
.directory
|
||||||
|
.vscode
|
||||||
dist
|
dist
|
||||||
*.dll
|
*.dll
|
||||||
documentation/build/doctrees
|
documentation/build/doctrees
|
||||||
|
|
|
@ -706,6 +706,7 @@ class Renderer(RegistryBase, LogMixin, RegistryProperties, DisplayWindow):
|
||||||
else:
|
else:
|
||||||
# The remaining elements do not fit, thus reset the indexes, create a new list and continue.
|
# The remaining elements do not fit, thus reset the indexes, create a new list and continue.
|
||||||
raw_list = raw_list[index + 1:]
|
raw_list = raw_list[index + 1:]
|
||||||
|
log.debug(raw_list)
|
||||||
raw_list[0] = raw_tags + raw_list[0]
|
raw_list[0] = raw_tags + raw_list[0]
|
||||||
html_list = html_list[index + 1:]
|
html_list = html_list[index + 1:]
|
||||||
html_list[0] = html_tags + html_list[0]
|
html_list[0] = html_tags + html_list[0]
|
||||||
|
|
|
@ -29,16 +29,14 @@ import copy
|
||||||
|
|
||||||
from PyQt5 import QtCore, QtWebChannel, QtWidgets
|
from PyQt5 import QtCore, QtWebChannel, QtWidgets
|
||||||
|
|
||||||
from openlp.core.common.path import Path, path_to_str
|
from openlp.core.common.path import path_to_str
|
||||||
from openlp.core.common.settings import Settings
|
from openlp.core.common.settings import Settings
|
||||||
from openlp.core.common.registry import Registry
|
from openlp.core.common.registry import Registry
|
||||||
|
from openlp.core.common.applocation import AppLocation
|
||||||
from openlp.core.ui import HideMode
|
from openlp.core.ui import HideMode
|
||||||
from openlp.core.display.screens import ScreenList
|
from openlp.core.display.screens import ScreenList
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
DISPLAY_PATH = Path(__file__).parent / 'html' / 'display.html'
|
|
||||||
CHECKERBOARD_PATH = Path(__file__).parent / 'html' / 'checkerboard.png'
|
|
||||||
OPENLP_SPLASH_SCREEN_PATH = Path(__file__).parent / 'html' / 'openlp-splash-screen.png'
|
|
||||||
|
|
||||||
|
|
||||||
class MediaWatcher(QtCore.QObject):
|
class MediaWatcher(QtCore.QObject):
|
||||||
|
@ -126,7 +124,11 @@ class DisplayWindow(QtWidgets.QWidget):
|
||||||
self.webview.page().setBackgroundColor(QtCore.Qt.transparent)
|
self.webview.page().setBackgroundColor(QtCore.Qt.transparent)
|
||||||
self.layout.addWidget(self.webview)
|
self.layout.addWidget(self.webview)
|
||||||
self.webview.loadFinished.connect(self.after_loaded)
|
self.webview.loadFinished.connect(self.after_loaded)
|
||||||
self.set_url(QtCore.QUrl.fromLocalFile(path_to_str(DISPLAY_PATH)))
|
display_base_path = AppLocation.get_directory(AppLocation.AppDir) / 'core' / 'display' / 'html'
|
||||||
|
self.display_path = display_base_path / 'display.html'
|
||||||
|
self.checkerboard_path = display_base_path / 'checkerboard.png'
|
||||||
|
self.openlp_splash_screen_path = display_base_path / 'openlp-splash-screen.png'
|
||||||
|
self.set_url(QtCore.QUrl.fromLocalFile(path_to_str(self.display_path)))
|
||||||
self.media_watcher = MediaWatcher(self)
|
self.media_watcher = MediaWatcher(self)
|
||||||
self.channel = QtWebChannel.QWebChannel(self)
|
self.channel = QtWebChannel.QWebChannel(self)
|
||||||
self.channel.registerObject('mediaWatcher', self.media_watcher)
|
self.channel.registerObject('mediaWatcher', self.media_watcher)
|
||||||
|
@ -169,7 +171,7 @@ class DisplayWindow(QtWidgets.QWidget):
|
||||||
bg_color = Settings().value('core/logo background color')
|
bg_color = Settings().value('core/logo background color')
|
||||||
image = Settings().value('core/logo file')
|
image = Settings().value('core/logo file')
|
||||||
if path_to_str(image).startswith(':'):
|
if path_to_str(image).startswith(':'):
|
||||||
image = OPENLP_SPLASH_SCREEN_PATH
|
image = self.openlp_splash_screen_path
|
||||||
image_uri = image.as_uri()
|
image_uri = image.as_uri()
|
||||||
self.run_javascript('Display.setStartupSplashScreen("{bg_color}", "{image}");'.format(bg_color=bg_color,
|
self.run_javascript('Display.setStartupSplashScreen("{bg_color}", "{image}");'.format(bg_color=bg_color,
|
||||||
image=image_uri))
|
image=image_uri))
|
||||||
|
@ -329,7 +331,7 @@ class DisplayWindow(QtWidgets.QWidget):
|
||||||
if theme.background_type == 'transparent' and not self.is_display:
|
if theme.background_type == 'transparent' and not self.is_display:
|
||||||
theme_copy = copy.deepcopy(theme)
|
theme_copy = copy.deepcopy(theme)
|
||||||
theme_copy.background_type = 'image'
|
theme_copy.background_type = 'image'
|
||||||
theme_copy.background_filename = CHECKERBOARD_PATH
|
theme_copy.background_filename = self.checkerboard_path
|
||||||
exported_theme = theme_copy.export_theme()
|
exported_theme = theme_copy.export_theme()
|
||||||
else:
|
else:
|
||||||
exported_theme = theme.export_theme()
|
exported_theme = theme.export_theme()
|
||||||
|
|
|
@ -417,11 +417,17 @@ class ProjectorDB(Manager):
|
||||||
value: (str) From ProjectorSource, Sources tables or PJLink default code list
|
value: (str) From ProjectorSource, Sources tables or PJLink default code list
|
||||||
"""
|
"""
|
||||||
source_dict = {}
|
source_dict = {}
|
||||||
|
# Apparently, there was a change to the projector object. Test for which object has db id
|
||||||
|
if hasattr(projector, "id"):
|
||||||
|
chk = projector.id
|
||||||
|
elif hasattr(projector.entry, "id"):
|
||||||
|
chk = projector.entry.id
|
||||||
|
|
||||||
# Get default list first
|
# Get default list first
|
||||||
for key in projector.source_available:
|
for key in projector.source_available:
|
||||||
item = self.get_object_filtered(ProjectorSource,
|
item = self.get_object_filtered(ProjectorSource,
|
||||||
and_(ProjectorSource.code == key,
|
and_(ProjectorSource.code == key,
|
||||||
ProjectorSource.projector_id == projector.id))
|
ProjectorSource.projector_id == chk))
|
||||||
if item is None:
|
if item is None:
|
||||||
source_dict[key] = PJLINK_DEFAULT_CODES[key]
|
source_dict[key] = PJLINK_DEFAULT_CODES[key]
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -46,31 +46,10 @@ from openlp.core.projectors.sourceselectform import SourceSelectSingle, SourceSe
|
||||||
from openlp.core.ui.icons import UiIcons
|
from openlp.core.ui.icons import UiIcons
|
||||||
from openlp.core.widgets.toolbar import OpenLPToolbar
|
from openlp.core.widgets.toolbar import OpenLPToolbar
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
log.debug('projectormanager loaded')
|
log.debug('projectormanager loaded')
|
||||||
|
|
||||||
|
|
||||||
# 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_SOCKET_TIMEOUT: ':/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 UiProjectorManager(object):
|
class UiProjectorManager(object):
|
||||||
"""
|
"""
|
||||||
UI part of the Projector Manager
|
UI part of the Projector Manager
|
||||||
|
@ -122,7 +101,7 @@ class UiProjectorManager(object):
|
||||||
self.one_toolbar.add_toolbar_action('connect_projector',
|
self.one_toolbar.add_toolbar_action('connect_projector',
|
||||||
text=translate('OpenLP.ProjectorManager',
|
text=translate('OpenLP.ProjectorManager',
|
||||||
'Connect to selected projector.'),
|
'Connect to selected projector.'),
|
||||||
icon=UiIcons().projector_connect,
|
icon=UiIcons().projector_select_connect,
|
||||||
tooltip=translate('OpenLP.ProjectorManager',
|
tooltip=translate('OpenLP.ProjectorManager',
|
||||||
'Connect to selected projector.'),
|
'Connect to selected projector.'),
|
||||||
triggers=self.on_connect_projector)
|
triggers=self.on_connect_projector)
|
||||||
|
@ -136,7 +115,7 @@ class UiProjectorManager(object):
|
||||||
self.one_toolbar.add_toolbar_action('disconnect_projector',
|
self.one_toolbar.add_toolbar_action('disconnect_projector',
|
||||||
text=translate('OpenLP.ProjectorManager',
|
text=translate('OpenLP.ProjectorManager',
|
||||||
'Disconnect from selected projectors'),
|
'Disconnect from selected projectors'),
|
||||||
icon=UiIcons().projector_disconnect,
|
icon=UiIcons().projector_select_disconnect,
|
||||||
tooltip=translate('OpenLP.ProjectorManager',
|
tooltip=translate('OpenLP.ProjectorManager',
|
||||||
'Disconnect from selected projector.'),
|
'Disconnect from selected projector.'),
|
||||||
triggers=self.on_disconnect_projector)
|
triggers=self.on_disconnect_projector)
|
||||||
|
@ -151,7 +130,7 @@ class UiProjectorManager(object):
|
||||||
self.one_toolbar.add_toolbar_action('poweron_projector',
|
self.one_toolbar.add_toolbar_action('poweron_projector',
|
||||||
text=translate('OpenLP.ProjectorManager',
|
text=translate('OpenLP.ProjectorManager',
|
||||||
'Power on selected projector'),
|
'Power on selected projector'),
|
||||||
icon=UiIcons().projector_on,
|
icon=UiIcons().projector_power_on,
|
||||||
tooltip=translate('OpenLP.ProjectorManager',
|
tooltip=translate('OpenLP.ProjectorManager',
|
||||||
'Power on selected projector.'),
|
'Power on selected projector.'),
|
||||||
triggers=self.on_poweron_projector)
|
triggers=self.on_poweron_projector)
|
||||||
|
@ -164,7 +143,7 @@ class UiProjectorManager(object):
|
||||||
triggers=self.on_poweron_projector)
|
triggers=self.on_poweron_projector)
|
||||||
self.one_toolbar.add_toolbar_action('poweroff_projector',
|
self.one_toolbar.add_toolbar_action('poweroff_projector',
|
||||||
text=translate('OpenLP.ProjectorManager', 'Standby selected projector'),
|
text=translate('OpenLP.ProjectorManager', 'Standby selected projector'),
|
||||||
icon=UiIcons().projector_off,
|
icon=UiIcons().projector_power_off,
|
||||||
tooltip=translate('OpenLP.ProjectorManager',
|
tooltip=translate('OpenLP.ProjectorManager',
|
||||||
'Put selected projector in standby.'),
|
'Put selected projector in standby.'),
|
||||||
triggers=self.on_poweroff_projector)
|
triggers=self.on_poweroff_projector)
|
||||||
|
@ -309,7 +288,7 @@ class ProjectorManager(QtWidgets.QWidget, RegistryBase, UiProjectorManager, LogM
|
||||||
S_INITIALIZE: UiIcons().projector_on,
|
S_INITIALIZE: UiIcons().projector_on,
|
||||||
S_STANDBY: UiIcons().projector_off,
|
S_STANDBY: UiIcons().projector_off,
|
||||||
S_WARMUP: UiIcons().projector_warmup,
|
S_WARMUP: UiIcons().projector_warmup,
|
||||||
S_ON: UiIcons().projector_off,
|
S_ON: UiIcons().projector_on,
|
||||||
S_COOLDOWN: UiIcons().projector_cooldown,
|
S_COOLDOWN: UiIcons().projector_cooldown,
|
||||||
E_ERROR: UiIcons().projector_error,
|
E_ERROR: UiIcons().projector_error,
|
||||||
E_NETWORK: UiIcons().error,
|
E_NETWORK: UiIcons().error,
|
||||||
|
@ -880,6 +859,7 @@ class ProjectorManager(QtWidgets.QWidget, RegistryBase, UiProjectorManager, LogM
|
||||||
"""
|
"""
|
||||||
Update the icons when the selected projectors change
|
Update the icons when the selected projectors change
|
||||||
"""
|
"""
|
||||||
|
log.debug('update_icons(): Checking for selected projector items in list')
|
||||||
count = len(self.projector_list_widget.selectedItems())
|
count = len(self.projector_list_widget.selectedItems())
|
||||||
projector = None
|
projector = None
|
||||||
if count == 0:
|
if count == 0:
|
||||||
|
@ -900,6 +880,7 @@ class ProjectorManager(QtWidgets.QWidget, RegistryBase, UiProjectorManager, LogM
|
||||||
self.get_toolbar_item('blank_projector_multiple', hidden=True)
|
self.get_toolbar_item('blank_projector_multiple', hidden=True)
|
||||||
self.get_toolbar_item('show_projector_multiple', hidden=True)
|
self.get_toolbar_item('show_projector_multiple', hidden=True)
|
||||||
elif count == 1:
|
elif count == 1:
|
||||||
|
log.debug('update_icons(): Found one item selected')
|
||||||
projector = self.projector_list_widget.selectedItems()[0].data(QtCore.Qt.UserRole)
|
projector = self.projector_list_widget.selectedItems()[0].data(QtCore.Qt.UserRole)
|
||||||
connected = QSOCKET_STATE[projector.link.state()] == S_CONNECTED
|
connected = QSOCKET_STATE[projector.link.state()] == S_CONNECTED
|
||||||
power = projector.link.power == S_ON
|
power = projector.link.power == S_ON
|
||||||
|
@ -910,12 +891,14 @@ class ProjectorManager(QtWidgets.QWidget, RegistryBase, UiProjectorManager, LogM
|
||||||
self.get_toolbar_item('blank_projector_multiple', hidden=True)
|
self.get_toolbar_item('blank_projector_multiple', hidden=True)
|
||||||
self.get_toolbar_item('show_projector_multiple', hidden=True)
|
self.get_toolbar_item('show_projector_multiple', hidden=True)
|
||||||
if connected:
|
if connected:
|
||||||
|
log.debug('update_icons(): Updating icons for connected state')
|
||||||
self.get_toolbar_item('view_projector', enabled=True)
|
self.get_toolbar_item('view_projector', enabled=True)
|
||||||
self.get_toolbar_item('source_view_projector',
|
self.get_toolbar_item('source_view_projector',
|
||||||
enabled=connected and power and projector.link.source_available is not None)
|
enabled=projector.link.source_available is not None and connected and power)
|
||||||
self.get_toolbar_item('edit_projector', hidden=True)
|
self.get_toolbar_item('edit_projector', hidden=True)
|
||||||
self.get_toolbar_item('delete_projector', hidden=True)
|
self.get_toolbar_item('delete_projector', hidden=True)
|
||||||
else:
|
else:
|
||||||
|
log.debug('update_icons(): Updating for not connected state')
|
||||||
self.get_toolbar_item('view_projector', hidden=True)
|
self.get_toolbar_item('view_projector', hidden=True)
|
||||||
self.get_toolbar_item('source_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('edit_projector', enabled=True)
|
||||||
|
@ -931,6 +914,7 @@ class ProjectorManager(QtWidgets.QWidget, RegistryBase, UiProjectorManager, LogM
|
||||||
self.get_toolbar_item('blank_projector', enabled=False)
|
self.get_toolbar_item('blank_projector', enabled=False)
|
||||||
self.get_toolbar_item('show_projector', enabled=False)
|
self.get_toolbar_item('show_projector', enabled=False)
|
||||||
else:
|
else:
|
||||||
|
log.debug('update_icons(): Updating for multiple items selected')
|
||||||
self.get_toolbar_item('edit_projector', enabled=False)
|
self.get_toolbar_item('edit_projector', enabled=False)
|
||||||
self.get_toolbar_item('delete_projector', enabled=False)
|
self.get_toolbar_item('delete_projector', enabled=False)
|
||||||
self.get_toolbar_item('view_projector', hidden=True)
|
self.get_toolbar_item('view_projector', hidden=True)
|
||||||
|
|
|
@ -528,8 +528,9 @@ class PJLinkCommands(object):
|
||||||
sources.append(source)
|
sources.append(source)
|
||||||
sources.sort()
|
sources.sort()
|
||||||
self.source_available = sources
|
self.source_available = sources
|
||||||
log.debug('({ip}) Setting projector sources_available to "{data}"'.format(ip=self.entry.name,
|
log.debug('({ip}) Setting projector source_available to "{data}"'.format(ip=self.entry.name,
|
||||||
data=self.source_available))
|
data=self.source_available))
|
||||||
|
self.projectorUpdateIcons.emit()
|
||||||
return
|
return
|
||||||
|
|
||||||
def process_lamp(self, data):
|
def process_lamp(self, data):
|
||||||
|
@ -886,6 +887,9 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands):
|
||||||
elif status >= S_NOT_CONNECTED and status in QSOCKET_STATE:
|
elif status >= S_NOT_CONNECTED and status in QSOCKET_STATE:
|
||||||
# Socket connection status update
|
# Socket connection status update
|
||||||
self.status_connect = status
|
self.status_connect = status
|
||||||
|
# Check if we need to update error state as well
|
||||||
|
if self.error_status != S_OK and status != S_NOT_CONNECTED:
|
||||||
|
self.error_status = S_OK
|
||||||
elif status >= S_NOT_CONNECTED and status in PROJECTOR_STATE:
|
elif status >= S_NOT_CONNECTED and status in PROJECTOR_STATE:
|
||||||
# Only affects the projector status
|
# Only affects the projector status
|
||||||
self.projector_status = status
|
self.projector_status = status
|
||||||
|
@ -905,7 +909,14 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands):
|
||||||
message=status_message if msg is None else msg))
|
message=status_message if msg is None else msg))
|
||||||
|
|
||||||
# Now that we logged extra information for debugging, broadcast the original change/message
|
# Now that we logged extra information for debugging, broadcast the original change/message
|
||||||
(code, message) = self._get_status(status)
|
# Check for connection errors first
|
||||||
|
if self.error_status != S_OK:
|
||||||
|
log.debug('({ip}) Signalling error code'.format(ip=self.entry.name))
|
||||||
|
code, message = self._get_status(self.error_status)
|
||||||
|
status = self.error_status
|
||||||
|
else:
|
||||||
|
log.debug('({ip}) Signalling status code'.format(ip=self.entry.name))
|
||||||
|
code, message = self._get_status(status)
|
||||||
if msg is not None:
|
if msg is not None:
|
||||||
message = msg
|
message = msg
|
||||||
elif message is None:
|
elif message is None:
|
||||||
|
@ -953,7 +964,7 @@ class PJLink(QtNetwork.QTcpSocket, PJLinkCommands):
|
||||||
log.error('({ip}) Invalid initial packet received - closing socket'.format(ip=self.entry.name))
|
log.error('({ip}) Invalid initial packet received - closing socket'.format(ip=self.entry.name))
|
||||||
return self.disconnect_from_host()
|
return self.disconnect_from_host()
|
||||||
# Convert the initial login prompt with the expected PJLink normal command format for processing
|
# Convert the initial login prompt with the expected PJLink normal command format for processing
|
||||||
log.debug('({ip}) check_login(): Formatting initial connection prompt'
|
log.debug('({ip}) check_login(): Formatting initial connection prompt '
|
||||||
'to PJLink packet'.format(ip=self.entry.name))
|
'to PJLink packet'.format(ip=self.entry.name))
|
||||||
return self.get_data('{start}{clss}{data}'.format(start=PJLINK_PREFIX,
|
return self.get_data('{start}{clss}{data}'.format(start=PJLINK_PREFIX,
|
||||||
clss='1',
|
clss='1',
|
||||||
|
|
|
@ -455,7 +455,7 @@ class AdvancedTab(SettingsTab):
|
||||||
Service Name options changed
|
Service Name options changed
|
||||||
"""
|
"""
|
||||||
self.service_name_day.setEnabled(default_service_enabled)
|
self.service_name_day.setEnabled(default_service_enabled)
|
||||||
time_enabled = default_service_enabled and self.service_name_day.currentIndex() is not 7
|
time_enabled = default_service_enabled and self.service_name_day.currentIndex() != 7
|
||||||
self.service_name_time.setEnabled(time_enabled)
|
self.service_name_time.setEnabled(time_enabled)
|
||||||
self.service_name_edit.setEnabled(default_service_enabled)
|
self.service_name_edit.setEnabled(default_service_enabled)
|
||||||
self.service_name_revert_button.setEnabled(default_service_enabled)
|
self.service_name_revert_button.setEnabled(default_service_enabled)
|
||||||
|
@ -497,7 +497,7 @@ class AdvancedTab(SettingsTab):
|
||||||
"""
|
"""
|
||||||
React to the day of the service name changing.
|
React to the day of the service name changing.
|
||||||
"""
|
"""
|
||||||
self.service_name_time.setEnabled(service_day is not 7)
|
self.service_name_time.setEnabled(service_day != 7)
|
||||||
self.update_service_name_example(None)
|
self.update_service_name_example(None)
|
||||||
|
|
||||||
def on_service_name_revert_button_clicked(self):
|
def on_service_name_revert_button_clicked(self):
|
||||||
|
|
|
@ -117,13 +117,17 @@ class UiIcons(object):
|
||||||
'presentation': {'icon': 'fa.bar-chart'},
|
'presentation': {'icon': 'fa.bar-chart'},
|
||||||
'preview': {'icon': 'fa.laptop'},
|
'preview': {'icon': 'fa.laptop'},
|
||||||
'projector': {'icon': 'op.video'},
|
'projector': {'icon': 'op.video'},
|
||||||
'projector_connect': {'icon': 'fa.plug'},
|
'projector_connect': {'icon': 'fa.plug'}, # Projector connect
|
||||||
'projector_cooldown': {'icon': 'fa.video-camera', 'attr': 'blue'},
|
'projector_cooldown': {'icon': 'fa.video-camera', 'attr': 'blue'},
|
||||||
'projector_disconnect': {'icon': 'fa.plug', 'attr': 'lightGray'},
|
'projector_disconnect': {'icon': 'fa.plug', 'attr': 'lightGray'}, # Projector disconnect
|
||||||
'projector_error': {'icon': 'fa.video-camera', 'attr': 'red'},
|
'projector_error': {'icon': 'fa.video-camera', 'attr': 'red'},
|
||||||
'projector_hdmi': {'icon': 'op.hdmi'},
|
'projector_hdmi': {'icon': 'op.hdmi'},
|
||||||
'projector_off': {'icon': 'fa.video-camera', 'attr': 'black'},
|
'projector_power_off': {'icon': 'fa.video-camera', 'attr': 'red'}, # Toolbar power off
|
||||||
'projector_on': {'icon': 'fa.video-camera', 'attr': 'green'},
|
'projector_power_on': {'icon': 'fa.video-camera', 'attr': 'green'}, # Toolbar power on
|
||||||
|
'projector_off': {'icon': 'fa.video-camera', 'attr': 'black'}, # Projector off
|
||||||
|
'projector_on': {'icon': 'fa.video-camera', 'attr': 'green'}, # Projector on
|
||||||
|
'projector_select_connect': {'icon': 'fa.plug', 'attr': 'green'}, # Toolbar connect
|
||||||
|
'projector_select_disconnect': {'icon': 'fa.plug', 'attr': 'red'}, # Toolbar disconnect
|
||||||
'projector_warmup': {'icon': 'fa.video-camera', 'attr': 'yellow'},
|
'projector_warmup': {'icon': 'fa.video-camera', 'attr': 'yellow'},
|
||||||
'picture': {'icon': 'fa.picture-o'},
|
'picture': {'icon': 'fa.picture-o'},
|
||||||
'print': {'icon': 'fa.print'},
|
'print': {'icon': 'fa.print'},
|
||||||
|
|
|
@ -206,7 +206,7 @@ class ThemesTab(SettingsTab):
|
||||||
find_and_set_in_combo_box(self.default_combo_box, self.global_theme)
|
find_and_set_in_combo_box(self.default_combo_box, self.global_theme)
|
||||||
# self.renderer.set_global_theme()
|
# self.renderer.set_global_theme()
|
||||||
self.renderer.set_theme_level(self.theme_level)
|
self.renderer.set_theme_level(self.theme_level)
|
||||||
if self.global_theme is not '':
|
if self.global_theme != '':
|
||||||
self._preview_global_theme()
|
self._preview_global_theme()
|
||||||
|
|
||||||
def _preview_global_theme(self):
|
def _preview_global_theme(self):
|
||||||
|
|
|
@ -46,6 +46,7 @@ from openlp.plugins.presentations.lib.presentationcontroller import Presentation
|
||||||
if is_win():
|
if is_win():
|
||||||
from win32com.client import Dispatch
|
from win32com.client import Dispatch
|
||||||
import pywintypes
|
import pywintypes
|
||||||
|
uno_available = False
|
||||||
# Declare an empty exception to match the exception imported from UNO
|
# Declare an empty exception to match the exception imported from UNO
|
||||||
|
|
||||||
class ErrorCodeIOException(Exception):
|
class ErrorCodeIOException(Exception):
|
||||||
|
|
|
@ -263,8 +263,9 @@ class SongSelectForm(QtWidgets.QDialog, Ui_SongSelectDialog, RegistryProperties)
|
||||||
self.login_progress_bar.setVisible(True)
|
self.login_progress_bar.setVisible(True)
|
||||||
self.application.process_events()
|
self.application.process_events()
|
||||||
# Log the user in
|
# Log the user in
|
||||||
if not self.song_select_importer.login(
|
subscription_level = self.song_select_importer.login(
|
||||||
self.username_edit.text(), self.password_edit.text(), self._update_login_progress):
|
self.username_edit.text(), self.password_edit.text(), self._update_login_progress)
|
||||||
|
if not subscription_level:
|
||||||
QtWidgets.QMessageBox.critical(
|
QtWidgets.QMessageBox.critical(
|
||||||
self,
|
self,
|
||||||
translate('SongsPlugin.SongSelectForm', 'Error Logging In'),
|
translate('SongsPlugin.SongSelectForm', 'Error Logging In'),
|
||||||
|
@ -272,6 +273,14 @@ class SongSelectForm(QtWidgets.QDialog, Ui_SongSelectDialog, RegistryProperties)
|
||||||
'There was a problem logging in, perhaps your username or password is incorrect?')
|
'There was a problem logging in, perhaps your username or password is incorrect?')
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
|
if subscription_level == 'Free':
|
||||||
|
QtWidgets.QMessageBox.information(
|
||||||
|
self,
|
||||||
|
translate('SongsPlugin.SongSelectForm', 'Free user'),
|
||||||
|
translate('SongsPlugin.SongSelectForm', 'You logged in with a free account, '
|
||||||
|
'the search will be limited to songs '
|
||||||
|
'in the public domain.')
|
||||||
|
)
|
||||||
if self.save_password_checkbox.isChecked():
|
if self.save_password_checkbox.isChecked():
|
||||||
Settings().setValue(self.plugin.settings_section + '/songselect username', self.username_edit.text())
|
Settings().setValue(self.plugin.settings_section + '/songselect username', self.username_edit.text())
|
||||||
Settings().setValue(self.plugin.settings_section + '/songselect password', self.password_edit.text())
|
Settings().setValue(self.plugin.settings_section + '/songselect password', self.password_edit.text())
|
||||||
|
|
|
@ -81,7 +81,7 @@ class SongSelectImport(object):
|
||||||
:param username: SongSelect username
|
:param username: SongSelect username
|
||||||
:param password: SongSelect password
|
:param password: SongSelect password
|
||||||
:param callback: Method to notify of progress.
|
:param callback: Method to notify of progress.
|
||||||
:return: True on success, False on failure.
|
:return: subscription level on success, None on failure.
|
||||||
"""
|
"""
|
||||||
if callback:
|
if callback:
|
||||||
callback()
|
callback()
|
||||||
|
@ -115,11 +115,31 @@ class SongSelectImport(object):
|
||||||
return False
|
return False
|
||||||
if callback:
|
if callback:
|
||||||
callback()
|
callback()
|
||||||
|
# Page if user is in an organization
|
||||||
if posted_page.find('input', id='SearchText') is not None:
|
if posted_page.find('input', id='SearchText') is not None:
|
||||||
return True
|
self.subscription_level = self.find_subscription_level(posted_page)
|
||||||
|
return self.subscription_level
|
||||||
|
# Page if user is not in an organization
|
||||||
|
elif posted_page.find('div', id="select-organization") is not None:
|
||||||
|
try:
|
||||||
|
home_page = BeautifulSoup(self.opener.open(BASE_URL).read(), 'lxml')
|
||||||
|
except (TypeError, URLError) as error:
|
||||||
|
log.exception('Could not reach SongSelect, {error}'.format(error=error))
|
||||||
|
self.subscription_level = self.find_subscription_level(home_page)
|
||||||
|
return self.subscription_level
|
||||||
else:
|
else:
|
||||||
log.debug(posted_page)
|
log.debug(posted_page)
|
||||||
return False
|
return None
|
||||||
|
|
||||||
|
def find_subscription_level(self, page):
|
||||||
|
scripts = page.find_all('script')
|
||||||
|
for tag in scripts:
|
||||||
|
if tag.string:
|
||||||
|
match = re.search("'Subscription': '(?P<subscription_level>[^']+)", tag.string)
|
||||||
|
if match:
|
||||||
|
return match.group('subscription_level')
|
||||||
|
log.error('Could not determine SongSelect subscription level')
|
||||||
|
return None
|
||||||
|
|
||||||
def logout(self):
|
def logout(self):
|
||||||
"""
|
"""
|
||||||
|
@ -128,7 +148,7 @@ class SongSelectImport(object):
|
||||||
try:
|
try:
|
||||||
self.opener.open(LOGOUT_URL)
|
self.opener.open(LOGOUT_URL)
|
||||||
except (TypeError, URLError) as error:
|
except (TypeError, URLError) as error:
|
||||||
log.exception('Could not log of SongSelect, {error}'.format(error=error))
|
log.exception('Could not log out of SongSelect, {error}'.format(error=error))
|
||||||
|
|
||||||
def search(self, search_text, max_results, callback=None):
|
def search(self, search_text, max_results, callback=None):
|
||||||
"""
|
"""
|
||||||
|
@ -145,7 +165,7 @@ class SongSelectImport(object):
|
||||||
'PrimaryLanguage': '',
|
'PrimaryLanguage': '',
|
||||||
'Keys': '',
|
'Keys': '',
|
||||||
'Themes': '',
|
'Themes': '',
|
||||||
'List': '',
|
'List': 'publicdomain' if self.subscription_level == 'Free' else '',
|
||||||
'Sort': '',
|
'Sort': '',
|
||||||
'SearchText': search_text
|
'SearchText': search_text
|
||||||
}
|
}
|
||||||
|
|
|
@ -121,7 +121,7 @@ class SongsPlugin(Plugin):
|
||||||
self.song_export_item.setVisible(True)
|
self.song_export_item.setVisible(True)
|
||||||
self.song_tools_menu.menuAction().setVisible(True)
|
self.song_tools_menu.menuAction().setVisible(True)
|
||||||
action_list = ActionList.get_instance()
|
action_list = ActionList.get_instance()
|
||||||
action_list.add_action(self.song_import_item, UiStrings().Import)
|
#action_list.add_action(self.song_import_item, UiStrings().Import)
|
||||||
action_list.add_action(self.song_export_item, UiStrings().Export)
|
action_list.add_action(self.song_export_item, UiStrings().Export)
|
||||||
action_list.add_action(self.tools_reindex_item, UiStrings().Tools)
|
action_list.add_action(self.tools_reindex_item, UiStrings().Tools)
|
||||||
action_list.add_action(self.tools_find_duplicates, UiStrings().Tools)
|
action_list.add_action(self.tools_find_duplicates, UiStrings().Tools)
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
version: OpenLP-win-ci-b{build}
|
version: OpenLP-win-ci-b{build}
|
||||||
|
|
||||||
|
image:
|
||||||
|
- Visual Studio 2017
|
||||||
|
|
||||||
clone_script:
|
clone_script:
|
||||||
- curl -L https://bazaar.launchpad.net/BRANCHPATH/tarball -o sourcecode.tar.gz
|
- curl -L https://bazaar.launchpad.net/BRANCHPATH/tarball -o sourcecode.tar.gz
|
||||||
- 7z e sourcecode.tar.gz
|
- 7z e sourcecode.tar.gz
|
||||||
|
@ -7,15 +10,17 @@ clone_script:
|
||||||
- mv BRANCHPATH openlp-branch
|
- mv BRANCHPATH openlp-branch
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
PYTHON: C:\\Python37-x64
|
matrix:
|
||||||
|
- PYTHON: C:\\Python37-x64
|
||||||
|
- PYTHON: C:\\Python37
|
||||||
|
|
||||||
install:
|
install:
|
||||||
# Install dependencies from pypi
|
# Install dependencies from pypi
|
||||||
- "%PYTHON%\\python.exe -m pip install sqlalchemy alembic appdirs chardet beautifulsoup4 lxml Mako mysql-connector-python nose mock pyodbc psycopg2 pypiwin32 websockets asyncio waitress six webob requests QtAwesome PyQt5 pymediainfo"
|
- "%PYTHON%\\python.exe -m pip install sqlalchemy alembic appdirs chardet beautifulsoup4 lxml Mako mysql-connector-python pytest mock pyodbc psycopg2 pypiwin32 websockets asyncio waitress six webob requests QtAwesome PyQt5 PyQtWebEngine pymediainfo"
|
||||||
# Download and unpack mupdf
|
# Download and unpack mupdf
|
||||||
- appveyor DownloadFile https://mupdf.com/downloads/archive/mupdf-1.14.0-windows.zip
|
- appveyor DownloadFile https://mupdf.com/downloads/archive/mupdf-1.14.0-windows.zip
|
||||||
- 7z x mupdf-1.14.0-windows.zip
|
- 7z x mupdf-1.14.0-windows.zip
|
||||||
- cp mupdf-1.14.0-windows/mupdf.exe openlp-branch/mupdf.exe
|
- cp mupdf-1.14.0-windows/mutool.exe openlp-branch/mutool.exe
|
||||||
# Download and unpack mediainfo
|
# Download and unpack mediainfo
|
||||||
- appveyor DownloadFile https://mediaarea.net/download/binary/mediainfo/18.08.1/MediaInfo_CLI_18.08.1_Windows_i386.zip
|
- appveyor DownloadFile https://mediaarea.net/download/binary/mediainfo/18.08.1/MediaInfo_CLI_18.08.1_Windows_i386.zip
|
||||||
- mkdir MediaInfo
|
- mkdir MediaInfo
|
||||||
|
@ -27,7 +32,7 @@ build: off
|
||||||
test_script:
|
test_script:
|
||||||
- cd openlp-branch
|
- cd openlp-branch
|
||||||
# Run the tests
|
# Run the tests
|
||||||
- "%PYTHON%\\python.exe -m nose -v tests"
|
- "%PYTHON%\\python.exe -m pytest -v tests"
|
||||||
# Go back to the user root folder
|
# Go back to the user root folder
|
||||||
- cd..
|
- cd..
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ ignore = E402,E722,W503,W504
|
||||||
|
|
||||||
[flake8]
|
[flake8]
|
||||||
exclude=resources.py,vlc.py
|
exclude=resources.py,vlc.py
|
||||||
|
max-line-length = 120
|
||||||
ignore = E402,W503,W504,D
|
ignore = E402,W503,W504,D
|
||||||
|
|
||||||
[pycodestyle]
|
[pycodestyle]
|
||||||
|
|
|
@ -15,13 +15,12 @@
|
||||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
|
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
|
||||||
# more details. #
|
# more details. #
|
||||||
# #
|
# #
|
||||||
|
|
||||||
|
|
||||||
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
|
||||||
###############################################################################
|
|
||||||
from tempfile import mkdtemp
|
|
||||||
# You should have received a copy of the GNU General Public License along #
|
# 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 #
|
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
||||||
|
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
||||||
|
###############################################################################
|
||||||
|
import os
|
||||||
|
from tempfile import mkdtemp
|
||||||
from unittest import TestCase
|
from unittest import TestCase
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
|
@ -57,7 +56,8 @@ class TestRemoteDeploy(TestCase):
|
||||||
# GIVEN: A new downloaded zip file
|
# GIVEN: A new downloaded zip file
|
||||||
mocked_zipfile = MagicMock()
|
mocked_zipfile = MagicMock()
|
||||||
MockZipFile.return_value = mocked_zipfile
|
MockZipFile.return_value = mocked_zipfile
|
||||||
root_path = Path('/tmp/remotes')
|
root_path_str = '{sep}tmp{sep}remotes'.format(sep=os.sep)
|
||||||
|
root_path = Path(root_path_str)
|
||||||
|
|
||||||
# WHEN: deploy_zipfile() is called
|
# WHEN: deploy_zipfile() is called
|
||||||
deploy_zipfile(root_path, 'site.zip')
|
deploy_zipfile(root_path, 'site.zip')
|
||||||
|
|
|
@ -322,7 +322,7 @@ class TestPath(TestCase):
|
||||||
obj = path.json_object(extra=1, args=2)
|
obj = path.json_object(extra=1, args=2)
|
||||||
|
|
||||||
# THEN: A JSON decodable object should have been returned.
|
# THEN: A JSON decodable object should have been returned.
|
||||||
assert obj == {'__Path__': ('/', 'base', 'path', 'to', 'fi.le')}
|
assert obj == {'__Path__': (os.sep, 'base', 'path', 'to', 'fi.le')}
|
||||||
|
|
||||||
def test_path_json_object_base_path(self):
|
def test_path_json_object_base_path(self):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -178,7 +178,7 @@ class TestImageManager(TestCase, TestMixin):
|
||||||
# THEN a KeyError is thrown
|
# THEN a KeyError is thrown
|
||||||
with self.assertRaises(KeyError) as context:
|
with self.assertRaises(KeyError) as context:
|
||||||
self.image_manager.get_image(TEST_PATH, 'church1.jpg')
|
self.image_manager.get_image(TEST_PATH, 'church1.jpg')
|
||||||
assert context.exception is not '', 'KeyError exception should have been thrown for missing image'
|
assert context.exception != '', 'KeyError exception should have been thrown for missing image'
|
||||||
|
|
||||||
@patch('openlp.core.lib.imagemanager.run_thread')
|
@patch('openlp.core.lib.imagemanager.run_thread')
|
||||||
def test_different_dimension_image(self, mocked_run_thread):
|
def test_different_dimension_image(self, mocked_run_thread):
|
||||||
|
@ -211,7 +211,7 @@ class TestImageManager(TestCase, TestMixin):
|
||||||
# WHEN: calling with correct image, but wrong dimensions
|
# WHEN: calling with correct image, but wrong dimensions
|
||||||
with self.assertRaises(KeyError) as context:
|
with self.assertRaises(KeyError) as context:
|
||||||
self.image_manager.get_image(full_path, 'church.jpg', 120, 120)
|
self.image_manager.get_image(full_path, 'church.jpg', 120, 120)
|
||||||
assert context.exception is not '', 'KeyError exception should have been thrown for missing dimension'
|
assert context.exception != '', 'KeyError exception should have been thrown for missing dimension'
|
||||||
|
|
||||||
@patch('openlp.core.lib.imagemanager.resize_image')
|
@patch('openlp.core.lib.imagemanager.resize_image')
|
||||||
@patch('openlp.core.lib.imagemanager.image_to_byte')
|
@patch('openlp.core.lib.imagemanager.image_to_byte')
|
||||||
|
|
|
@ -154,13 +154,13 @@ class TestServiceItem(TestCase, TestMixin):
|
||||||
mocked_get_section_data_path:
|
mocked_get_section_data_path:
|
||||||
mocked_exists.return_value = True
|
mocked_exists.return_value = True
|
||||||
mocked_get_section_data_path.return_value = Path('/path/')
|
mocked_get_section_data_path.return_value = Path('/path/')
|
||||||
service_item.set_from_service(line, TEST_PATHb)
|
service_item.set_from_service(line, TEST_PATH)
|
||||||
|
|
||||||
# THEN: We should get back a valid service item
|
# THEN: We should get back a valid service item
|
||||||
assert service_item.is_valid is True, 'The new service item should be valid'
|
assert service_item.is_valid is True, 'The new service item should be valid'
|
||||||
assert test_file == service_item.get_rendered_frame(0), 'The first frame should match the path to the image'
|
assert test_file == service_item.get_rendered_frame(0), 'The first frame should match the path to the image'
|
||||||
assert frame_array == service_item.get_frames()[0], 'The return should match frame array1'
|
assert frame_array == service_item.get_frames()[0], 'The return should match frame array1'
|
||||||
assert test_file == str(service_item.get_frame_path(0)), \
|
assert test_file == service_item.get_frame_path(0), \
|
||||||
'The frame path should match the full path to the image'
|
'The frame path should match the full path to the image'
|
||||||
assert image_name == service_item.get_frame_title(0), 'The frame title should match the image name'
|
assert image_name == service_item.get_frame_title(0), 'The frame title should match the image name'
|
||||||
assert image_name == service_item.get_display_title(), 'The display title should match the first image name'
|
assert image_name == service_item.get_display_title(), 'The display title should match the first image name'
|
||||||
|
@ -328,7 +328,7 @@ class TestServiceItem(TestCase, TestMixin):
|
||||||
|
|
||||||
# WHEN: We add a custom from a saved service
|
# WHEN: We add a custom from a saved service
|
||||||
line = convert_file_service_item(TEST_PATH, 'serviceitem-song-linked-audio.osj')
|
line = convert_file_service_item(TEST_PATH, 'serviceitem-song-linked-audio.osj')
|
||||||
service_item.set_from_service(line, '/test/')
|
service_item.set_from_service(line, Path('/test/'))
|
||||||
|
|
||||||
# THEN: We should get back a valid service item
|
# THEN: We should get back a valid service item
|
||||||
assert service_item.is_valid is True, 'The new service item should be valid'
|
assert service_item.is_valid is True, 'The new service item should be valid'
|
||||||
|
|
|
@ -57,4 +57,4 @@ class MediaPluginTest(TestCase, TestMixin):
|
||||||
# THEN: about() should return a string object
|
# THEN: about() should return a string object
|
||||||
assert isinstance(MediaPlugin.about(), str)
|
assert isinstance(MediaPlugin.about(), str)
|
||||||
# THEN: about() should return a non-empty string
|
# THEN: about() should return a non-empty string
|
||||||
assert len(MediaPlugin.about()) is not 0
|
assert len(MediaPlugin.about()) != 0
|
||||||
|
|
|
@ -199,7 +199,7 @@ class TestLib(TestCase):
|
||||||
result = _remove_typos(diff)
|
result = _remove_typos(diff)
|
||||||
|
|
||||||
# THEN: There should be no typos in the middle anymore. The remaining equals should have been merged.
|
# THEN: There should be no typos in the middle anymore. The remaining equals should have been merged.
|
||||||
assert len(result) is 1, 'The result should contain only one element.'
|
assert len(result) == 1, 'The result should contain only one element.'
|
||||||
assert result[0][0] == 'equal', 'The result should contain an equal element.'
|
assert result[0][0] == 'equal', 'The result should contain an equal element.'
|
||||||
assert result[0][1] == 0, 'The start indices should be kept.'
|
assert result[0][1] == 0, 'The start indices should be kept.'
|
||||||
assert result[0][2] == 22, 'The stop indices should be kept.'
|
assert result[0][2] == 22, 'The stop indices should be kept.'
|
||||||
|
|
|
@ -64,7 +64,7 @@ class TestSongSelectImport(TestCase, TestMixin):
|
||||||
@patch('openlp.plugins.songs.lib.songselect.BeautifulSoup')
|
@patch('openlp.plugins.songs.lib.songselect.BeautifulSoup')
|
||||||
def test_login_fails(self, MockedBeautifulSoup, mocked_build_opener):
|
def test_login_fails(self, MockedBeautifulSoup, mocked_build_opener):
|
||||||
"""
|
"""
|
||||||
Test that when logging in to SongSelect fails, the login method returns False
|
Test that when logging in to SongSelect fails, the login method returns None
|
||||||
"""
|
"""
|
||||||
# GIVEN: A bunch of mocked out stuff and an importer object
|
# GIVEN: A bunch of mocked out stuff and an importer object
|
||||||
mocked_opener = MagicMock()
|
mocked_opener = MagicMock()
|
||||||
|
@ -80,12 +80,12 @@ class TestSongSelectImport(TestCase, TestMixin):
|
||||||
# WHEN: The login method is called after being rigged to fail
|
# WHEN: The login method is called after being rigged to fail
|
||||||
result = importer.login('username', 'password', mock_callback)
|
result = importer.login('username', 'password', mock_callback)
|
||||||
|
|
||||||
# THEN: callback was called 3 times, open was called twice, find was called twice, and False was returned
|
# THEN: callback was called 3 times, open was called twice, find was called twice, and None was returned
|
||||||
assert 3 == mock_callback.call_count, 'callback should have been called 3 times'
|
assert 3 == mock_callback.call_count, 'callback should have been called 3 times'
|
||||||
assert 2 == mocked_login_page.find.call_count, 'find should have been called twice'
|
assert 2 == mocked_login_page.find.call_count, 'find should have been called twice'
|
||||||
assert 1 == mocked_posted_page.find.call_count, 'find should have been called once'
|
assert 2 == mocked_posted_page.find.call_count, 'find should have been called twice'
|
||||||
assert 2 == mocked_opener.open.call_count, 'opener should have been called twice'
|
assert 2 == mocked_opener.open.call_count, 'opener should have been called twice'
|
||||||
assert result is False, 'The login method should have returned False'
|
assert result is None, 'The login method should have returned None'
|
||||||
|
|
||||||
@patch('openlp.plugins.songs.lib.songselect.build_opener')
|
@patch('openlp.plugins.songs.lib.songselect.build_opener')
|
||||||
def test_login_except(self, mocked_build_opener):
|
def test_login_except(self, mocked_build_opener):
|
||||||
|
@ -129,7 +129,7 @@ class TestSongSelectImport(TestCase, TestMixin):
|
||||||
assert 2 == mocked_login_page.find.call_count, 'find should have been called twice on the login page'
|
assert 2 == mocked_login_page.find.call_count, 'find should have been called twice on the login page'
|
||||||
assert 1 == mocked_posted_page.find.call_count, 'find should have been called once on the posted page'
|
assert 1 == mocked_posted_page.find.call_count, 'find should have been called once on the posted page'
|
||||||
assert 2 == mocked_opener.open.call_count, 'opener should have been called twice'
|
assert 2 == mocked_opener.open.call_count, 'opener should have been called twice'
|
||||||
assert result is True, 'The login method should have returned True'
|
assert result is None, 'The login method should have returned the subscription level'
|
||||||
|
|
||||||
@patch('openlp.plugins.songs.lib.songselect.build_opener')
|
@patch('openlp.plugins.songs.lib.songselect.build_opener')
|
||||||
@patch('openlp.plugins.songs.lib.songselect.BeautifulSoup')
|
@patch('openlp.plugins.songs.lib.songselect.BeautifulSoup')
|
||||||
|
@ -146,7 +146,8 @@ class TestSongSelectImport(TestCase, TestMixin):
|
||||||
mocked_login_page.find.side_effect = [{'value': 'blah'}, mocked_form]
|
mocked_login_page.find.side_effect = [{'value': 'blah'}, mocked_form]
|
||||||
mocked_posted_page = MagicMock()
|
mocked_posted_page = MagicMock()
|
||||||
mocked_posted_page.find.return_value = MagicMock()
|
mocked_posted_page.find.return_value = MagicMock()
|
||||||
MockedBeautifulSoup.side_effect = [mocked_login_page, mocked_posted_page]
|
mocked_home_page = MagicMock()
|
||||||
|
MockedBeautifulSoup.side_effect = [mocked_login_page, mocked_posted_page, mocked_home_page]
|
||||||
mock_callback = MagicMock()
|
mock_callback = MagicMock()
|
||||||
importer = SongSelectImport(None)
|
importer = SongSelectImport(None)
|
||||||
|
|
||||||
|
@ -158,7 +159,7 @@ class TestSongSelectImport(TestCase, TestMixin):
|
||||||
assert 2 == mocked_login_page.find.call_count, 'find should have been called twice on the login page'
|
assert 2 == mocked_login_page.find.call_count, 'find should have been called twice on the login page'
|
||||||
assert 1 == mocked_posted_page.find.call_count, 'find should have been called once on the posted page'
|
assert 1 == mocked_posted_page.find.call_count, 'find should have been called once on the posted page'
|
||||||
assert 'https://profile.ccli.com/do/login', mocked_opener.open.call_args_list[1][0][0]
|
assert 'https://profile.ccli.com/do/login', mocked_opener.open.call_args_list[1][0][0]
|
||||||
assert result is True, 'The login method should have returned True'
|
assert result is None, 'The login method should have returned the subscription level'
|
||||||
|
|
||||||
@patch('openlp.plugins.songs.lib.songselect.build_opener')
|
@patch('openlp.plugins.songs.lib.songselect.build_opener')
|
||||||
def test_logout(self, mocked_build_opener):
|
def test_logout(self, mocked_build_opener):
|
||||||
|
@ -191,6 +192,7 @@ class TestSongSelectImport(TestCase, TestMixin):
|
||||||
MockedBeautifulSoup.return_value = mocked_results_page
|
MockedBeautifulSoup.return_value = mocked_results_page
|
||||||
mock_callback = MagicMock()
|
mock_callback = MagicMock()
|
||||||
importer = SongSelectImport(None)
|
importer = SongSelectImport(None)
|
||||||
|
importer.subscription_level = 'premium'
|
||||||
|
|
||||||
# WHEN: The login method is called after being rigged to fail
|
# WHEN: The login method is called after being rigged to fail
|
||||||
results = importer.search('text', 1000, mock_callback)
|
results = importer.search('text', 1000, mock_callback)
|
||||||
|
@ -231,6 +233,7 @@ class TestSongSelectImport(TestCase, TestMixin):
|
||||||
MockedBeautifulSoup.return_value = mocked_results_page
|
MockedBeautifulSoup.return_value = mocked_results_page
|
||||||
mock_callback = MagicMock()
|
mock_callback = MagicMock()
|
||||||
importer = SongSelectImport(None)
|
importer = SongSelectImport(None)
|
||||||
|
importer.subscription_level = 'premium'
|
||||||
|
|
||||||
# WHEN: The search method is called
|
# WHEN: The search method is called
|
||||||
results = importer.search('text', 1000, mock_callback)
|
results = importer.search('text', 1000, mock_callback)
|
||||||
|
@ -282,6 +285,7 @@ class TestSongSelectImport(TestCase, TestMixin):
|
||||||
MockedBeautifulSoup.return_value = mocked_results_page
|
MockedBeautifulSoup.return_value = mocked_results_page
|
||||||
mock_callback = MagicMock()
|
mock_callback = MagicMock()
|
||||||
importer = SongSelectImport(None)
|
importer = SongSelectImport(None)
|
||||||
|
importer.subscription_level = 'premium'
|
||||||
|
|
||||||
# WHEN: The search method is called
|
# WHEN: The search method is called
|
||||||
results = importer.search('text', 2, mock_callback)
|
results = importer.search('text', 2, mock_callback)
|
||||||
|
|
|
@ -45,8 +45,8 @@ class TestSongUsage(TestCase):
|
||||||
# THEN: about() should return a string object
|
# THEN: about() should return a string object
|
||||||
assert isinstance(SongUsagePlugin.about(), str)
|
assert isinstance(SongUsagePlugin.about(), str)
|
||||||
# THEN: about() should return a non-empty string
|
# THEN: about() should return a non-empty string
|
||||||
assert len(SongUsagePlugin.about()) is not 0
|
assert len(SongUsagePlugin.about()) != 0
|
||||||
assert len(SongUsagePlugin.about()) is not 0
|
assert len(SongUsagePlugin.about()) != 0
|
||||||
|
|
||||||
@patch('openlp.plugins.songusage.songusageplugin.Manager')
|
@patch('openlp.plugins.songusage.songusageplugin.Manager')
|
||||||
def test_song_usage_init(self, MockedManager):
|
def test_song_usage_init(self, MockedManager):
|
||||||
|
|
|
@ -134,6 +134,7 @@ class TestProjectorDB(TestCase, TestMixin):
|
||||||
"""
|
"""
|
||||||
Set up anything necessary for all tests
|
Set up anything necessary for all tests
|
||||||
"""
|
"""
|
||||||
|
self.tmp_folder = mkdtemp(prefix='openlp_')
|
||||||
# Create a test app to keep from segfaulting
|
# Create a test app to keep from segfaulting
|
||||||
Registry.create()
|
Registry.create()
|
||||||
self.registry = Registry()
|
self.registry = Registry()
|
||||||
|
@ -153,11 +154,11 @@ class TestProjectorDB(TestCase, TestMixin):
|
||||||
patch('openlp.core.ui.mainwindow.ThemeManager'), \
|
patch('openlp.core.ui.mainwindow.ThemeManager'), \
|
||||||
patch('openlp.core.ui.mainwindow.ProjectorManager'), \
|
patch('openlp.core.ui.mainwindow.ProjectorManager'), \
|
||||||
patch('openlp.core.ui.mainwindow.websockets.WebSocketServer'), \
|
patch('openlp.core.ui.mainwindow.websockets.WebSocketServer'), \
|
||||||
patch('openlp.core.ui.mainwindow.server.HttpServer'):
|
patch('openlp.core.ui.mainwindow.server.HttpServer'), \
|
||||||
|
patch('openlp.core.state.State.list_plugins') as mock_plugins:
|
||||||
|
mock_plugins.return_value = []
|
||||||
self.main_window = MainWindow()
|
self.main_window = MainWindow()
|
||||||
|
|
||||||
# Create a temporary database directory and database
|
|
||||||
self.tmp_folder = mkdtemp(prefix='openlp_')
|
|
||||||
tmpdb_url = 'sqlite:///{db}'.format(db=os.path.join(self.tmp_folder, TEST_DB))
|
tmpdb_url = 'sqlite:///{db}'.format(db=os.path.join(self.tmp_folder, TEST_DB))
|
||||||
mocked_init_url.return_value = tmpdb_url
|
mocked_init_url.return_value = tmpdb_url
|
||||||
self.projector = ProjectorDB()
|
self.projector = ProjectorDB()
|
||||||
|
|
|
@ -452,12 +452,16 @@ class TestPJLinkCommands(TestCase):
|
||||||
"""
|
"""
|
||||||
Test saving video source available information
|
Test saving video source available information
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# GIVEN: Test object
|
# GIVEN: Test object
|
||||||
with patch.object(openlp.core.projectors.pjlink, 'log') as mock_log:
|
with patch.object(openlp.core.projectors.pjlink, 'log') as mock_log:
|
||||||
pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True)
|
pjlink = PJLink(Projector(**TEST1_DATA), no_poll=True)
|
||||||
pjlink.source_available = []
|
pjlink.source_available = []
|
||||||
log_debug_calls = [call('({ip}) Setting projector sources_available to '
|
log_debug_calls = [call('({ip}) reset_information() connect status is '
|
||||||
|
'S_NOT_CONNECTED'.format(ip=pjlink.name)),
|
||||||
|
call('({ip}) Setting projector source_available to '
|
||||||
'"[\'11\', \'12\', \'21\', \'22\', \'31\', \'32\']"'.format(ip=pjlink.name))]
|
'"[\'11\', \'12\', \'21\', \'22\', \'31\', \'32\']"'.format(ip=pjlink.name))]
|
||||||
|
|
||||||
chk_data = '21 12 11 22 32 31' # Although they should already be sorted, use unsorted to verify method
|
chk_data = '21 12 11 22 32 31' # Although they should already be sorted, use unsorted to verify method
|
||||||
chk_test = ['11', '12', '21', '22', '31', '32']
|
chk_test = ['11', '12', '21', '22', '31', '32']
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,7 @@ Package to test for proper bzr tags.
|
||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
from subprocess import PIPE, Popen
|
from subprocess import PIPE, Popen
|
||||||
from unittest import TestCase
|
from unittest import TestCase, SkipTest
|
||||||
|
|
||||||
|
|
||||||
TAGS1 = {'1.9.0', '1.9.1', '1.9.2', '1.9.3', '1.9.4', '1.9.5', '1.9.6', '1.9.7', '1.9.8', '1.9.9', '1.9.10',
|
TAGS1 = {'1.9.0', '1.9.1', '1.9.2', '1.9.3', '1.9.4', '1.9.5', '1.9.6', '1.9.7', '1.9.8', '1.9.9', '1.9.10',
|
||||||
|
@ -42,7 +42,10 @@ class TestBzrTags(TestCase):
|
||||||
path = os.path.dirname(__file__)
|
path = os.path.dirname(__file__)
|
||||||
|
|
||||||
# WHEN getting the branches tags
|
# WHEN getting the branches tags
|
||||||
bzr = Popen(('bzr', 'tags', '--directory=' + path), stdout=PIPE)
|
try:
|
||||||
|
bzr = Popen(('bzr', 'tags', '--directory=' + path), stdout=PIPE)
|
||||||
|
except Exception:
|
||||||
|
raise SkipTest('bzr is not installed')
|
||||||
std_out = bzr.communicate()[0]
|
std_out = bzr.communicate()[0]
|
||||||
count = len(TAGS1)
|
count = len(TAGS1)
|
||||||
tags = [line.decode('utf-8').split()[0] for line in std_out.splitlines()]
|
tags = [line.decode('utf-8').split()[0] for line in std_out.splitlines()]
|
||||||
|
|
Loading…
Reference in New Issue