This commit is contained in:
Philip Ridout 2017-09-30 22:47:50 +01:00
commit d8a32d3962
22 changed files with 420 additions and 345 deletions

View File

@ -46,34 +46,14 @@ from openlp.core.ui.exceptionform import ExceptionForm
from openlp.core.ui.firsttimeform import FirstTimeForm from openlp.core.ui.firsttimeform import FirstTimeForm
from openlp.core.ui.firsttimelanguageform import FirstTimeLanguageForm from openlp.core.ui.firsttimelanguageform import FirstTimeLanguageForm
from openlp.core.ui.mainwindow import MainWindow from openlp.core.ui.mainwindow import MainWindow
from openlp.core.ui.style import get_application_stylesheet
__all__ = ['OpenLP', 'main'] __all__ = ['OpenLP', 'main']
log = logging.getLogger() 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): class OpenLP(OpenLPMixin, QtWidgets.QApplication):
""" """
@ -118,14 +98,7 @@ class OpenLP(OpenLPMixin, QtWidgets.QApplication):
QtCore.QCoreApplication.exit() QtCore.QCoreApplication.exit()
sys.exit() sys.exit()
# Correct stylesheet bugs # Correct stylesheet bugs
application_stylesheet = '' application_stylesheet = get_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
if application_stylesheet: if application_stylesheet:
self.setStyleSheet(application_stylesheet) self.setStyleSheet(application_stylesheet)
can_show_splash = Settings().value('core/show splash') can_show_splash = Settings().value('core/show splash')

View File

@ -63,7 +63,7 @@ def download_and_check(callback=None):
sha256, version = download_sha256() sha256, version = download_sha256()
file_size = get_url_file_size('https://get.openlp.org/webclient/site.zip') file_size = get_url_file_size('https://get.openlp.org/webclient/site.zip')
callback.setRange(0, file_size) 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', AppLocation.get_section_data_path('remotes') / 'site.zip',
sha256=sha256): sha256=sha256):
deploy_zipfile(str(AppLocation.get_section_data_path('remotes')), 'site.zip') deploy_zipfile(str(AppLocation.get_section_data_path('remotes')), 'site.zip')

View File

@ -21,18 +21,13 @@
############################################################################### ###############################################################################
import logging import logging
import os
from openlp.core.api.http.endpoint import Endpoint from openlp.core.api.http.endpoint import Endpoint
from openlp.core.api.endpoint.core import TRANSLATED_STRINGS 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__) 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}') @remote_endpoint.route('{view}')

View File

@ -23,7 +23,7 @@ import logging
import json import json
from openlp.core.api.http.endpoint import Endpoint 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 from openlp.core.common import Registry

View File

@ -26,17 +26,24 @@ with OpenLP. It uses JSON to communicate with the remotes.
""" """
import logging import logging
import time
from PyQt5 import QtCore from PyQt5 import QtCore, QtWidgets
from waitress import serve from waitress import serve
from openlp.core.api.http import register_endpoint from openlp.core.api.http import register_endpoint
from openlp.core.api.http import application 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.poll import Poller
from openlp.core.api.endpoint.controller import controller_endpoint, api_controller_endpoint 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.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.service import service_endpoint, api_service_endpoint
from openlp.core.api.endpoint.remote import remote_endpoint
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -59,6 +66,7 @@ class HttpWorker(QtCore.QObject):
""" """
address = Settings().value('api/ip address') address = Settings().value('api/ip address')
port = Settings().value('api/port') port = Settings().value('api/port')
Registry().execute('get_website_version')
serve(application, host=address, port=port) serve(application, host=address, port=port)
def stop(self): def stop(self):
@ -79,11 +87,15 @@ class HttpServer(RegistryMixin, RegistryProperties, OpenLPMixin):
self.worker.moveToThread(self.thread) self.worker.moveToThread(self.thread)
self.thread.started.connect(self.worker.run) self.thread.started.connect(self.worker.run)
self.thread.start() 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): def bootstrap_post_set_up(self):
""" """
Register the poll return service and start the servers. Register the poll return service and start the servers.
""" """
self.initialise()
self.poller = Poller() self.poller = Poller()
Registry().register('poller', self.poller) Registry().register('poller', self.poller)
application.initialise() application.initialise()
@ -95,3 +107,79 @@ class HttpServer(RegistryMixin, RegistryProperties, OpenLPMixin):
register_endpoint(main_endpoint) register_endpoint(main_endpoint)
register_endpoint(service_endpoint) register_endpoint(service_endpoint)
register_endpoint(api_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()

View File

@ -222,6 +222,8 @@ class ApiTab(SettingsTab):
self.remote_url.setText('<a href="{url}">{url}</a>'.format(url=http_url)) self.remote_url.setText('<a href="{url}">{url}</a>'.format(url=http_url))
http_url_temp = http_url + 'stage' http_url_temp = http_url + 'stage'
self.stage_url.setText('<a href="{url}">{url}</a>'.format(url=http_url_temp)) self.stage_url.setText('<a href="{url}">{url}</a>'.format(url=http_url_temp))
http_url_temp = http_url + 'chords'
self.chords_url.setText('<a href="{url}">{url}</a>'.format(url=http_url_temp))
http_url_temp = http_url + 'main' http_url_temp = http_url + 'main'
self.live_url.setText('<a href="{url}">{url}</a>'.format(url=http_url_temp)) self.live_url.setText('<a href="{url}">{url}</a>'.format(url=http_url_temp))

View File

@ -136,6 +136,7 @@ class Settings(QtCore.QSettings):
'advanced/single click service preview': False, 'advanced/single click service preview': False,
'advanced/x11 bypass wm': X11_BYPASS_DEFAULT, 'advanced/x11 bypass wm': X11_BYPASS_DEFAULT,
'advanced/search as type': True, 'advanced/search as type': True,
'advanced/use_dark_style': False,
'api/twelve hour': True, 'api/twelve hour': True,
'api/port': 4316, 'api/port': 4316,
'api/websocket port': 4317, 'api/websocket port': 4317,
@ -177,6 +178,7 @@ class Settings(QtCore.QSettings):
'images/background color': '#000000', 'images/background color': '#000000',
'media/players': 'system,webkit', 'media/players': 'system,webkit',
'media/override player': QtCore.Qt.Unchecked, 'media/override player': QtCore.Qt.Unchecked,
'remotes/download version': '0.0',
'players/background color': '#000000', 'players/background color': '#000000',
'servicemanager/last directory': None, 'servicemanager/last directory': None,
'servicemanager/last file': None, 'servicemanager/last file': None,

View File

@ -32,6 +32,7 @@ from openlp.core.common.languagemanager import format_time
from openlp.core.common.path import path_to_str from openlp.core.common.path import path_to_str
from openlp.core.lib import SettingsTab, build_icon from openlp.core.lib import SettingsTab, build_icon
from openlp.core.ui.lib import PathEdit, PathType from openlp.core.ui.lib import PathEdit, PathType
from openlp.core.ui.style import HAS_DARK_STYLE
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -109,8 +110,80 @@ class AdvancedTab(SettingsTab):
self.enable_auto_close_check_box.setObjectName('enable_auto_close_check_box') self.enable_auto_close_check_box.setObjectName('enable_auto_close_check_box')
self.ui_layout.addRow(self.enable_auto_close_check_box) self.ui_layout.addRow(self.enable_auto_close_check_box)
self.left_layout.addWidget(self.ui_group_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 # 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_group_box.setObjectName('service_name_group_box')
self.service_name_layout = QtWidgets.QFormLayout(self.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) 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 = QtWidgets.QLabel(self.service_name_group_box)
self.service_name_example.setObjectName('service_name_example') self.service_name_example.setObjectName('service_name_example')
self.service_name_layout.addRow(self.service_name_example_label, self.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) self.right_layout.addWidget(self.service_name_group_box)
# Data Directory # After the last item on each side, add some spacing
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.left_layout.addStretch() 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() self.right_layout.addStretch()
# Set up all the connections and things
self.should_update_service_name_example = False self.should_update_service_name_example = False
self.service_name_check_box.toggled.connect(self.service_name_check_box_toggled) 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) 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')) 'Auto-scroll the next slide to bottom'))
self.enable_auto_close_check_box.setText(translate('OpenLP.AdvancedTab', self.enable_auto_close_check_box.setText(translate('OpenLP.AdvancedTab',
'Enable application exit confirmation')) '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_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_check_box.setText(translate('OpenLP.AdvancedTab', 'Enable default service name'))
self.service_name_time_label.setText(translate('OpenLP.AdvancedTab', 'Date and Time:')) 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(): if self.autoscroll_map[i] == autoscroll_value and i < self.autoscroll_combo_box.count():
self.autoscroll_combo_box.setCurrentIndex(i) self.autoscroll_combo_box.setCurrentIndex(i)
self.enable_auto_close_check_box.setChecked(settings.value('enable exit confirmation')) 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.hide_mouse_check_box.setChecked(settings.value('hide mouse'))
self.service_name_day.setCurrentIndex(settings.value('default service day')) self.service_name_day.setCurrentIndex(settings.value('default service day'))
self.service_name_time.setTime(QtCore.QTime(settings.value('default service hour'), 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('config_screen_changed')
self.settings_form.register_post_process('slidecontroller_update_slide_limits') self.settings_form.register_post_process('slidecontroller_update_slide_limits')
settings.setValue('search as type', self.is_search_as_you_type_enabled) 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() settings.endGroup()
def on_search_as_type_check_box_changed(self, check_state): def on_search_as_type_check_box_changed(self, check_state):

View File

@ -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.dockwidget import OpenLPDockWidget
from openlp.core.ui.lib.filedialog import FileDialog from openlp.core.ui.lib.filedialog import FileDialog
from openlp.core.ui.lib.mediadockmanager import MediaDockManager 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 from openlp.core.version import get_version
log = logging.getLogger(__name__) 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): class Ui_MainWindow(object):
""" """
@ -155,7 +136,7 @@ class Ui_MainWindow(object):
# Create the MediaManager # Create the MediaManager
self.media_manager_dock = OpenLPDockWidget(main_window, 'media_manager_dock', self.media_manager_dock = OpenLPDockWidget(main_window, 'media_manager_dock',
':/system/system_mediamanager.png') ':/system/system_mediamanager.png')
self.media_manager_dock.setStyleSheet(MEDIA_MANAGER_STYLE) self.media_manager_dock.setStyleSheet(get_library_stylesheet())
# Create the media toolbox # Create the media toolbox
self.media_tool_box = QtWidgets.QToolBox(self.media_manager_dock) self.media_tool_box = QtWidgets.QToolBox(self.media_manager_dock)
self.media_tool_box.setObjectName('media_tool_box') self.media_tool_box.setObjectName('media_tool_box')

109
openlp/core/ui/style.py Normal file
View File

@ -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 ''

View File

@ -62,7 +62,7 @@ def bibles_service(request):
:param request: The http request object. :param request: The http request object.
""" """
service(request, 'bibles', log) return service(request, 'bibles', log)
@api_bibles_endpoint.route('bibles/search') @api_bibles_endpoint.route('bibles/search')
@ -95,6 +95,6 @@ def bibles_service_api(request):
:param request: The http request object. :param request: The http request object.
""" """
try: try:
search(request, 'bibles', log) return search(request, 'bibles', log)
except NotFound: except NotFound:
return {'results': {'items': []}} return {'results': {'items': []}}

View File

@ -62,7 +62,7 @@ def custom_service(request):
:param request: The http request object. :param request: The http request object.
""" """
service(request, 'custom', log) return service(request, 'custom', log)
@api_custom_endpoint.route('custom/search') @api_custom_endpoint.route('custom/search')
@ -95,6 +95,6 @@ def custom_service_api(request):
:param request: The http request object. :param request: The http request object.
""" """
try: try:
search(request, 'custom', log) return search(request, 'custom', log)
except NotFound: except NotFound:
return {'results': {'items': []}} return {'results': {'items': []}}

View File

@ -75,7 +75,7 @@ def images_service(request):
:param request: The http request object. :param request: The http request object.
""" """
service(request, 'images', log) return service(request, 'images', log)
@api_images_endpoint.route('images/search') @api_images_endpoint.route('images/search')
@ -108,6 +108,6 @@ def images_service_api(request):
:param request: The http request object. :param request: The http request object.
""" """
try: try:
search(request, 'images', log) return search(request, 'images', log)
except NotFound: except NotFound:
return {'results': {'items': []}} return {'results': {'items': []}}

View File

@ -62,7 +62,7 @@ def media_service(request):
:param request: The http request object. :param request: The http request object.
""" """
service(request, 'media', log) return service(request, 'media', log)
@api_media_endpoint.route('media/search') @api_media_endpoint.route('media/search')
@ -95,6 +95,6 @@ def media_service_api(request):
:param request: The http request object. :param request: The http request object.
""" """
try: try:
search(request, 'media', log) return search(request, 'media', log)
except NotFound: except NotFound:
return {'results': {'items': []}} return {'results': {'items': []}}

View File

@ -76,7 +76,7 @@ def presentations_service(request):
:param request: The http request object. :param request: The http request object.
""" """
service(request, 'presentations', log) return service(request, 'presentations', log)
@api_presentations_endpoint.route('presentations/search') @api_presentations_endpoint.route('presentations/search')
@ -109,6 +109,6 @@ def presentations_service_api(request):
:param request: The http request object. :param request: The http request object.
""" """
try: try:
search(request, 'presentations', log) return search(request, 'presentations', log)
except NotFound: except NotFound:
return {'results': {'items': []}} return {'results': {'items': []}}

View File

@ -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 #
###############################################################################

View File

@ -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',
'<strong>Web Interface</strong>'
'<br />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()

View File

@ -62,7 +62,7 @@ def songs_service(request):
:param request: The http request object. :param request: The http request object.
""" """
service(request, 'songs', log) return service(request, 'songs', log)
@api_songs_endpoint.route('songs/search') @api_songs_endpoint.route('songs/search')
@ -95,6 +95,6 @@ def songs_service_api(request):
:param request: The http request object. :param request: The http request object.
""" """
try: try:
search(request, 'songs', log) return service(request, 'songs', log)
except NotFound: except NotFound:
return {'results': {'items': []}} return {'results': {'items': []}}

View File

@ -22,14 +22,12 @@
import os import os
import shutil import shutil
from tempfile import mkdtemp from tempfile import mkdtemp
from unittest import TestCase 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): class TestRemoteDeploy(TestCase):
@ -54,6 +52,7 @@ class TestRemoteDeploy(TestCase):
Remote Deploy tests - test the dummy zip file is processed correctly Remote Deploy tests - test the dummy zip file is processed correctly
""" """
# GIVEN: A new downloaded zip file # GIVEN: A new downloaded zip file
aa = TEST_PATH
zip_file = os.path.join(TEST_PATH, 'remotes', 'site.zip') zip_file = os.path.join(TEST_PATH, 'remotes', 'site.zip')
app_root = os.path.join(self.app_root, 'site.zip') app_root = os.path.join(self.app_root, 'site.zip')
shutil.copyfile(zip_file, app_root) shutil.copyfile(zip_file, app_root)

View File

@ -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 == ''

View File

@ -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 #
###############################################################################

View File

@ -94,4 +94,3 @@ class TestPluginManager(TestCase, TestMixin):
self.assertIn('custom', plugin_names, 'There should be a "custom" plugin') self.assertIn('custom', plugin_names, 'There should be a "custom" plugin')
self.assertIn('songusage', plugin_names, 'There should be a "songusage" 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('alerts', plugin_names, 'There should be a "alerts" plugin')
self.assertIn('remotes', plugin_names, 'There should be a "remotes" plugin')