diff --git a/openlp/core/__init__.py b/openlp/core/__init__.py
index 3ae23ff2e..c9e446143 100644
--- a/openlp/core/__init__.py
+++ b/openlp/core/__init__.py
@@ -46,34 +46,14 @@ from openlp.core.ui.exceptionform import ExceptionForm
from openlp.core.ui.firsttimeform import FirstTimeForm
from openlp.core.ui.firsttimelanguageform import FirstTimeLanguageForm
from openlp.core.ui.mainwindow import MainWindow
+from openlp.core.ui.style import get_application_stylesheet
+
__all__ = ['OpenLP', 'main']
log = logging.getLogger()
-WIN_REPAIR_STYLESHEET = """
-QMainWindow::separator
-{
- border: none;
-}
-
-QDockWidget::title
-{
- border: 1px solid palette(dark);
- padding-left: 5px;
- padding-top: 2px;
- margin: 1px 0;
-}
-
-QToolBar
-{
- border: none;
- margin: 0;
- padding: 0;
-}
-"""
-
class OpenLP(OpenLPMixin, QtWidgets.QApplication):
"""
@@ -118,14 +98,7 @@ class OpenLP(OpenLPMixin, QtWidgets.QApplication):
QtCore.QCoreApplication.exit()
sys.exit()
# Correct stylesheet bugs
- application_stylesheet = ''
- if not Settings().value('advanced/alternate rows'):
- base_color = self.palette().color(QtGui.QPalette.Active, QtGui.QPalette.Base)
- alternate_rows_repair_stylesheet = \
- 'QTableWidget, QListWidget, QTreeWidget {alternate-background-color: ' + base_color.name() + ';}\n'
- application_stylesheet += alternate_rows_repair_stylesheet
- if is_win():
- application_stylesheet += WIN_REPAIR_STYLESHEET
+ application_stylesheet = get_application_stylesheet()
if application_stylesheet:
self.setStyleSheet(application_stylesheet)
can_show_splash = Settings().value('core/show splash')
diff --git a/openlp/plugins/remotes/deploy.py b/openlp/core/api/deploy.py
similarity index 96%
rename from openlp/plugins/remotes/deploy.py
rename to openlp/core/api/deploy.py
index 44c628837..9b1e6e793 100644
--- a/openlp/plugins/remotes/deploy.py
+++ b/openlp/core/api/deploy.py
@@ -63,7 +63,7 @@ def download_and_check(callback=None):
sha256, version = download_sha256()
file_size = get_url_file_size('https://get.openlp.org/webclient/site.zip')
callback.setRange(0, file_size)
- if url_get_file(callback, '{host}{name}'.format(host='https://get.openlp.org/webclient/', name='site.zip'),
+ if url_get_file(callback, 'https://get.openlp.org/webclient/site.zip',
AppLocation.get_section_data_path('remotes') / 'site.zip',
sha256=sha256):
deploy_zipfile(str(AppLocation.get_section_data_path('remotes')), 'site.zip')
diff --git a/openlp/plugins/remotes/endpoint.py b/openlp/core/api/endpoint/remote.py
similarity index 90%
rename from openlp/plugins/remotes/endpoint.py
rename to openlp/core/api/endpoint/remote.py
index a9b0d0815..4741ada15 100644
--- a/openlp/plugins/remotes/endpoint.py
+++ b/openlp/core/api/endpoint/remote.py
@@ -21,18 +21,13 @@
###############################################################################
import logging
-import os
-
from openlp.core.api.http.endpoint import Endpoint
from openlp.core.api.endpoint.core import TRANSLATED_STRINGS
-from openlp.core.common import AppLocation
-static_dir = os.path.join(str(AppLocation.get_section_data_path('remotes')))
-
log = logging.getLogger(__name__)
-remote_endpoint = Endpoint('remote', template_dir=static_dir, static_dir=static_dir)
+remote_endpoint = Endpoint('remote', template_dir='remotes', static_dir='remotes')
@remote_endpoint.route('{view}')
diff --git a/openlp/core/api/endpoint/service.py b/openlp/core/api/endpoint/service.py
index acb139b43..4e3b53fbb 100644
--- a/openlp/core/api/endpoint/service.py
+++ b/openlp/core/api/endpoint/service.py
@@ -23,7 +23,7 @@ import logging
import json
from openlp.core.api.http.endpoint import Endpoint
-from openlp.core.api.http import register_endpoint, requires_auth
+from openlp.core.api.http import requires_auth
from openlp.core.common import Registry
diff --git a/openlp/core/api/http/server.py b/openlp/core/api/http/server.py
index a7ec34903..2a2ec7292 100644
--- a/openlp/core/api/http/server.py
+++ b/openlp/core/api/http/server.py
@@ -26,17 +26,24 @@ with OpenLP. It uses JSON to communicate with the remotes.
"""
import logging
+import time
-from PyQt5 import QtCore
+from PyQt5 import QtCore, QtWidgets
from waitress import serve
from openlp.core.api.http import register_endpoint
from openlp.core.api.http import application
-from openlp.core.common import RegistryMixin, RegistryProperties, OpenLPMixin, Settings, Registry
+from openlp.core.common import AppLocation, RegistryMixin, RegistryProperties, OpenLPMixin, \
+ Settings, Registry, UiStrings, check_directory_exists
+from openlp.core.lib import translate
+
+from openlp.core.api.deploy import download_and_check, download_sha256
from openlp.core.api.poll import Poller
from openlp.core.api.endpoint.controller import controller_endpoint, api_controller_endpoint
from openlp.core.api.endpoint.core import chords_endpoint, stage_endpoint, blank_endpoint, main_endpoint
from openlp.core.api.endpoint.service import service_endpoint, api_service_endpoint
+from openlp.core.api.endpoint.remote import remote_endpoint
+
log = logging.getLogger(__name__)
@@ -59,6 +66,7 @@ class HttpWorker(QtCore.QObject):
"""
address = Settings().value('api/ip address')
port = Settings().value('api/port')
+ Registry().execute('get_website_version')
serve(application, host=address, port=port)
def stop(self):
@@ -79,11 +87,15 @@ class HttpServer(RegistryMixin, RegistryProperties, OpenLPMixin):
self.worker.moveToThread(self.thread)
self.thread.started.connect(self.worker.run)
self.thread.start()
+ Registry().register_function('download_website', self.first_time)
+ Registry().register_function('get_website_version', self.website_version)
+ Registry().set_flag('website_version', '0.0')
def bootstrap_post_set_up(self):
"""
Register the poll return service and start the servers.
"""
+ self.initialise()
self.poller = Poller()
Registry().register('poller', self.poller)
application.initialise()
@@ -95,3 +107,79 @@ class HttpServer(RegistryMixin, RegistryProperties, OpenLPMixin):
register_endpoint(main_endpoint)
register_endpoint(service_endpoint)
register_endpoint(api_service_endpoint)
+ register_endpoint(remote_endpoint)
+
+ @staticmethod
+ def initialise():
+ """
+ Create the internal file structure if it does not exist
+ :return:
+ """
+ check_directory_exists(AppLocation.get_section_data_path('remotes') / 'assets')
+ check_directory_exists(AppLocation.get_section_data_path('remotes') / 'images')
+ check_directory_exists(AppLocation.get_section_data_path('remotes') / 'static')
+ check_directory_exists(AppLocation.get_section_data_path('remotes') / 'static' / 'index')
+ check_directory_exists(AppLocation.get_section_data_path('remotes') / 'templates')
+
+ def first_time(self):
+ """
+ Import web site code if active
+ """
+ self.application.process_events()
+ progress = DownloadProgressDialog(self)
+ progress.forceShow()
+ self.application.process_events()
+ time.sleep(1)
+ download_and_check(progress)
+ self.application.process_events()
+ time.sleep(1)
+ progress.close()
+ self.application.process_events()
+ Settings().setValue('remotes/download version', self.version)
+
+ def website_version(self):
+ """
+ Download and save the website version and sha256
+ :return: None
+ """
+ sha256, self.version = download_sha256()
+ Registry().set_flag('website_sha256', sha256)
+ Registry().set_flag('website_version', self.version)
+
+
+class DownloadProgressDialog(QtWidgets.QProgressDialog):
+ """
+ Local class to handle download display based and supporting httputils:get_web_page
+ """
+ def __init__(self, parent):
+ super(DownloadProgressDialog, self).__init__(parent.main_window)
+ self.parent = parent
+ self.setWindowModality(QtCore.Qt.WindowModal)
+ self.setWindowTitle(translate('RemotePlugin', 'Importing Website'))
+ self.setLabelText(UiStrings().StartingImport)
+ self.setCancelButton(None)
+ self.setRange(0, 1)
+ self.setMinimumDuration(0)
+ self.was_cancelled = False
+ self.previous_size = 0
+
+ def _download_progress(self, count, block_size):
+ """
+ Calculate and display the download progress.
+ """
+ increment = (count * block_size) - self.previous_size
+ self._increment_progress_bar(None, increment)
+ self.previous_size = count * block_size
+
+ def _increment_progress_bar(self, status_text, increment=1):
+ """
+ Update the wizard progress page.
+
+ :param status_text: Current status information to display.
+ :param increment: The value to increment the progress bar by.
+ """
+ if status_text:
+ self.setText(status_text)
+ if increment > 0:
+ self.setValue(self.value() + increment)
+ self.parent.application.process_events()
diff --git a/openlp/core/api/tab.py b/openlp/core/api/tab.py
index 3ec8c4515..e10a68238 100644
--- a/openlp/core/api/tab.py
+++ b/openlp/core/api/tab.py
@@ -222,6 +222,8 @@ class ApiTab(SettingsTab):
self.remote_url.setText('{url}'.format(url=http_url))
http_url_temp = http_url + 'stage'
self.stage_url.setText('{url}'.format(url=http_url_temp))
+ http_url_temp = http_url + 'chords'
+ self.chords_url.setText('{url}'.format(url=http_url_temp))
http_url_temp = http_url + 'main'
self.live_url.setText('{url}'.format(url=http_url_temp))
diff --git a/openlp/core/common/settings.py b/openlp/core/common/settings.py
index be1b29c75..ca59d4e6f 100644
--- a/openlp/core/common/settings.py
+++ b/openlp/core/common/settings.py
@@ -136,6 +136,7 @@ class Settings(QtCore.QSettings):
'advanced/single click service preview': False,
'advanced/x11 bypass wm': X11_BYPASS_DEFAULT,
'advanced/search as type': True,
+ 'advanced/use_dark_style': False,
'api/twelve hour': True,
'api/port': 4316,
'api/websocket port': 4317,
@@ -177,6 +178,7 @@ class Settings(QtCore.QSettings):
'images/background color': '#000000',
'media/players': 'system,webkit',
'media/override player': QtCore.Qt.Unchecked,
+ 'remotes/download version': '0.0',
'players/background color': '#000000',
'servicemanager/last directory': None,
'servicemanager/last file': None,
diff --git a/openlp/core/ui/advancedtab.py b/openlp/core/ui/advancedtab.py
index 8ec55999b..26303f1cb 100644
--- a/openlp/core/ui/advancedtab.py
+++ b/openlp/core/ui/advancedtab.py
@@ -32,6 +32,7 @@ from openlp.core.common.languagemanager import format_time
from openlp.core.common.path import path_to_str
from openlp.core.lib import SettingsTab, build_icon
from openlp.core.ui.lib import PathEdit, PathType
+from openlp.core.ui.style import HAS_DARK_STYLE
log = logging.getLogger(__name__)
@@ -109,8 +110,80 @@ class AdvancedTab(SettingsTab):
self.enable_auto_close_check_box.setObjectName('enable_auto_close_check_box')
self.ui_layout.addRow(self.enable_auto_close_check_box)
self.left_layout.addWidget(self.ui_group_box)
+ if HAS_DARK_STYLE:
+ self.use_dark_style_checkbox = QtWidgets.QCheckBox(self.ui_group_box)
+ self.use_dark_style_checkbox.setObjectName('use_dark_style_checkbox')
+ self.ui_layout.addRow(self.use_dark_style_checkbox)
+ # Data Directory
+ self.data_directory_group_box = QtWidgets.QGroupBox(self.left_column)
+ self.data_directory_group_box.setObjectName('data_directory_group_box')
+ self.data_directory_layout = QtWidgets.QFormLayout(self.data_directory_group_box)
+ self.data_directory_layout.setObjectName('data_directory_layout')
+ self.data_directory_new_label = QtWidgets.QLabel(self.data_directory_group_box)
+ self.data_directory_new_label.setObjectName('data_directory_current_label')
+ self.data_directory_path_edit = PathEdit(self.data_directory_group_box, path_type=PathType.Directories,
+ default_path=AppLocation.get_directory(AppLocation.DataDir))
+ self.data_directory_layout.addRow(self.data_directory_new_label, self.data_directory_path_edit)
+ self.new_data_directory_has_files_label = QtWidgets.QLabel(self.data_directory_group_box)
+ self.new_data_directory_has_files_label.setObjectName('new_data_directory_has_files_label')
+ self.new_data_directory_has_files_label.setWordWrap(True)
+ self.data_directory_cancel_button = QtWidgets.QToolButton(self.data_directory_group_box)
+ self.data_directory_cancel_button.setObjectName('data_directory_cancel_button')
+ self.data_directory_cancel_button.setIcon(build_icon(':/general/general_delete.png'))
+ self.data_directory_copy_check_layout = QtWidgets.QHBoxLayout()
+ self.data_directory_copy_check_layout.setObjectName('data_directory_copy_check_layout')
+ self.data_directory_copy_check_box = QtWidgets.QCheckBox(self.data_directory_group_box)
+ self.data_directory_copy_check_box.setObjectName('data_directory_copy_check_box')
+ self.data_directory_copy_check_layout.addWidget(self.data_directory_copy_check_box)
+ self.data_directory_copy_check_layout.addStretch()
+ self.data_directory_copy_check_layout.addWidget(self.data_directory_cancel_button)
+ self.data_directory_layout.addRow(self.data_directory_copy_check_layout)
+ self.data_directory_layout.addRow(self.new_data_directory_has_files_label)
+ self.left_layout.addWidget(self.data_directory_group_box)
+ # Hide mouse
+ self.hide_mouse_group_box = QtWidgets.QGroupBox(self.right_column)
+ self.hide_mouse_group_box.setObjectName('hide_mouse_group_box')
+ self.hide_mouse_layout = QtWidgets.QVBoxLayout(self.hide_mouse_group_box)
+ self.hide_mouse_layout.setObjectName('hide_mouse_layout')
+ self.hide_mouse_check_box = QtWidgets.QCheckBox(self.hide_mouse_group_box)
+ self.hide_mouse_check_box.setObjectName('hide_mouse_check_box')
+ self.hide_mouse_layout.addWidget(self.hide_mouse_check_box)
+ self.right_layout.addWidget(self.hide_mouse_group_box)
+ # Service Item Slide Limits
+ self.slide_group_box = QtWidgets.QGroupBox(self.right_column)
+ self.slide_group_box.setObjectName('slide_group_box')
+ self.slide_layout = QtWidgets.QVBoxLayout(self.slide_group_box)
+ self.slide_layout.setObjectName('slide_layout')
+ self.slide_label = QtWidgets.QLabel(self.slide_group_box)
+ self.slide_label.setWordWrap(True)
+ self.slide_layout.addWidget(self.slide_label)
+ self.end_slide_radio_button = QtWidgets.QRadioButton(self.slide_group_box)
+ self.end_slide_radio_button.setObjectName('end_slide_radio_button')
+ self.slide_layout.addWidget(self.end_slide_radio_button)
+ self.wrap_slide_radio_button = QtWidgets.QRadioButton(self.slide_group_box)
+ self.wrap_slide_radio_button.setObjectName('wrap_slide_radio_button')
+ self.slide_layout.addWidget(self.wrap_slide_radio_button)
+ self.next_item_radio_button = QtWidgets.QRadioButton(self.slide_group_box)
+ self.next_item_radio_button.setObjectName('next_item_radio_button')
+ self.slide_layout.addWidget(self.next_item_radio_button)
+ self.right_layout.addWidget(self.slide_group_box)
+ # Display Workarounds
+ self.display_workaround_group_box = QtWidgets.QGroupBox(self.right_column)
+ self.display_workaround_group_box.setObjectName('display_workaround_group_box')
+ self.display_workaround_layout = QtWidgets.QVBoxLayout(self.display_workaround_group_box)
+ self.display_workaround_layout.setObjectName('display_workaround_layout')
+ self.ignore_aspect_ratio_check_box = QtWidgets.QCheckBox(self.display_workaround_group_box)
+ self.ignore_aspect_ratio_check_box.setObjectName('ignore_aspect_ratio_check_box')
+ self.display_workaround_layout.addWidget(self.ignore_aspect_ratio_check_box)
+ self.x11_bypass_check_box = QtWidgets.QCheckBox(self.display_workaround_group_box)
+ self.x11_bypass_check_box.setObjectName('x11_bypass_check_box')
+ self.display_workaround_layout.addWidget(self.x11_bypass_check_box)
+ self.alternate_rows_check_box = QtWidgets.QCheckBox(self.display_workaround_group_box)
+ self.alternate_rows_check_box.setObjectName('alternate_rows_check_box')
+ self.display_workaround_layout.addWidget(self.alternate_rows_check_box)
+ self.right_layout.addWidget(self.display_workaround_group_box)
# Default service name
- self.service_name_group_box = QtWidgets.QGroupBox(self.left_column)
+ self.service_name_group_box = QtWidgets.QGroupBox(self.right_column)
self.service_name_group_box.setObjectName('service_name_group_box')
self.service_name_layout = QtWidgets.QFormLayout(self.service_name_group_box)
self.service_name_check_box = QtWidgets.QCheckBox(self.service_name_group_box)
@@ -147,77 +220,11 @@ class AdvancedTab(SettingsTab):
self.service_name_example = QtWidgets.QLabel(self.service_name_group_box)
self.service_name_example.setObjectName('service_name_example')
self.service_name_layout.addRow(self.service_name_example_label, self.service_name_example)
- self.left_layout.addWidget(self.service_name_group_box)
- # Data Directory
- self.data_directory_group_box = QtWidgets.QGroupBox(self.left_column)
- self.data_directory_group_box.setObjectName('data_directory_group_box')
- self.data_directory_layout = QtWidgets.QFormLayout(self.data_directory_group_box)
- self.data_directory_layout.setObjectName('data_directory_layout')
- self.data_directory_new_label = QtWidgets.QLabel(self.data_directory_group_box)
- self.data_directory_new_label.setObjectName('data_directory_current_label')
- self.data_directory_path_edit = PathEdit(self.data_directory_group_box, path_type=PathType.Directories,
- default_path=AppLocation.get_directory(AppLocation.DataDir))
- self.data_directory_layout.addRow(self.data_directory_new_label, self.data_directory_path_edit)
- self.new_data_directory_has_files_label = QtWidgets.QLabel(self.data_directory_group_box)
- self.new_data_directory_has_files_label.setObjectName('new_data_directory_has_files_label')
- self.new_data_directory_has_files_label.setWordWrap(True)
- self.data_directory_cancel_button = QtWidgets.QToolButton(self.data_directory_group_box)
- self.data_directory_cancel_button.setObjectName('data_directory_cancel_button')
- self.data_directory_cancel_button.setIcon(build_icon(':/general/general_delete.png'))
- self.data_directory_copy_check_layout = QtWidgets.QHBoxLayout()
- self.data_directory_copy_check_layout.setObjectName('data_directory_copy_check_layout')
- self.data_directory_copy_check_box = QtWidgets.QCheckBox(self.data_directory_group_box)
- self.data_directory_copy_check_box.setObjectName('data_directory_copy_check_box')
- self.data_directory_copy_check_layout.addWidget(self.data_directory_copy_check_box)
- self.data_directory_copy_check_layout.addStretch()
- self.data_directory_copy_check_layout.addWidget(self.data_directory_cancel_button)
- self.data_directory_layout.addRow(self.data_directory_copy_check_layout)
- self.data_directory_layout.addRow(self.new_data_directory_has_files_label)
- self.left_layout.addWidget(self.data_directory_group_box)
+ self.right_layout.addWidget(self.service_name_group_box)
+ # After the last item on each side, add some spacing
self.left_layout.addStretch()
- # Hide mouse
- self.hide_mouse_group_box = QtWidgets.QGroupBox(self.right_column)
- self.hide_mouse_group_box.setObjectName('hide_mouse_group_box')
- self.hide_mouse_layout = QtWidgets.QVBoxLayout(self.hide_mouse_group_box)
- self.hide_mouse_layout.setObjectName('hide_mouse_layout')
- self.hide_mouse_check_box = QtWidgets.QCheckBox(self.hide_mouse_group_box)
- self.hide_mouse_check_box.setObjectName('hide_mouse_check_box')
- self.hide_mouse_layout.addWidget(self.hide_mouse_check_box)
- self.right_layout.addWidget(self.hide_mouse_group_box)
- # Service Item Slide Limits
- self.slide_group_box = QtWidgets.QGroupBox(self.right_column)
- self.slide_group_box.setObjectName('slide_group_box')
- self.slide_layout = QtWidgets.QVBoxLayout(self.slide_group_box)
- self.slide_layout.setObjectName('slide_layout')
- self.slide_label = QtWidgets.QLabel(self.slide_group_box)
- self.slide_label.setWordWrap(True)
- self.slide_layout.addWidget(self.slide_label)
- self.end_slide_radio_button = QtWidgets.QRadioButton(self.slide_group_box)
- self.end_slide_radio_button.setObjectName('end_slide_radio_button')
- self.slide_layout.addWidget(self.end_slide_radio_button)
- self.wrap_slide_radio_button = QtWidgets.QRadioButton(self.slide_group_box)
- self.wrap_slide_radio_button.setObjectName('wrap_slide_radio_button')
- self.slide_layout.addWidget(self.wrap_slide_radio_button)
- self.next_item_radio_button = QtWidgets.QRadioButton(self.slide_group_box)
- self.next_item_radio_button.setObjectName('next_item_radio_button')
- self.slide_layout.addWidget(self.next_item_radio_button)
- self.right_layout.addWidget(self.slide_group_box)
- # Display Workarounds
- self.display_workaround_group_box = QtWidgets.QGroupBox(self.left_column)
- self.display_workaround_group_box.setObjectName('display_workaround_group_box')
- self.display_workaround_layout = QtWidgets.QVBoxLayout(self.display_workaround_group_box)
- self.display_workaround_layout.setObjectName('display_workaround_layout')
- self.ignore_aspect_ratio_check_box = QtWidgets.QCheckBox(self.display_workaround_group_box)
- self.ignore_aspect_ratio_check_box.setObjectName('ignore_aspect_ratio_check_box')
- self.display_workaround_layout.addWidget(self.ignore_aspect_ratio_check_box)
- self.x11_bypass_check_box = QtWidgets.QCheckBox(self.display_workaround_group_box)
- self.x11_bypass_check_box.setObjectName('x11_bypass_check_box')
- self.display_workaround_layout.addWidget(self.x11_bypass_check_box)
- self.alternate_rows_check_box = QtWidgets.QCheckBox(self.display_workaround_group_box)
- self.alternate_rows_check_box.setObjectName('alternate_rows_check_box')
- self.display_workaround_layout.addWidget(self.alternate_rows_check_box)
- self.right_layout.addWidget(self.display_workaround_group_box)
self.right_layout.addStretch()
+ # Set up all the connections and things
self.should_update_service_name_example = False
self.service_name_check_box.toggled.connect(self.service_name_check_box_toggled)
self.service_name_day.currentIndexChanged.connect(self.on_service_name_day_changed)
@@ -282,6 +289,8 @@ class AdvancedTab(SettingsTab):
'Auto-scroll the next slide to bottom'))
self.enable_auto_close_check_box.setText(translate('OpenLP.AdvancedTab',
'Enable application exit confirmation'))
+ if HAS_DARK_STYLE:
+ self.use_dark_style_checkbox.setText(translate('OpenLP.AdvancedTab', 'Use dark style (needs restart)'))
self.service_name_group_box.setTitle(translate('OpenLP.AdvancedTab', 'Default Service Name'))
self.service_name_check_box.setText(translate('OpenLP.AdvancedTab', 'Enable default service name'))
self.service_name_time_label.setText(translate('OpenLP.AdvancedTab', 'Date and Time:'))
@@ -349,6 +358,8 @@ class AdvancedTab(SettingsTab):
if self.autoscroll_map[i] == autoscroll_value and i < self.autoscroll_combo_box.count():
self.autoscroll_combo_box.setCurrentIndex(i)
self.enable_auto_close_check_box.setChecked(settings.value('enable exit confirmation'))
+ if HAS_DARK_STYLE:
+ self.use_dark_style_checkbox.setChecked(settings.value('use_dark_style'))
self.hide_mouse_check_box.setChecked(settings.value('hide mouse'))
self.service_name_day.setCurrentIndex(settings.value('default service day'))
self.service_name_time.setTime(QtCore.QTime(settings.value('default service hour'),
@@ -420,6 +431,8 @@ class AdvancedTab(SettingsTab):
self.settings_form.register_post_process('config_screen_changed')
self.settings_form.register_post_process('slidecontroller_update_slide_limits')
settings.setValue('search as type', self.is_search_as_you_type_enabled)
+ if HAS_DARK_STYLE:
+ settings.setValue('use_dark_style', self.use_dark_style_checkbox.isChecked())
settings.endGroup()
def on_search_as_type_check_box_changed(self, check_state):
diff --git a/openlp/core/ui/mainwindow.py b/openlp/core/ui/mainwindow.py
index e0232e115..25fe818ee 100644
--- a/openlp/core/ui/mainwindow.py
+++ b/openlp/core/ui/mainwindow.py
@@ -51,31 +51,12 @@ from openlp.core.ui.projector.manager import ProjectorManager
from openlp.core.ui.lib.dockwidget import OpenLPDockWidget
from openlp.core.ui.lib.filedialog import FileDialog
from openlp.core.ui.lib.mediadockmanager import MediaDockManager
+from openlp.core.ui.style import PROGRESSBAR_STYLE, get_library_stylesheet
from openlp.core.version import get_version
log = logging.getLogger(__name__)
-MEDIA_MANAGER_STYLE = """
-::tab#media_tool_box {
- background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
- stop: 0 palette(button), stop: 1.0 palette(mid));
- border: 0;
- border-radius: 2px;
- margin-top: 0;
- margin-bottom: 0;
- text-align: left;
-}
-/* This is here to make the tabs on KDE with the Breeze theme work */
-::tab:selected {}
-"""
-
-PROGRESSBAR_STYLE = """
-QProgressBar{
- height: 10px;
-}
-"""
-
class Ui_MainWindow(object):
"""
@@ -155,7 +136,7 @@ class Ui_MainWindow(object):
# Create the MediaManager
self.media_manager_dock = OpenLPDockWidget(main_window, 'media_manager_dock',
':/system/system_mediamanager.png')
- self.media_manager_dock.setStyleSheet(MEDIA_MANAGER_STYLE)
+ self.media_manager_dock.setStyleSheet(get_library_stylesheet())
# Create the media toolbox
self.media_tool_box = QtWidgets.QToolBox(self.media_manager_dock)
self.media_tool_box.setObjectName('media_tool_box')
diff --git a/openlp/core/ui/style.py b/openlp/core/ui/style.py
new file mode 100644
index 000000000..764a3bd26
--- /dev/null
+++ b/openlp/core/ui/style.py
@@ -0,0 +1,109 @@
+# -*- coding: utf-8 -*-
+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
+
+###############################################################################
+# OpenLP - Open Source Lyrics Projection #
+# --------------------------------------------------------------------------- #
+# Copyright (c) 2008-2017 OpenLP Developers #
+# --------------------------------------------------------------------------- #
+# 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:`~openlp.core.ui.dark` module looks for and loads a dark theme
+"""
+from PyQt5 import QtGui
+
+from openlp.core.common import is_macosx, is_win
+from openlp.core.common.registry import Registry
+from openlp.core.common.settings import Settings
+
+try:
+ import qdarkstyle
+ HAS_DARK_STYLE = True
+except ImportError:
+ HAS_DARK_STYLE = False
+
+WIN_REPAIR_STYLESHEET = """
+QMainWindow::separator
+{
+ border: none;
+}
+
+QDockWidget::title
+{
+ border: 1px solid palette(dark);
+ padding-left: 5px;
+ padding-top: 2px;
+ margin: 1px 0;
+}
+
+QToolBar
+{
+ border: none;
+ margin: 0;
+ padding: 0;
+}
+"""
+
+MEDIA_MANAGER_STYLE = """
+::tab#media_tool_box {
+ background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
+ stop: 0 palette(button), stop: 1.0 palette(mid));
+ border: 0;
+ border-radius: 2px;
+ margin-top: 0;
+ margin-bottom: 0;
+ text-align: left;
+}
+/* This is here to make the tabs on KDE with the Breeze theme work */
+::tab:selected {}
+"""
+
+PROGRESSBAR_STYLE = """
+QProgressBar{
+ height: 10px;
+}
+"""
+
+
+def get_application_stylesheet():
+ """
+ Return the correct application stylesheet based on the current style and operating system
+
+ :return str: The correct stylesheet as a string
+ """
+ stylesheet = ''
+ if HAS_DARK_STYLE and Settings().value('advanced/use_dark_style'):
+ stylesheet = qdarkstyle.load_stylesheet_pyqt5()
+ else:
+ if not Settings().value('advanced/alternate rows'):
+ base_color = Registry().get('application').palette().color(QtGui.QPalette.Active, QtGui.QPalette.Base)
+ alternate_rows_repair_stylesheet = \
+ 'QTableWidget, QListWidget, QTreeWidget {alternate-background-color: ' + base_color.name() + ';}\n'
+ stylesheet += alternate_rows_repair_stylesheet
+ if is_win():
+ stylesheet += WIN_REPAIR_STYLESHEET
+ return stylesheet
+
+
+def get_library_stylesheet():
+ """
+ Return the correct stylesheet for the main window
+
+ :return str: The correct stylesheet as a string
+ """
+ if not HAS_DARK_STYLE or not Settings().value('advanced/use_dark_style'):
+ return MEDIA_MANAGER_STYLE
+ else:
+ return ''
diff --git a/openlp/plugins/bibles/endpoint.py b/openlp/plugins/bibles/endpoint.py
index 08a945e7c..4d34eb966 100644
--- a/openlp/plugins/bibles/endpoint.py
+++ b/openlp/plugins/bibles/endpoint.py
@@ -62,7 +62,7 @@ def bibles_service(request):
:param request: The http request object.
"""
- service(request, 'bibles', log)
+ return service(request, 'bibles', log)
@api_bibles_endpoint.route('bibles/search')
@@ -95,6 +95,6 @@ def bibles_service_api(request):
:param request: The http request object.
"""
try:
- search(request, 'bibles', log)
+ return search(request, 'bibles', log)
except NotFound:
return {'results': {'items': []}}
diff --git a/openlp/plugins/custom/endpoint.py b/openlp/plugins/custom/endpoint.py
index 687ffaa1b..ca5c39088 100644
--- a/openlp/plugins/custom/endpoint.py
+++ b/openlp/plugins/custom/endpoint.py
@@ -62,7 +62,7 @@ def custom_service(request):
:param request: The http request object.
"""
- service(request, 'custom', log)
+ return service(request, 'custom', log)
@api_custom_endpoint.route('custom/search')
@@ -95,6 +95,6 @@ def custom_service_api(request):
:param request: The http request object.
"""
try:
- search(request, 'custom', log)
+ return search(request, 'custom', log)
except NotFound:
return {'results': {'items': []}}
diff --git a/openlp/plugins/images/endpoint.py b/openlp/plugins/images/endpoint.py
index ca82da00a..05c1e64b4 100644
--- a/openlp/plugins/images/endpoint.py
+++ b/openlp/plugins/images/endpoint.py
@@ -75,7 +75,7 @@ def images_service(request):
:param request: The http request object.
"""
- service(request, 'images', log)
+ return service(request, 'images', log)
@api_images_endpoint.route('images/search')
@@ -108,6 +108,6 @@ def images_service_api(request):
:param request: The http request object.
"""
try:
- search(request, 'images', log)
+ return search(request, 'images', log)
except NotFound:
return {'results': {'items': []}}
diff --git a/openlp/plugins/media/endpoint.py b/openlp/plugins/media/endpoint.py
index 014c3c790..c7b703358 100644
--- a/openlp/plugins/media/endpoint.py
+++ b/openlp/plugins/media/endpoint.py
@@ -62,7 +62,7 @@ def media_service(request):
:param request: The http request object.
"""
- service(request, 'media', log)
+ return service(request, 'media', log)
@api_media_endpoint.route('media/search')
@@ -95,6 +95,6 @@ def media_service_api(request):
:param request: The http request object.
"""
try:
- search(request, 'media', log)
+ return search(request, 'media', log)
except NotFound:
return {'results': {'items': []}}
diff --git a/openlp/plugins/presentations/endpoint.py b/openlp/plugins/presentations/endpoint.py
index ce622083c..99c828b4b 100644
--- a/openlp/plugins/presentations/endpoint.py
+++ b/openlp/plugins/presentations/endpoint.py
@@ -76,7 +76,7 @@ def presentations_service(request):
:param request: The http request object.
"""
- service(request, 'presentations', log)
+ return service(request, 'presentations', log)
@api_presentations_endpoint.route('presentations/search')
@@ -109,6 +109,6 @@ def presentations_service_api(request):
:param request: The http request object.
"""
try:
- search(request, 'presentations', log)
+ return search(request, 'presentations', log)
except NotFound:
return {'results': {'items': []}}
diff --git a/openlp/plugins/remotes/__init__.py b/openlp/plugins/remotes/__init__.py
deleted file mode 100644
index ea62548f4..000000000
--- a/openlp/plugins/remotes/__init__.py
+++ /dev/null
@@ -1,21 +0,0 @@
-# -*- coding: utf-8 -*-
-# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
-
-###############################################################################
-# OpenLP - Open Source Lyrics Projection #
-# --------------------------------------------------------------------------- #
-# Copyright (c) 2008-2017 OpenLP Developers #
-# --------------------------------------------------------------------------- #
-# 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 #
-###############################################################################
diff --git a/openlp/plugins/remotes/remoteplugin.py b/openlp/plugins/remotes/remoteplugin.py
deleted file mode 100644
index 1344c66d1..000000000
--- a/openlp/plugins/remotes/remoteplugin.py
+++ /dev/null
@@ -1,155 +0,0 @@
-# -*- coding: utf-8 -*-
-# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
-
-###############################################################################
-# OpenLP - Open Source Lyrics Projection #
-# --------------------------------------------------------------------------- #
-# Copyright (c) 2008-2017 OpenLP Developers #
-# --------------------------------------------------------------------------- #
-# 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 #
-###############################################################################
-
-import logging
-import os
-import time
-
-from PyQt5 import QtCore, QtWidgets
-
-from openlp.core.api.http import register_endpoint
-from openlp.core.common import AppLocation, Registry, Settings, OpenLPMixin, UiStrings, check_directory_exists
-from openlp.core.lib import Plugin, StringContent, translate, build_icon
-from openlp.plugins.remotes.endpoint import remote_endpoint
-from openlp.plugins.remotes.deploy import download_and_check, download_sha256
-
-log = logging.getLogger(__name__)
-__default_settings__ = {
- 'remotes/download version': '0000_00_00'
-}
-
-
-class RemotesPlugin(Plugin, OpenLPMixin):
- log.info('Remotes Plugin loaded')
-
- def __init__(self):
- """
- remotes constructor
- """
- super(RemotesPlugin, self).__init__('remotes', __default_settings__, {})
- self.icon_path = ':/plugins/plugin_remote.png'
- self.icon = build_icon(self.icon_path)
- self.weight = -1
- register_endpoint(remote_endpoint)
- Registry().register_function('download_website', self.first_time)
- Registry().register_function('get_website_version', self.website_version)
- Registry().set_flag('website_version', '0001_01_01')
-
- def initialise(self):
- """
- Create the internal file structure if it does not exist
- :return:
- """
- check_directory_exists(AppLocation.get_section_data_path('remotes') / 'assets')
- check_directory_exists(AppLocation.get_section_data_path('remotes') / 'images')
- check_directory_exists(AppLocation.get_section_data_path('remotes') / 'static')
- check_directory_exists(AppLocation.get_section_data_path('remotes') / 'static', 'index')
- check_directory_exists(AppLocation.get_section_data_path('remotes') / 'templates')
-
- @staticmethod
- def about():
- """
- Information about this plugin
- """
- about_text = translate(
- 'RemotePlugin',
- 'Web Interface'
- '
The web interface plugin provides the ability to develop web based interfaces using OpenLP web '
- 'services.\nPredefined interfaces can be download as well as custom developed interfaces.')
- return about_text
-
- def set_plugin_text_strings(self):
- """
- Called to define all translatable texts of the plugin
- """
- # Name PluginList
- self.text_strings[StringContent.Name] = {
- 'singular': translate('RemotePlugin', 'Web Interface', 'name singular'),
- 'plural': translate('RemotePlugin', 'Web Interface', 'name plural')
- }
- # Name for MediaDockManager, SettingsManager
- self.text_strings[StringContent.VisibleName] = {
- 'title': translate('RemotePlugin', 'Web Remote', 'container title')
- }
-
- def first_time(self):
- """
- Import web site code if active
- """
- self.application.process_events()
- progress = Progress(self)
- progress.forceShow()
- self.application.process_events()
- time.sleep(1)
- download_and_check(progress)
- self.application.process_events()
- time.sleep(1)
- progress.close()
- self.application.process_events()
- Settings().setValue('remotes/download version', self.version)
-
- def website_version(self):
- """
- Download and save the website version and sha256
- :return: None
- """
- sha256, self.version = download_sha256()
- Registry().set_flag('website_sha256', sha256)
- Registry().set_flag('website_version', self.version)
-
-
-class Progress(QtWidgets.QProgressDialog):
- """
- Local class to handle download display based and supporting httputils:get_web_page
- """
- def __init__(self, parent):
- super(Progress, self).__init__(parent.main_window)
- self.parent = parent
- self.setWindowModality(QtCore.Qt.WindowModal)
- self.setWindowTitle(translate('RemotePlugin', 'Importing Website'))
- self.setLabelText(UiStrings().StartingImport)
- self.setCancelButton(None)
- self.setRange(0, 1)
- self.setMinimumDuration(0)
- self.was_cancelled = False
- self.previous_size = 0
-
- def _download_progress(self, count, block_size):
- """
- Calculate and display the download progress.
- """
- increment = (count * block_size) - self.previous_size
- self._increment_progress_bar(None, increment)
- self.previous_size = count * block_size
-
- def _increment_progress_bar(self, status_text, increment=1):
- """
- Update the wizard progress page.
-
- :param status_text: Current status information to display.
- :param increment: The value to increment the progress bar by.
- """
- if status_text:
- self.setText(status_text)
- if increment > 0:
- self.setValue(self.value() + increment)
- self.parent.application.process_events()
diff --git a/openlp/plugins/songs/endpoint.py b/openlp/plugins/songs/endpoint.py
index 8711fcccd..c8af62764 100644
--- a/openlp/plugins/songs/endpoint.py
+++ b/openlp/plugins/songs/endpoint.py
@@ -62,7 +62,7 @@ def songs_service(request):
:param request: The http request object.
"""
- service(request, 'songs', log)
+ return service(request, 'songs', log)
@api_songs_endpoint.route('songs/search')
@@ -95,6 +95,6 @@ def songs_service_api(request):
:param request: The http request object.
"""
try:
- search(request, 'songs', log)
+ return service(request, 'songs', log)
except NotFound:
return {'results': {'items': []}}
diff --git a/tests/functional/openlp_plugins/remotes/test_deploy.py b/tests/functional/openlp_core_api/test_deploy.py
similarity index 96%
rename from tests/functional/openlp_plugins/remotes/test_deploy.py
rename to tests/functional/openlp_core_api/test_deploy.py
index 1909b94fd..273894b99 100644
--- a/tests/functional/openlp_plugins/remotes/test_deploy.py
+++ b/tests/functional/openlp_core_api/test_deploy.py
@@ -22,14 +22,12 @@
import os
import shutil
-
from tempfile import mkdtemp
from unittest import TestCase
-from openlp.plugins.remotes.deploy import deploy_zipfile
+from openlp.core.api.deploy import deploy_zipfile
-
-TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..', 'resources'))
+TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'resources'))
class TestRemoteDeploy(TestCase):
@@ -54,6 +52,7 @@ class TestRemoteDeploy(TestCase):
Remote Deploy tests - test the dummy zip file is processed correctly
"""
# GIVEN: A new downloaded zip file
+ aa = TEST_PATH
zip_file = os.path.join(TEST_PATH, 'remotes', 'site.zip')
app_root = os.path.join(self.app_root, 'site.zip')
shutil.copyfile(zip_file, app_root)
diff --git a/tests/functional/openlp_core_ui/test_style.py b/tests/functional/openlp_core_ui/test_style.py
new file mode 100644
index 000000000..7435df1c7
--- /dev/null
+++ b/tests/functional/openlp_core_ui/test_style.py
@@ -0,0 +1,111 @@
+# -*- coding: utf-8 -*-
+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
+
+###############################################################################
+# OpenLP - Open Source Lyrics Projection #
+# --------------------------------------------------------------------------- #
+# Copyright (c) 2008-2017 OpenLP Developers #
+# --------------------------------------------------------------------------- #
+# 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 :mod:`~openlp.core.ui.style` module.
+"""
+from unittest.mock import MagicMock, patch
+
+import openlp.core.ui.style
+from openlp.core.ui.style import MEDIA_MANAGER_STYLE, WIN_REPAIR_STYLESHEET, get_application_stylesheet, \
+ get_library_stylesheet
+
+
+@patch('openlp.core.ui.style.HAS_DARK_STYLE', True)
+@patch('openlp.core.ui.style.Settings')
+@patch.object(openlp.core.ui.style, 'qdarkstyle')
+def test_get_application_stylesheet_dark(mocked_qdarkstyle, MockSettings):
+ """Test that the dark stylesheet is returned when available and enabled"""
+ # GIVEN: We're on Windows and no dark style is set
+ mocked_settings = MagicMock()
+ mocked_settings.value.return_value = True
+ MockSettings.return_value = mocked_settings
+ mocked_qdarkstyle.load_stylesheet_pyqt5.return_value = 'dark_style'
+
+ # WHEN: can_show_icon() is called
+ result = get_application_stylesheet()
+
+ # THEN: the result should be false
+ assert result == 'dark_style'
+
+
+@patch('openlp.core.ui.style.HAS_DARK_STYLE', False)
+@patch('openlp.core.ui.style.is_win')
+@patch('openlp.core.ui.style.Settings')
+@patch('openlp.core.ui.style.Registry')
+def test_get_application_stylesheet_not_alternate_rows(MockRegistry, MockSettings, mocked_is_win):
+ """Test that the alternate rows stylesheet is returned when enabled in settings"""
+ # GIVEN: We're on Windows and no dark style is set
+ mocked_is_win.return_value = False
+ MockSettings.return_value.value.return_value = False
+ MockRegistry.return_value.get.return_value.palette.return_value.color.return_value.name.return_value = 'color'
+
+ # WHEN: can_show_icon() is called
+ result = get_application_stylesheet()
+
+ # THEN: the result should be false
+ MockSettings.return_value.value.assert_called_once_with('advanced/alternate rows')
+ assert result == 'QTableWidget, QListWidget, QTreeWidget {alternate-background-color: color;}\n', result
+
+
+@patch('openlp.core.ui.style.HAS_DARK_STYLE', False)
+@patch('openlp.core.ui.style.is_win')
+@patch('openlp.core.ui.style.Settings')
+def test_get_application_stylesheet_win_repair(MockSettings, mocked_is_win):
+ """Test that the Windows repair stylesheet is returned when on Windows"""
+ # GIVEN: We're on Windows and no dark style is set
+ mocked_is_win.return_value = True
+ MockSettings.return_value.value.return_value = True
+
+ # WHEN: can_show_icon() is called
+ result = get_application_stylesheet()
+
+ # THEN: the result should be false
+ MockSettings.return_value.value.assert_called_once_with('advanced/alternate rows')
+ assert result == WIN_REPAIR_STYLESHEET
+
+
+@patch('openlp.core.ui.style.HAS_DARK_STYLE', False)
+@patch('openlp.core.ui.style.Settings')
+def test_get_library_stylesheet_no_dark_style(MockSettings):
+ """Test that the media manager stylesheet is returned when there's no dark theme available"""
+ # GIVEN: No dark style
+ MockSettings.return_value.value.return_value = False
+
+ # WHEN: get_library_stylesheet() is called
+ result = get_library_stylesheet()
+
+ # THEN: The correct stylesheet should be returned
+ assert result == MEDIA_MANAGER_STYLE
+
+
+@patch('openlp.core.ui.style.HAS_DARK_STYLE', True)
+@patch('openlp.core.ui.style.Settings')
+def test_get_library_stylesheet_dark_style(MockSettings):
+ """Test that no stylesheet is returned when the dark theme is enabled"""
+ # GIVEN: No dark style
+ MockSettings.return_value.value.return_value = True
+
+ # WHEN: get_library_stylesheet() is called
+ result = get_library_stylesheet()
+
+ # THEN: The correct stylesheet should be returned
+ assert result == ''
diff --git a/tests/functional/openlp_plugins/remotes/__init__.py b/tests/functional/openlp_plugins/remotes/__init__.py
deleted file mode 100644
index ea62548f4..000000000
--- a/tests/functional/openlp_plugins/remotes/__init__.py
+++ /dev/null
@@ -1,21 +0,0 @@
-# -*- coding: utf-8 -*-
-# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
-
-###############################################################################
-# OpenLP - Open Source Lyrics Projection #
-# --------------------------------------------------------------------------- #
-# Copyright (c) 2008-2017 OpenLP Developers #
-# --------------------------------------------------------------------------- #
-# 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 #
-###############################################################################
diff --git a/tests/interfaces/openlp_core_lib/test_pluginmanager.py b/tests/interfaces/openlp_core_lib/test_pluginmanager.py
index 2e9e8342f..3dbae0e03 100644
--- a/tests/interfaces/openlp_core_lib/test_pluginmanager.py
+++ b/tests/interfaces/openlp_core_lib/test_pluginmanager.py
@@ -94,4 +94,3 @@ class TestPluginManager(TestCase, TestMixin):
self.assertIn('custom', plugin_names, 'There should be a "custom" plugin')
self.assertIn('songusage', plugin_names, 'There should be a "songusage" plugin')
self.assertIn('alerts', plugin_names, 'There should be a "alerts" plugin')
- self.assertIn('remotes', plugin_names, 'There should be a "remotes" plugin')