mirror of https://gitlab.com/openlp/openlp.git
merge
This commit is contained in:
commit
0574505ca3
|
@ -11,6 +11,7 @@ indent_style = space
|
|||
|
||||
[*.py]
|
||||
indent_size = 4
|
||||
continuation_indent_size = 8
|
||||
max_line_length = 120
|
||||
|
||||
[*.{html,js,json,yaml,yml}]
|
||||
|
|
|
@ -60,7 +60,7 @@ after_test:
|
|||
$ErrorActionPreference = "Continue"
|
||||
# This is where we create a package using PyInstaller
|
||||
# Install PyInstaller
|
||||
python -m pip install --no-warn-script-location pyinstaller
|
||||
python -m pip install --no-warn-script-location pyinstaller==4.9
|
||||
# Some windows only stuff...
|
||||
If ($isWindows) {
|
||||
# Disabled portable installers - can't figure out how to make them silent
|
||||
|
|
|
@ -78,9 +78,6 @@ class HttpServer(RegistryBase, RegistryProperties, LogMixin):
|
|||
"""
|
||||
super(HttpServer, self).__init__(parent)
|
||||
Registry().register('authentication_token', token_hex())
|
||||
if not Registry().get_flag('no_web_server'):
|
||||
worker = HttpWorker()
|
||||
run_thread(worker, 'http_server')
|
||||
|
||||
def bootstrap_post_set_up(self):
|
||||
"""
|
||||
|
@ -89,3 +86,6 @@ class HttpServer(RegistryBase, RegistryProperties, LogMixin):
|
|||
create_paths(AppLocation.get_section_data_path('remotes'))
|
||||
self.poller = Poller()
|
||||
Registry().register('poller', self.poller)
|
||||
if not Registry().get_flag('no_web_server'):
|
||||
worker = HttpWorker()
|
||||
run_thread(worker, 'http_server')
|
||||
|
|
|
@ -40,6 +40,7 @@ def controller_live_items():
|
|||
if current_item:
|
||||
live_item = current_item.to_dict()
|
||||
live_item['slides'][live_controller.selected_row]['selected'] = True
|
||||
live_item['id'] = str(current_item.unique_identifier)
|
||||
return jsonify(live_item)
|
||||
|
||||
|
||||
|
@ -51,6 +52,7 @@ def controller_live_item():
|
|||
live_item = {}
|
||||
if current_item:
|
||||
live_item = current_item.to_dict(True, live_controller.selected_row)
|
||||
live_item['id'] = str(current_item.unique_identifier)
|
||||
return jsonify(live_item)
|
||||
|
||||
|
||||
|
|
|
@ -21,11 +21,13 @@
|
|||
##########################################################################
|
||||
import logging
|
||||
|
||||
import re
|
||||
from flask import abort, request, Blueprint, jsonify
|
||||
|
||||
from openlp.core.api.lib import login_required
|
||||
from openlp.core.lib.plugin import PluginStatus
|
||||
from openlp.core.common.registry import Registry
|
||||
from openlp.plugins.songs.lib import transpose_lyrics
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
@ -134,3 +136,45 @@ def set_search_option(plugin):
|
|||
else:
|
||||
log.error('Invalid option or value')
|
||||
return '', 400
|
||||
|
||||
|
||||
@plugins.route('/songs/transpose-live-item/<transpose_value>', methods=['GET'])
|
||||
@login_required
|
||||
def transpose(transpose_value):
|
||||
log.debug('songs/transpose-live-item called')
|
||||
if transpose_value:
|
||||
try:
|
||||
transpose_value = int(transpose_value)
|
||||
except ValueError:
|
||||
abort(400)
|
||||
# Get lyrics from the live serviceitem in the live-controller and transpose it
|
||||
live_controller = Registry().get('live_controller')
|
||||
current_item = live_controller.service_item
|
||||
# make sure an service item is currently displayed and that it is a song
|
||||
if not current_item or current_item.name != 'songs':
|
||||
abort(400)
|
||||
previous_pages = {}
|
||||
chord_song_text = ''
|
||||
# re-create the song lyrics with OpenLP verse-tags to be able to transpose in one go so any keys are used
|
||||
for raw_slide in current_item.slides:
|
||||
verse_tag = raw_slide['verse']
|
||||
if verse_tag in previous_pages and previous_pages[verse_tag][0] == raw_slide:
|
||||
pages = previous_pages[verse_tag][1]
|
||||
else:
|
||||
pages = current_item.renderer.format_slide(raw_slide['text'], current_item)
|
||||
previous_pages[verse_tag] = (raw_slide, pages)
|
||||
for page in pages:
|
||||
chord_song_text += '---[Verse:{verse_tag}]---\n'.format(verse_tag=verse_tag)
|
||||
chord_song_text += page
|
||||
chord_song_text += '\n'
|
||||
# transpose
|
||||
transposed_lyrics = transpose_lyrics(chord_song_text, transpose_value)
|
||||
# re-split into verses
|
||||
chord_slides = []
|
||||
verse_list = re.split(r'---\[Verse:(.+?)\]---', transposed_lyrics)
|
||||
# remove first blank entry
|
||||
verse_list = verse_list[1:]
|
||||
for i in range(0, len(verse_list), 2):
|
||||
chord_slides.append({'chords': verse_list[i + 1].strip(), 'verse': verse_list[i]})
|
||||
return jsonify(chord_slides), 200
|
||||
abort(400)
|
||||
|
|
|
@ -33,7 +33,7 @@ import time
|
|||
from websockets import serve
|
||||
|
||||
from openlp.core.common.mixins import LogMixin, RegistryProperties
|
||||
from openlp.core.common.registry import Registry
|
||||
from openlp.core.common.registry import Registry, RegistryBase
|
||||
from openlp.core.threading import ThreadWorker, run_thread
|
||||
from openlp.core.api.websocketspoll import WebSocketPoller
|
||||
|
||||
|
@ -165,7 +165,7 @@ class WebSocketWorker(ThreadWorker, RegistryProperties, LogMixin):
|
|||
self.event_loop.call_soon_threadsafe(queue.put_nowait, state)
|
||||
|
||||
|
||||
class WebSocketServer(RegistryProperties, QtCore.QObject, LogMixin):
|
||||
class WebSocketServer(RegistryBase, RegistryProperties, QtCore.QObject, LogMixin):
|
||||
"""
|
||||
Wrapper round a server instance
|
||||
"""
|
||||
|
@ -176,6 +176,9 @@ class WebSocketServer(RegistryProperties, QtCore.QObject, LogMixin):
|
|||
super(WebSocketServer, self).__init__()
|
||||
self.worker = None
|
||||
|
||||
def bootstrap_post_set_up(self):
|
||||
self.start()
|
||||
|
||||
def start(self):
|
||||
"""
|
||||
Starts the WebSockets server
|
||||
|
|
|
@ -365,6 +365,7 @@ class Settings(QtCore.QSettings):
|
|||
'themes/theme level': ThemeLevel.Global,
|
||||
'themes/item transitions': False,
|
||||
'themes/hot reload': False,
|
||||
'user interface/is preset layout': False,
|
||||
'user interface/live panel': True,
|
||||
'user interface/live splitter geometry': QtCore.QByteArray(),
|
||||
'user interface/lock panel': True,
|
||||
|
@ -374,8 +375,11 @@ class Settings(QtCore.QSettings):
|
|||
'user interface/main window state': QtCore.QByteArray(),
|
||||
'user interface/preview panel': True,
|
||||
'user interface/preview splitter geometry': QtCore.QByteArray(),
|
||||
'user interface/is preset layout': False,
|
||||
'user interface/theme manager view mode': 1,
|
||||
'user interface/show library': True,
|
||||
'user interface/show projectors': True,
|
||||
'user interface/show service': True,
|
||||
'user interface/show themes': True,
|
||||
'projector/show after wizard': False,
|
||||
'projector/db type': 'sqlite',
|
||||
'projector/db username': '',
|
||||
|
|
|
@ -815,7 +815,8 @@ class ThemePreviewRenderer(DisplayWindow, LogMixin):
|
|||
check_string = separator.join(html_list[index + 1:]).strip()
|
||||
if self._text_fits_on_slide(html_tags + check_string):
|
||||
previous_html = html_tags + check_string + line_end
|
||||
previous_raw = raw_tags + check_string + line_end
|
||||
check_string_raw = separator.join(raw_list[index + 1:]).strip()
|
||||
previous_raw = raw_tags + check_string_raw + line_end
|
||||
break
|
||||
else:
|
||||
# The remaining elements do not fit, thus reset the indexes, create a new list and continue.
|
||||
|
|
|
@ -32,7 +32,6 @@ from openlp.core.common import Singleton
|
|||
from openlp.core.common.i18n import translate
|
||||
from openlp.core.common.registry import Registry
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
@ -40,6 +39,7 @@ class Screen(object):
|
|||
"""
|
||||
A Python representation of a screen
|
||||
"""
|
||||
|
||||
def __init__(self, number=None, geometry=None, custom_geometry=None, is_primary=False, is_display=False):
|
||||
"""
|
||||
Set up the screen object
|
||||
|
@ -241,6 +241,10 @@ class ScreenList(metaclass=Singleton):
|
|||
If more than 1 screen, set first non-primary screen to display, otherwise just set the available screen as
|
||||
display.
|
||||
"""
|
||||
# Reset first, so we end up with just one display at most
|
||||
for screen in self:
|
||||
screen.is_display = False
|
||||
|
||||
if len(self) > 1:
|
||||
for screen in self:
|
||||
if not screen.is_primary:
|
||||
|
@ -349,6 +353,7 @@ class ScreenList(metaclass=Singleton):
|
|||
"""
|
||||
Update the list of screens
|
||||
"""
|
||||
|
||||
def _screen_compare(this, other):
|
||||
"""
|
||||
Compare screens. Can't use a key here because of the nested property and method to be called
|
||||
|
@ -364,6 +369,7 @@ class ScreenList(metaclass=Singleton):
|
|||
return 1
|
||||
else:
|
||||
return 0
|
||||
|
||||
self.screens = []
|
||||
os_screens = self.application.screens()
|
||||
os_screens.sort(key=cmp_to_key(_screen_compare))
|
||||
|
@ -379,8 +385,16 @@ class ScreenList(metaclass=Singleton):
|
|||
:param changed_screen: The screen which has been plugged.
|
||||
"""
|
||||
number = len(self.screens)
|
||||
|
||||
# Ensure we have only one primary screen in the list.
|
||||
is_primary = self.application.primaryScreen() == changed_screen
|
||||
if is_primary:
|
||||
for screen in self.screens:
|
||||
screen.is_primary = False
|
||||
|
||||
self.screens.append(Screen(number, changed_screen.geometry(),
|
||||
is_primary=self.application.primaryScreen() == changed_screen))
|
||||
self.find_new_display_screen()
|
||||
changed_screen.geometryChanged.connect(self.screens[-1].on_geometry_changed)
|
||||
Registry().execute('config_screen_changed')
|
||||
|
||||
|
@ -411,4 +425,6 @@ class ScreenList(metaclass=Singleton):
|
|||
"""
|
||||
for screen in self.screens:
|
||||
screen.is_primary = self.application.primaryScreen().geometry() == screen.geometry
|
||||
self.find_new_display_screen()
|
||||
|
||||
Registry().execute('config_screen_changed')
|
||||
|
|
|
@ -77,6 +77,7 @@ class WebEngineView(QtWebEngineWidgets.QWebEngineView):
|
|||
self.page().settings().setAttribute(QtWebEngineWidgets.QWebEngineSettings.LocalStorageEnabled, True)
|
||||
self.page().settings().setAttribute(QtWebEngineWidgets.QWebEngineSettings.LocalContentCanAccessFileUrls, True)
|
||||
self.page().settings().setAttribute(QtWebEngineWidgets.QWebEngineSettings.LocalContentCanAccessRemoteUrls, True)
|
||||
self.setContextMenuPolicy(QtCore.Qt.PreventContextMenu)
|
||||
|
||||
def eventFilter(self, obj, ev):
|
||||
"""
|
||||
|
|
|
@ -224,7 +224,7 @@ class ServiceItem(RegistryProperties):
|
|||
rendered_slide = {
|
||||
'title': raw_slide['title'],
|
||||
'text': render_tags(page),
|
||||
'chords': render_tags(page, can_render_chords=True),
|
||||
'chords': page,
|
||||
'verse': index,
|
||||
'footer': self.footer_html
|
||||
}
|
||||
|
@ -379,7 +379,7 @@ class ServiceItem(RegistryProperties):
|
|||
'start_time': self.start_time,
|
||||
'end_time': self.end_time,
|
||||
'media_length': self.media_length,
|
||||
'background_audio': self.background_audio,
|
||||
'background_audio': [],
|
||||
'theme_overwritten': self.theme_overwritten,
|
||||
'will_auto_start': self.will_auto_start,
|
||||
'processor': self.processor,
|
||||
|
@ -387,6 +387,14 @@ class ServiceItem(RegistryProperties):
|
|||
'sha256_file_hash': self.sha256_file_hash,
|
||||
'stored_filename': stored_filename
|
||||
}
|
||||
for file_path, file_hash in self.background_audio:
|
||||
if lite_save:
|
||||
path_str = str(file_path)
|
||||
else:
|
||||
# a side-effect of this is that if the song is imported from the service created from this, the
|
||||
# background audio filename shown in the ui will be the file_hash + the suffix.
|
||||
path_str = '{hash}{suffix}'.format(hash=file_hash, suffix=file_path.suffix)
|
||||
service_header['background_audio'].append(path_str)
|
||||
service_data = []
|
||||
if self.service_item_type == ServiceItemType.Text:
|
||||
for slide in self.slides:
|
||||
|
@ -481,7 +489,9 @@ class ServiceItem(RegistryProperties):
|
|||
self.stored_filename = header.get('stored_filename', None)
|
||||
if 'background_audio' in header and State().check_preconditions('media'):
|
||||
self.background_audio = []
|
||||
for file_path in header['background_audio']:
|
||||
for file_str in header['background_audio']:
|
||||
file_path = Path(file_str)
|
||||
file_hash = None
|
||||
# In OpenLP 3.0 we switched to storing Path objects in JSON files
|
||||
if version >= 3:
|
||||
if path:
|
||||
|
@ -490,7 +500,9 @@ class ServiceItem(RegistryProperties):
|
|||
# Handle service files prior to OpenLP 3.0
|
||||
# Windows can handle both forward and backward slashes, so we use ntpath to get the basename
|
||||
file_path = path / ntpath.basename(file_path)
|
||||
self.background_audio.append(file_path)
|
||||
if not file_hash:
|
||||
file_hash = sha256_file_hash(file_path)
|
||||
self.background_audio.append((file_path, file_hash))
|
||||
self.theme_overwritten = header.get('theme_overwritten', False)
|
||||
if self.service_item_type == ServiceItemType.Text:
|
||||
for slide in service_item['serviceitem']['data']:
|
||||
|
@ -887,7 +899,7 @@ class ServiceItem(RegistryProperties):
|
|||
'data': self.data_string or {},
|
||||
'fromPlugin': self.from_plugin,
|
||||
'capabilities': self.capabilities,
|
||||
'backgroundAudio': [str(file_path) for file_path in self.background_audio],
|
||||
'backgroundAudio': self.background_audio,
|
||||
'isThemeOverwritten': self.theme_overwritten,
|
||||
'slides': []
|
||||
}
|
||||
|
|
|
@ -277,7 +277,7 @@ class Ui_MainWindow(object):
|
|||
self.settings_configure_item = create_action(main_window, 'settingsConfigureItem',
|
||||
icon=UiIcons().settings, can_shortcuts=True,
|
||||
category=UiStrings().Settings)
|
||||
# Give QT Extra Hint that this is the Preferences Menu Item
|
||||
# Give Qt Extra Hint that this is the Preferences Menu Item
|
||||
self.settings_configure_item.setMenuRole(QtWidgets.QAction.PreferencesRole)
|
||||
self.settings_import_item = create_action(main_window, 'settingsImportItem',
|
||||
category=UiStrings().Import, can_shortcuts=True)
|
||||
|
@ -287,7 +287,7 @@ class Ui_MainWindow(object):
|
|||
self.about_item = create_action(main_window, 'aboutItem', icon=UiIcons().info,
|
||||
can_shortcuts=True, category=UiStrings().Help,
|
||||
triggers=self.on_about_item_clicked)
|
||||
# Give QT Extra Hint that this is an About Menu Item
|
||||
# Give Qt Extra Hint that this is an About Menu Item
|
||||
self.about_item.setMenuRole(QtWidgets.QAction.AboutRole)
|
||||
if is_win():
|
||||
self.local_help_file = AppLocation.get_directory(AppLocation.AppDir) / 'OpenLP.chm'
|
||||
|
@ -480,9 +480,6 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, LogMixin, RegistryPropert
|
|||
self.copy_data = False
|
||||
self.settings.set_up_default_values()
|
||||
self.about_form = AboutForm(self)
|
||||
self.ws_server = WebSocketServer()
|
||||
self.ws_server.start()
|
||||
self.http_server = HttpServer(self)
|
||||
SettingsForm(self)
|
||||
self.formatting_tag_form = FormattingTagForm(self)
|
||||
self.shortcut_form = ShortcutListForm(self)
|
||||
|
@ -495,10 +492,10 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, LogMixin, RegistryPropert
|
|||
self.update_recent_files_menu()
|
||||
self.plugin_form = PluginForm(self)
|
||||
# Set up signals and slots
|
||||
self.media_manager_dock.visibilityChanged.connect(self.view_media_manager_item.setChecked)
|
||||
self.service_manager_dock.visibilityChanged.connect(self.view_service_manager_item.setChecked)
|
||||
self.theme_manager_dock.visibilityChanged.connect(self.view_theme_manager_item.setChecked)
|
||||
self.projector_manager_dock.visibilityChanged.connect(self.view_projector_manager_item.setChecked)
|
||||
self.media_manager_dock.visibilityChanged.connect(self.toggle_media_manager)
|
||||
self.service_manager_dock.visibilityChanged.connect(self.toggle_service_manager)
|
||||
self.theme_manager_dock.visibilityChanged.connect(self.toggle_theme_manager)
|
||||
self.projector_manager_dock.visibilityChanged.connect(self.toggle_projector_manager)
|
||||
self.import_theme_item.triggered.connect(self.theme_manager_contents.on_import_theme)
|
||||
self.export_theme_item.triggered.connect(self.theme_manager_contents.on_export_theme)
|
||||
self.web_site_item.triggered.connect(self.on_help_web_site_clicked)
|
||||
|
@ -526,6 +523,9 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, LogMixin, RegistryPropert
|
|||
Registry().register_function('bootstrap_post_set_up', self.bootstrap_post_set_up)
|
||||
# Reset the cursor
|
||||
self.application.set_normal_cursor()
|
||||
# Starting up web services
|
||||
self.http_server = HttpServer(self)
|
||||
self.ws_server = WebSocketServer()
|
||||
|
||||
def _wait_for_threads(self):
|
||||
"""
|
||||
|
@ -661,10 +661,14 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, LogMixin, RegistryPropert
|
|||
self.set_view_mode(False, True, False, False, True, True)
|
||||
self.mode_live_item.setChecked(True)
|
||||
else:
|
||||
self.set_view_mode(True, True, True,
|
||||
self.settings.value('user interface/preview panel'),
|
||||
self.settings.value('user interface/live panel'),
|
||||
True)
|
||||
self.set_view_mode(
|
||||
self.settings.value('user interface/show library'),
|
||||
self.settings.value('user interface/show service'),
|
||||
self.settings.value('user interface/show themes'),
|
||||
self.settings.value('user interface/preview panel'),
|
||||
self.settings.value('user interface/live panel'),
|
||||
self.settings.value('user interface/show projectors')
|
||||
)
|
||||
|
||||
def first_time(self):
|
||||
"""
|
||||
|
@ -1154,15 +1158,21 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, LogMixin, RegistryPropert
|
|||
"""
|
||||
Toggle the visibility of the media manager
|
||||
"""
|
||||
self.media_manager_dock.setVisible(not self.media_manager_dock.isVisible())
|
||||
if self.sender() is self.view_media_manager_item:
|
||||
self.media_manager_dock.setVisible(not self.media_manager_dock.isVisible())
|
||||
self.view_media_manager_item.setChecked(self.media_manager_dock.isVisible())
|
||||
self.settings.setValue('user interface/is preset layout', False)
|
||||
self.settings.setValue('user interface/show library', self.media_manager_dock.isVisible())
|
||||
|
||||
def toggle_projector_manager(self):
|
||||
"""
|
||||
Toggle visibility of the projector manager
|
||||
"""
|
||||
self.projector_manager_dock.setVisible(not self.projector_manager_dock.isVisible())
|
||||
if self.sender() is self.view_projector_manager_item:
|
||||
self.projector_manager_dock.setVisible(not self.projector_manager_dock.isVisible())
|
||||
self.view_projector_manager_item.setChecked(self.projector_manager_dock.isVisible())
|
||||
self.settings.setValue('user interface/is preset layout', False)
|
||||
self.settings.setValue('user interface/show projectors', self.projector_manager_dock.isVisible())
|
||||
# Check/uncheck checkbox on First time wizard based on visibility of this panel.
|
||||
if not self.settings.value('projector/show after wizard'):
|
||||
self.settings.setValue('projector/show after wizard', True)
|
||||
|
@ -1173,15 +1183,21 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, LogMixin, RegistryPropert
|
|||
"""
|
||||
Toggle the visibility of the service manager
|
||||
"""
|
||||
self.service_manager_dock.setVisible(not self.service_manager_dock.isVisible())
|
||||
if self.sender() is self.view_service_manager_item:
|
||||
self.service_manager_dock.setVisible(not self.service_manager_dock.isVisible())
|
||||
self.view_service_manager_item.setChecked(self.service_manager_dock.isVisible())
|
||||
self.settings.setValue('user interface/is preset layout', False)
|
||||
self.settings.setValue('user interface/show service', self.service_manager_dock.isVisible())
|
||||
|
||||
def toggle_theme_manager(self):
|
||||
"""
|
||||
Toggle the visibility of the theme manager
|
||||
"""
|
||||
self.theme_manager_dock.setVisible(not self.theme_manager_dock.isVisible())
|
||||
if self.sender() is self.view_theme_manager_item:
|
||||
self.theme_manager_dock.setVisible(not self.theme_manager_dock.isVisible())
|
||||
self.view_theme_manager_item.setChecked(self.theme_manager_dock.isVisible())
|
||||
self.settings.setValue('user interface/is preset layout', False)
|
||||
self.settings.setValue('user interface/show themes', self.theme_manager_dock.isVisible())
|
||||
|
||||
def set_preview_panel_visibility(self, visible):
|
||||
"""
|
||||
|
|
|
@ -241,7 +241,7 @@ class MediaController(RegistryBase, LogMixin, RegistryProperties):
|
|||
controller.media_info.volume = self.settings.value('media/preview volume')
|
||||
# background will always loop video.
|
||||
if service_item.is_capable(ItemCapabilities.HasBackgroundAudio):
|
||||
controller.media_info.file_info = service_item.background_audio
|
||||
controller.media_info.file_info = [file_path for (file_path, file_hash) in service_item.background_audio]
|
||||
controller.media_info.media_type = MediaType.Audio
|
||||
# is_background indicates we shouldn't override the normal display
|
||||
controller.media_info.is_background = True
|
||||
|
|
|
@ -571,8 +571,9 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi
|
|||
"""
|
||||
Get a list of files used in the service and files that are missing.
|
||||
|
||||
:return: A list of files used in the service that exist, and a list of files that don't.
|
||||
:rtype: (list[Path], list[str])
|
||||
:return: A list of tuples with files used in the service that exist and their sha256-based name in the
|
||||
servicefile, and a list of files that doesn't exists.
|
||||
:rtype: (list[(Path,str)], list[str])
|
||||
"""
|
||||
write_list = []
|
||||
missing_list = []
|
||||
|
@ -589,7 +590,7 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi
|
|||
if item['service_item'].stored_filename:
|
||||
sha256_file_name = Path(item['service_item'].stored_filename)
|
||||
else:
|
||||
sha256_file_name = Path(sha256_file_hash(frame_path)) / frame_path.suffix
|
||||
sha256_file_name = Path(sha256_file_hash(frame_path) + frame_path.suffix)
|
||||
bundle = (frame_path, sha256_file_name)
|
||||
if bundle in write_list or str(frame_path) in missing_list:
|
||||
continue
|
||||
|
@ -626,8 +627,8 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi
|
|||
if path_from_tuple in write_list:
|
||||
continue
|
||||
write_list.append(path_from_tuple)
|
||||
for audio_path in item['service_item'].background_audio:
|
||||
service_path = sha256_file_hash(audio_path) + os.path.splitext(audio_path)[1]
|
||||
for (audio_path, audio_file_hash) in item['service_item'].background_audio:
|
||||
service_path = audio_file_hash + audio_path.suffix
|
||||
audio_path_tuple = (audio_path, service_path)
|
||||
if audio_path_tuple in write_list:
|
||||
continue
|
||||
|
|
|
@ -402,7 +402,7 @@ class ThemeManager(QtWidgets.QWidget, RegistryBase, Ui_ThemeManager, LogMixin, R
|
|||
theme_data.background_filename = self.theme_path / new_theme_name / theme_data.background_filename.name
|
||||
theme_data.theme_name = new_theme_name
|
||||
theme_data.extend_image_filename(self.theme_path)
|
||||
self.save_theme(theme_data, background_override=old_background)
|
||||
self.save_theme(theme_data, background_file=old_background)
|
||||
self.update_preview_images([new_theme_name])
|
||||
self.load_themes()
|
||||
|
||||
|
@ -722,12 +722,12 @@ class ThemeManager(QtWidgets.QWidget, RegistryBase, Ui_ThemeManager, LogMixin, R
|
|||
return False
|
||||
return True
|
||||
|
||||
def save_theme(self, theme, background_override=None):
|
||||
def save_theme(self, theme, background_file=None):
|
||||
"""
|
||||
Writes the theme to the disk and including the background image and thumbnail if necessary
|
||||
|
||||
:param Theme theme: The theme data object.
|
||||
:param background_override: Background to use rather than background_source. Optionally.
|
||||
:param background_file: Background to use rather than background_source. Optional.
|
||||
:rtype: None
|
||||
"""
|
||||
name = theme.theme_name
|
||||
|
@ -739,15 +739,14 @@ class ThemeManager(QtWidgets.QWidget, RegistryBase, Ui_ThemeManager, LogMixin, R
|
|||
theme_path.write_text(theme_pretty)
|
||||
except OSError:
|
||||
self.log_exception('Saving theme to file failed')
|
||||
if theme.background_source and theme.background_filename and theme.background_type != 'stream':
|
||||
background_file = background_override
|
||||
if theme.background_filename and theme.background_type != 'stream':
|
||||
# Use theme source image if override doesn't exist
|
||||
if not background_file or not background_file.exists():
|
||||
background_file = theme.background_source
|
||||
if self.old_background_image_path and theme.background_filename != self.old_background_image_path:
|
||||
delete_file(self.old_background_image_path)
|
||||
if not background_file.exists():
|
||||
self.log_warning('Background does not exist, retaining cached background')
|
||||
if not background_file or not background_file.exists():
|
||||
self.log_warning('Background source does not exist, retaining cached background')
|
||||
elif background_file != theme.background_filename:
|
||||
try:
|
||||
shutil.copyfile(background_file, theme.background_filename)
|
||||
|
|
|
@ -116,7 +116,11 @@ class WordProjectBible(BibleImport):
|
|||
header_div = soup.find('div', 'textHeader')
|
||||
chapters_p = header_div.find('p')
|
||||
if not chapters_p:
|
||||
chapters_p = soup.p
|
||||
log.debug('Corrupted header, searching for span instead of a p')
|
||||
chapters_p = header_div.find('span')
|
||||
if not chapters_p:
|
||||
log.debug('Cannot find chapters, using all p instead')
|
||||
chapters_p = soup.p
|
||||
log.debug(chapters_p)
|
||||
for item in chapters_p.contents:
|
||||
if self.stop_import_flag:
|
||||
|
|
|
@ -38,7 +38,6 @@ from PyQt5 import QtCore
|
|||
from openlp.core.common import delete_file, get_uno_command, get_uno_instance, trace_error_handler
|
||||
from openlp.core.common.platform import is_win
|
||||
from openlp.core.common.registry import Registry
|
||||
from openlp.core.common.settings import Settings
|
||||
from openlp.core.display.screens import ScreenList
|
||||
from openlp.plugins.presentations.lib.presentationcontroller import PresentationController, PresentationDocument, \
|
||||
TextType
|
||||
|
@ -333,7 +332,7 @@ class ImpressDocument(PresentationDocument):
|
|||
# but OpenLP sets screen numbers based on screen coordinates (geometry)
|
||||
# so we work out what OpenOffice display to use based on which of the openlp screens is primary
|
||||
# unless the user has defined in Settings to use the impress Slide Show setting for presentation display
|
||||
if not Settings().value('presentations/impress use display setting'):
|
||||
if not self.settings.value('presentations/impress use display setting'):
|
||||
public_display_screen_number = ScreenList().current.number
|
||||
screens = list(ScreenList())
|
||||
if screens[public_display_screen_number].is_primary:
|
||||
|
|
|
@ -180,23 +180,24 @@ class LibreOfficeServer(object):
|
|||
if hasattr(self, '_docs'):
|
||||
while self._docs:
|
||||
self._docs[0].close_presentation()
|
||||
docs = self.desktop.getComponents()
|
||||
count = 0
|
||||
if docs.hasElements():
|
||||
list_elements = docs.createEnumeration()
|
||||
while list_elements.hasMoreElements():
|
||||
doc = list_elements.nextElement()
|
||||
if doc.getImplementationName() != 'com.sun.star.comp.framework.BackingComp':
|
||||
count += 1
|
||||
if count > 0:
|
||||
log.debug('LibreOffice not terminated as docs are still open')
|
||||
can_kill = False
|
||||
else:
|
||||
try:
|
||||
self.desktop.terminate()
|
||||
log.debug('LibreOffice killed')
|
||||
except Exception:
|
||||
log.exception('Failed to terminate LibreOffice')
|
||||
if self.desktop:
|
||||
docs = self.desktop.getComponents()
|
||||
count = 0
|
||||
if docs.hasElements():
|
||||
list_elements = docs.createEnumeration()
|
||||
while list_elements.hasMoreElements():
|
||||
doc = list_elements.nextElement()
|
||||
if doc.getImplementationName() != 'com.sun.star.comp.framework.BackingComp':
|
||||
count += 1
|
||||
if count > 0:
|
||||
log.debug('LibreOffice not terminated as docs are still open')
|
||||
can_kill = False
|
||||
else:
|
||||
try:
|
||||
self.desktop.terminate()
|
||||
log.debug('LibreOffice killed')
|
||||
except Exception:
|
||||
log.exception('Failed to terminate LibreOffice')
|
||||
if getattr(self, '_process') and can_kill:
|
||||
self._process.kill()
|
||||
|
||||
|
|
|
@ -114,7 +114,12 @@ class MacLOController(PresentationController, LogMixin):
|
|||
Called at system exit to clean up any running presentations.
|
||||
"""
|
||||
log.debug('Kill LibreOffice')
|
||||
self.client.shutdown()
|
||||
try:
|
||||
# Some people like to close LibreOffice themselves, let's just catch any errors so that OpenLP fails
|
||||
# silently
|
||||
self.client.shutdown()
|
||||
except Exception:
|
||||
pass
|
||||
self.server_process.kill()
|
||||
|
||||
|
||||
|
|
|
@ -300,6 +300,9 @@ class PresentationMediaItem(FolderLibraryItem):
|
|||
return False
|
||||
items = [self.list_view.itemFromIndex(item) if isinstance(item, QtCore.QModelIndex) else item
|
||||
for item in items]
|
||||
# If this is a folder, show an error message and return
|
||||
if items and isinstance(items[0].data(0, QtCore.Qt.UserRole), Folder):
|
||||
return False
|
||||
if file_path is None:
|
||||
file_path = Path(items[0].data(0, QtCore.Qt.UserRole).file_path)
|
||||
file_type = file_path.suffix.lower()[1:]
|
||||
|
|
|
@ -28,7 +28,6 @@ from openlp.core.common import Singleton, md5_hash, sha256_file_hash
|
|||
from openlp.core.common.applocation import AppLocation
|
||||
from openlp.core.common.path import create_paths
|
||||
from openlp.core.common.registry import Registry
|
||||
from openlp.core.common.settings import Settings
|
||||
from openlp.core.lib import create_thumb
|
||||
|
||||
|
||||
|
@ -147,7 +146,7 @@ class PresentationDocument(object):
|
|||
# get_temp_folder and PresentationPluginapp_startup is removed
|
||||
if self.settings.value('presentations/thumbnail_scheme') == 'md5':
|
||||
folder = md5_hash(bytes(self.file_path))
|
||||
elif Settings().value('presentations/thumbnail_scheme') == 'sha256file':
|
||||
elif self.settings.value('presentations/thumbnail_scheme') == 'sha256file':
|
||||
if self._sha256_file_hash:
|
||||
folder = self._sha256_file_hash
|
||||
else:
|
||||
|
@ -170,7 +169,7 @@ class PresentationDocument(object):
|
|||
# get_thumbnail_folder and PresentationPluginapp_startup is removed
|
||||
if self.settings.value('presentations/thumbnail_scheme') == 'md5':
|
||||
folder = md5_hash(bytes(self.file_path))
|
||||
elif Settings().value('presentations/thumbnail_scheme') == 'sha256file':
|
||||
elif self.settings.value('presentations/thumbnail_scheme') == 'sha256file':
|
||||
if self._sha256_file_hash:
|
||||
folder = self._sha256_file_hash
|
||||
else:
|
||||
|
|
|
@ -103,6 +103,13 @@ class Book(BaseModel):
|
|||
"""
|
||||
Book model
|
||||
"""
|
||||
@property
|
||||
def songs(self):
|
||||
"""
|
||||
A property to return the songs associated with this book.
|
||||
"""
|
||||
return [sbe.song for sbe in self.entries]
|
||||
|
||||
def __repr__(self):
|
||||
return '<Book id="{myid:d}" name="{name}" publisher="{publisher}" />'.format(myid=self.id,
|
||||
name=self.name,
|
||||
|
@ -237,8 +244,10 @@ def init_schema(url):
|
|||
|
||||
**media_files Table**
|
||||
* id
|
||||
* _file_path
|
||||
* file_path
|
||||
* file_hash
|
||||
* type
|
||||
* weight
|
||||
|
||||
**song_books Table**
|
||||
The *song_books* table holds a list of books that a congregation gets
|
||||
|
@ -305,6 +314,7 @@ def init_schema(url):
|
|||
Column('id', types.Integer(), primary_key=True),
|
||||
Column('song_id', types.Integer(), ForeignKey('songs.id'), default=None),
|
||||
Column('file_path', PathType, nullable=False),
|
||||
Column('file_hash', types.Unicode(128), nullable=False),
|
||||
Column('type', types.Unicode(64), nullable=False, default='audio'),
|
||||
Column('weight', types.Integer(), default=0)
|
||||
)
|
||||
|
@ -383,7 +393,7 @@ def init_schema(url):
|
|||
class_mapper(SongBookEntry)
|
||||
except UnmappedClassError:
|
||||
mapper(SongBookEntry, songs_songbooks_table, properties={
|
||||
'songbook': relation(Book)
|
||||
'songbook': relation(Book, backref='entries')
|
||||
})
|
||||
try:
|
||||
class_mapper(Book)
|
||||
|
|
|
@ -24,6 +24,7 @@ The :mod:`zionworx` module provides the functionality for importing ZionWorx son
|
|||
import csv
|
||||
import logging
|
||||
|
||||
from openlp.core.common import get_file_encoding
|
||||
from openlp.core.common.i18n import translate
|
||||
from openlp.plugins.songs.lib.importers.songimport import SongImport
|
||||
|
||||
|
@ -72,8 +73,10 @@ class ZionWorxImport(SongImport):
|
|||
"""
|
||||
Receive a CSV file (from a ZionWorx database dump) to import.
|
||||
"""
|
||||
# Encoding should always be ISO-8859-1
|
||||
with self.import_source.open('rt', encoding='ISO-8859-1') as songs_file:
|
||||
# Try to detect encoding, fall back to UTF-8 / ISO-8859-1
|
||||
encoding = get_file_encoding(self.import_source)
|
||||
log.info(f'Encoding: {encoding}')
|
||||
with self.import_source.open('rt', encoding=encoding) as songs_file:
|
||||
field_names = ['SongNum', 'Title1', 'Title2', 'Lyrics', 'Writer', 'Copyright', 'Keywords',
|
||||
'DefaultStyle']
|
||||
songs_reader = csv.DictReader(songs_file, field_names)
|
||||
|
|
|
@ -81,10 +81,10 @@ class SongMediaItem(MediaManagerItem):
|
|||
song.media_files = []
|
||||
for i, bga in enumerate(item.background_audio):
|
||||
dest_path =\
|
||||
AppLocation.get_section_data_path(self.plugin.name) / 'audio' / str(song.id) / os.path.split(bga)[1]
|
||||
AppLocation.get_section_data_path(self.plugin.name) / 'audio' / str(song.id) / os.path.split(bga[0])[1]
|
||||
create_paths(dest_path.parent)
|
||||
copyfile(AppLocation.get_section_data_path('servicemanager') / bga, dest_path)
|
||||
song.media_files.append(MediaFile.populate(weight=i, file_path=dest_path))
|
||||
copyfile(AppLocation.get_section_data_path('servicemanager') / bga[0], dest_path)
|
||||
song.media_files.append(MediaFile.populate(weight=i, file_path=dest_path, file_hash=bga[1]))
|
||||
self.plugin.manager.save_object(song, True)
|
||||
|
||||
def add_middle_header_bar(self):
|
||||
|
@ -534,6 +534,7 @@ class SongMediaItem(MediaManagerItem):
|
|||
copyfile(media_file.file_path, new_media_file_path)
|
||||
new_media_file = MediaFile()
|
||||
new_media_file.file_path = new_media_file_path
|
||||
new_media_file.file_hash = media_file.file_hash
|
||||
new_media_file.type = media_file.type
|
||||
new_media_file.weight = media_file.weight
|
||||
new_song.media_files.append(new_media_file)
|
||||
|
@ -629,7 +630,7 @@ class SongMediaItem(MediaManagerItem):
|
|||
total_length = 0
|
||||
for m in song.media_files:
|
||||
total_length += self.media_controller.media_length(m.file_path)
|
||||
service_item.background_audio = [m.file_path for m in song.media_files]
|
||||
service_item.background_audio = [(m.file_path, m.file_hash) for m in song.media_files]
|
||||
service_item.set_media_length(total_length)
|
||||
service_item.metadata.append('<em>{label}:</em> {media}'.
|
||||
format(label=translate('SongsPlugin.MediaItem', 'Media'),
|
||||
|
|
|
@ -29,14 +29,15 @@ from pathlib import Path
|
|||
from sqlalchemy import Column, ForeignKey, Table, types
|
||||
from sqlalchemy.sql.expression import false, func, null, text
|
||||
|
||||
from openlp.core.common import sha256_file_hash
|
||||
from openlp.core.common.applocation import AppLocation
|
||||
from openlp.core.common.db import drop_columns
|
||||
from openlp.core.common.json import OpenLPJSONEncoder
|
||||
from openlp.core.common.json import OpenLPJSONEncoder, OpenLPJSONDecoder
|
||||
from openlp.core.lib.db import PathType, get_upgrade_op
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
__version__ = 7
|
||||
__version__ = 8
|
||||
|
||||
|
||||
# TODO: When removing an upgrade path the ftw-data needs updating to the minimum supported version
|
||||
|
@ -190,3 +191,28 @@ def upgrade_7(session, metadata):
|
|||
else:
|
||||
op.drop_constraint('media_files', 'foreignkey')
|
||||
op.drop_column('media_files', 'filenames')
|
||||
|
||||
|
||||
def upgrade_8(session, metadata):
|
||||
"""
|
||||
Version 8 upgrade - add sha256 hash to media
|
||||
"""
|
||||
log.debug('Starting upgrade_8 for adding sha256 hashes')
|
||||
old_table = Table('media_files', metadata, autoload=True)
|
||||
if 'file_hash' not in [col.name for col in old_table.c.values()]:
|
||||
op = get_upgrade_op(session)
|
||||
op.add_column('media_files', Column('file_hash', types.Unicode(128)))
|
||||
conn = op.get_bind()
|
||||
results = conn.execute('SELECT * FROM media_files')
|
||||
data_path = AppLocation.get_data_path()
|
||||
for row in results.fetchall():
|
||||
file_path = json.loads(row.file_path, cls=OpenLPJSONDecoder)
|
||||
full_file_path = data_path / file_path
|
||||
if full_file_path.exists():
|
||||
hash = sha256_file_hash(full_file_path)
|
||||
else:
|
||||
log.warning('{audio} does not exists, so no sha256 hash added.'.format(audio=str(file_path)))
|
||||
# set a fake "hash" to allow for the upgrade to go through. The image will be marked as invalid
|
||||
hash = 'NONE'
|
||||
sql = 'UPDATE media_files SET file_hash = :hash WHERE id = :id'
|
||||
conn.execute(sql, {'hash': hash, 'id': row.id})
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
"""
|
||||
Functional tests to test the Http Server Class.
|
||||
"""
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
from openlp.core.api.http.server import HttpServer
|
||||
|
@ -29,14 +30,17 @@ from openlp.core.common.registry import Registry
|
|||
|
||||
@patch('openlp.core.api.http.server.HttpWorker')
|
||||
@patch('openlp.core.api.http.server.run_thread')
|
||||
def test_server_start(mocked_run_thread, MockHttpWorker, registry):
|
||||
@patch('openlp.core.api.deploy.AppLocation.get_section_data_path')
|
||||
def test_server_start(mocked_get_section_data_path, mocked_run_thread, MockHttpWorker, registry):
|
||||
"""
|
||||
Test the starting of the Waitress Server with the disable flag set off
|
||||
"""
|
||||
# GIVEN: A new httpserver
|
||||
# GIVEN: A new httpserver and mocked get_section_data_path
|
||||
mocked_get_section_data_path.return_value = Path('.')
|
||||
# WHEN: I start the server
|
||||
Registry().set_flag('no_web_server', False)
|
||||
HttpServer()
|
||||
server = HttpServer()
|
||||
server.bootstrap_post_set_up()
|
||||
|
||||
# THEN: the api environment should have been created
|
||||
assert mocked_run_thread.call_count == 1, 'The qthread should have been called once'
|
||||
|
@ -45,14 +49,18 @@ def test_server_start(mocked_run_thread, MockHttpWorker, registry):
|
|||
|
||||
@patch('openlp.core.api.http.server.HttpWorker')
|
||||
@patch('openlp.core.api.http.server.run_thread')
|
||||
def test_server_start_not_required(mocked_run_thread, MockHttpWorker, registry):
|
||||
@patch('openlp.core.api.deploy.AppLocation.get_section_data_path')
|
||||
def test_server_start_not_required(mocked_get_section_data_path, mocked_run_thread, MockHttpWorker, registry):
|
||||
"""
|
||||
Test the starting of the Waitress Server with the disable flag set off
|
||||
"""
|
||||
# GIVEN: A new httpserver
|
||||
# GIVEN: A new httpserver and mocked get_section_data_path
|
||||
mocked_get_section_data_path.return_value = Path('.')
|
||||
|
||||
# WHEN: I start the server
|
||||
Registry().set_flag('no_web_server', True)
|
||||
HttpServer()
|
||||
server = HttpServer()
|
||||
server.bootstrap_post_set_up()
|
||||
|
||||
# THEN: the api environment should have been created
|
||||
assert mocked_run_thread.call_count == 0, 'The qthread should not have have been called'
|
||||
|
|
|
@ -32,6 +32,7 @@ def test_retrieve_live_items(flask_client, settings):
|
|||
fake_live_controller = MagicMock()
|
||||
fake_live_controller.service_item = MagicMock()
|
||||
fake_live_controller.selected_row = 0
|
||||
fake_live_controller.service_item.unique_identifier = 42
|
||||
fake_live_controller.service_item.to_dict.return_value = {'slides': [{'selected': False}]}
|
||||
Registry().register('live_controller', fake_live_controller)
|
||||
|
||||
|
@ -39,7 +40,7 @@ def test_retrieve_live_items(flask_client, settings):
|
|||
res = flask_client.get('/api/v2/controller/live-items').get_json()
|
||||
|
||||
# THEN: The correct item data should be returned
|
||||
assert res == {'slides': [{'selected': True}]}
|
||||
assert res == {'slides': [{'selected': True}], 'id': '42'}
|
||||
|
||||
|
||||
def test_controller_set_requires_login(settings, flask_client):
|
||||
|
|
|
@ -233,17 +233,17 @@ def test_format_slide(settings):
|
|||
with patch('openlp.core.display.render.ThemePreviewRenderer.__init__') as init_fn:
|
||||
init_fn.return_value = None
|
||||
preview_renderer = ThemePreviewRenderer()
|
||||
lyrics = 'hello {st}test{/st}\nline two\n[---]\nline after optional split'
|
||||
lyrics = 'hello {st}test{/st}\nline two line after a {st}nice{/st} new line'
|
||||
preview_renderer._is_initialised = True
|
||||
preview_renderer.log_debug = MagicMock()
|
||||
preview_renderer._text_fits_on_slide = MagicMock(side_effect=lambda a: a == '')
|
||||
preview_renderer._text_fits_on_slide = MagicMock(side_effect=lambda a: len(a) < 80)
|
||||
preview_renderer.force_page = False
|
||||
|
||||
# WHEN: format_slide is run
|
||||
formatted_slides = preview_renderer.format_slide(lyrics, None)
|
||||
|
||||
# THEN: The formatted slides should have all the text and no blank slides
|
||||
assert formatted_slides == ['hello {st}test{/st}', 'line two', 'line after optional split']
|
||||
# THEN: The formatted slides should have all the text, no blank slides and formatting tags should still be there
|
||||
assert formatted_slides == ['hello {st}test{/st}', 'line two line after a {st}nice{/st} new line']
|
||||
|
||||
|
||||
def test_format_slide_no_split(settings):
|
||||
|
|
|
@ -88,9 +88,11 @@ def test_create_screen_list(mocked_screens, settings):
|
|||
assert screen_list.screens[0].number == 0
|
||||
assert screen_list.screens[0].geometry == QtCore.QRect(0, 0, 1024, 768)
|
||||
assert screen_list.screens[0].is_primary is True
|
||||
assert screen_list.screens[0].is_display is False
|
||||
assert screen_list.screens[1].number == 1
|
||||
assert screen_list.screens[1].geometry == QtCore.QRect(1024, 0, 1024, 768)
|
||||
assert screen_list.screens[1].is_primary is False
|
||||
assert screen_list.screens[1].is_display is True
|
||||
|
||||
|
||||
@patch('openlp.core.display.screens.QtWidgets.QApplication.screens')
|
||||
|
@ -415,3 +417,183 @@ def test_screen_repr():
|
|||
|
||||
# THEN: The string should be correct (screens are 0-based)
|
||||
assert screen_str == '<Screen 2 (primary)>'
|
||||
|
||||
|
||||
@patch('openlp.core.display.screens.QtWidgets.QApplication.screens')
|
||||
def test_screen_removed(mocked_screens, settings):
|
||||
"""Test that the screen list is correct after a new screen is removed"""
|
||||
# GIVEN: A screenlist of a mocked application with two screens
|
||||
mocked_application = MagicMock()
|
||||
mocked_screen1 = MagicMock(**{'geometry.return_value': QtCore.QRect(0, 0, 1024, 768)})
|
||||
mocked_screen2 = MagicMock(**{'geometry.return_value': QtCore.QRect(1024, 0, 1024, 768)})
|
||||
mocked_application.screens.return_value = [mocked_screen1, mocked_screen2]
|
||||
mocked_application.primaryScreen.return_value = mocked_screen1
|
||||
|
||||
screen_list = ScreenList.create(mocked_application)
|
||||
|
||||
# WHEN: Screen 2 is removed from the application
|
||||
mocked_application.screens.return_value = [mocked_screen1]
|
||||
screen_list.on_screen_removed(mocked_screen2)
|
||||
|
||||
# THEN: We have 1 primary screen left in the list
|
||||
assert len(screen_list.screens) == 1
|
||||
assert screen_list.screens[0].number == 0
|
||||
assert screen_list.screens[0].geometry == QtCore.QRect(0, 0, 1024, 768)
|
||||
assert screen_list.screens[0].is_primary is True
|
||||
assert screen_list.screens[0].is_display is True
|
||||
|
||||
|
||||
@patch('openlp.core.display.screens.QtWidgets.QApplication.screens')
|
||||
def test_screen_added(mocked_screens, settings):
|
||||
"""Test that the screen list is correct after a screen is added"""
|
||||
# GIVEN: A screenlist of a mocked application with one screen
|
||||
mocked_application = MagicMock()
|
||||
mocked_screen1 = MagicMock(**{'geometry.return_value': QtCore.QRect(0, 0, 1024, 768)})
|
||||
mocked_application.screens.return_value = [mocked_screen1]
|
||||
mocked_application.primaryScreen.return_value = mocked_screen1
|
||||
|
||||
screen_list = ScreenList.create(mocked_application)
|
||||
|
||||
# WHEN: Screen 2 is added to the application
|
||||
mocked_screen2 = MagicMock(**{'geometry.return_value': QtCore.QRect(1024, 0, 1024, 768)})
|
||||
mocked_application.screens.return_value = [mocked_screen1, mocked_screen2]
|
||||
screen_list.on_screen_added(mocked_screen2)
|
||||
|
||||
# THEN: We have 2 screens, one primary, one display
|
||||
assert len(screen_list.screens) == 2
|
||||
assert screen_list.screens[0].number == 0
|
||||
assert screen_list.screens[0].geometry == QtCore.QRect(0, 0, 1024, 768)
|
||||
assert screen_list.screens[0].is_primary is True
|
||||
assert screen_list.screens[0].is_display is False
|
||||
assert screen_list.screens[1].number == 1
|
||||
assert screen_list.screens[1].geometry == QtCore.QRect(1024, 0, 1024, 768)
|
||||
assert screen_list.screens[1].is_primary is False
|
||||
assert screen_list.screens[1].is_display is True
|
||||
|
||||
|
||||
@patch('openlp.core.display.screens.QtWidgets.QApplication.screens')
|
||||
def test_screen_removed_added(mocked_screens, settings):
|
||||
"""
|
||||
Test that the screen list is correct after a screen disappears and reappears again.
|
||||
This is a common scenario when a secondary screen (possibly a projector) is turned on and off, goes into standby
|
||||
or has a unstable connection. When the secondary screen returns it should also be marked display as before.
|
||||
"""
|
||||
# GIVEN: A screenlist of a mocked application with two screens
|
||||
mocked_application = MagicMock()
|
||||
mocked_screen1 = MagicMock(**{'geometry.return_value': QtCore.QRect(0, 0, 1024, 768)})
|
||||
mocked_screen2 = MagicMock(**{'geometry.return_value': QtCore.QRect(1024, 0, 1024, 768)})
|
||||
mocked_application.screens.return_value = [mocked_screen1, mocked_screen2]
|
||||
mocked_application.primaryScreen.return_value = mocked_screen1
|
||||
|
||||
# Create the screenlist with both screens present
|
||||
screen_list = ScreenList.create(mocked_application)
|
||||
|
||||
# WHEN: Screen 2 is removed and added again
|
||||
# Remove screen 2
|
||||
mocked_application.screens.return_value = [mocked_screen1]
|
||||
screen_list.on_screen_removed(mocked_screen2)
|
||||
# Add screen 2
|
||||
mocked_application.screens.return_value = [mocked_screen1, mocked_screen2]
|
||||
screen_list.on_screen_added(mocked_screen2)
|
||||
|
||||
# THEN: We have 2 screens, one primary, one display
|
||||
assert len(screen_list.screens) == 2
|
||||
assert screen_list.screens[0].number == 0
|
||||
assert screen_list.screens[0].geometry == QtCore.QRect(0, 0, 1024, 768)
|
||||
assert screen_list.screens[0].is_primary is True
|
||||
assert screen_list.screens[0].is_display is False
|
||||
assert screen_list.screens[1].number == 1
|
||||
assert screen_list.screens[1].geometry == QtCore.QRect(1024, 0, 1024, 768)
|
||||
assert screen_list.screens[1].is_primary is False
|
||||
assert screen_list.screens[1].is_display is True
|
||||
|
||||
|
||||
@patch('openlp.core.display.screens.QtWidgets.QApplication.screens')
|
||||
def test_third_screen_added(mocked_screens, settings):
|
||||
"""Test that the screen list is correct after a third screen is added"""
|
||||
# GIVEN: A screenlist of a mocked application with one screen
|
||||
mocked_application = MagicMock()
|
||||
mocked_screen1 = MagicMock(**{'geometry.return_value': QtCore.QRect(0, 0, 1024, 768)})
|
||||
mocked_screen2 = MagicMock(**{'geometry.return_value': QtCore.QRect(1024, 0, 1024, 768)})
|
||||
mocked_application.screens.return_value = [mocked_screen1, mocked_screen2]
|
||||
mocked_application.primaryScreen.return_value = mocked_screen1
|
||||
|
||||
screen_list = ScreenList.create(mocked_application)
|
||||
|
||||
# WHEN: Screen 2 is added to the application
|
||||
mocked_screen3 = MagicMock(**{'geometry.return_value': QtCore.QRect(2048, 0, 1024, 768)})
|
||||
mocked_application.screens.return_value = [mocked_screen1, mocked_screen2, mocked_screen3]
|
||||
screen_list.on_screen_added(mocked_screen3)
|
||||
|
||||
# THEN: We have 3 screens, one primary, one display, one that is neither.
|
||||
assert len(screen_list.screens) == 3
|
||||
assert screen_list.screens[0].number == 0
|
||||
assert screen_list.screens[0].geometry == QtCore.QRect(0, 0, 1024, 768)
|
||||
assert screen_list.screens[0].is_primary is True
|
||||
assert screen_list.screens[0].is_display is False
|
||||
assert screen_list.screens[1].number == 1
|
||||
assert screen_list.screens[1].geometry == QtCore.QRect(1024, 0, 1024, 768)
|
||||
assert screen_list.screens[1].is_primary is False
|
||||
assert screen_list.screens[1].is_display is True
|
||||
assert screen_list.screens[2].number == 2
|
||||
assert screen_list.screens[2].geometry == QtCore.QRect(2048, 0, 1024, 768)
|
||||
assert screen_list.screens[2].is_primary is False
|
||||
assert screen_list.screens[2].is_display is False
|
||||
|
||||
|
||||
@patch('openlp.core.display.screens.QtWidgets.QApplication.screens')
|
||||
def test_third_screen_removed(mocked_screens, settings):
|
||||
"""Test that the screen list is correct after a third screen is removed"""
|
||||
# GIVEN: A screenlist of a mocked application with one screen
|
||||
mocked_application = MagicMock()
|
||||
mocked_screen1 = MagicMock(**{'geometry.return_value': QtCore.QRect(0, 0, 1024, 768)})
|
||||
mocked_screen2 = MagicMock(**{'geometry.return_value': QtCore.QRect(1024, 0, 1024, 768)})
|
||||
mocked_screen3 = MagicMock(**{'geometry.return_value': QtCore.QRect(2048, 0, 1024, 768)})
|
||||
mocked_application.screens.return_value = [mocked_screen1, mocked_screen2, mocked_screen3]
|
||||
mocked_application.primaryScreen.return_value = mocked_screen1
|
||||
|
||||
screen_list = ScreenList.create(mocked_application)
|
||||
|
||||
# WHEN: Screen 2 is added to the application
|
||||
mocked_application.screens.return_value = [mocked_screen1, mocked_screen2]
|
||||
screen_list.on_screen_removed(mocked_screen3)
|
||||
|
||||
# THEN: We have 3 screens, one primary, one display, one that is neither.
|
||||
assert len(screen_list.screens) == 2
|
||||
assert screen_list.screens[0].number == 0
|
||||
assert screen_list.screens[0].geometry == QtCore.QRect(0, 0, 1024, 768)
|
||||
assert screen_list.screens[0].is_primary is True
|
||||
assert screen_list.screens[0].is_display is False
|
||||
assert screen_list.screens[1].number == 1
|
||||
assert screen_list.screens[1].geometry == QtCore.QRect(1024, 0, 1024, 768)
|
||||
assert screen_list.screens[1].is_primary is False
|
||||
assert screen_list.screens[1].is_display is True
|
||||
|
||||
|
||||
@patch('openlp.core.display.screens.QtWidgets.QApplication.screens')
|
||||
def test_swap_primary_screen(mocked_screens, settings):
|
||||
"""Test that the screen list is correct after a different screen becomes a primary screen"""
|
||||
# GIVEN: A screenlist of a mocked application with two screens
|
||||
mocked_application = MagicMock()
|
||||
mocked_screen1 = MagicMock(**{'geometry.return_value': QtCore.QRect(0, 0, 1024, 768)})
|
||||
mocked_screen2 = MagicMock(**{'geometry.return_value': QtCore.QRect(1024, 0, 1024, 768)})
|
||||
mocked_application.screens.return_value = [mocked_screen1, mocked_screen2]
|
||||
mocked_application.primaryScreen.return_value = mocked_screen1
|
||||
|
||||
# Create the screenlist with the initial state
|
||||
screen_list = ScreenList.create(mocked_application)
|
||||
|
||||
# THEN: Make screen 2 the primary screen
|
||||
mocked_application.primaryScreen.return_value = mocked_screen2
|
||||
screen_list.on_primary_screen_changed()
|
||||
|
||||
# THEN: The second screen is now primary
|
||||
assert len(screen_list.screens) == 2
|
||||
assert screen_list.screens[0].number == 0
|
||||
assert screen_list.screens[0].geometry == QtCore.QRect(0, 0, 1024, 768)
|
||||
assert screen_list.screens[0].is_primary is False
|
||||
assert screen_list.screens[0].is_display is True
|
||||
assert screen_list.screens[1].number == 1
|
||||
assert screen_list.screens[1].geometry == QtCore.QRect(1024, 0, 1024, 768)
|
||||
assert screen_list.screens[1].is_primary is True
|
||||
assert screen_list.screens[1].is_display is False
|
||||
|
|
|
@ -412,7 +412,8 @@ def test_service_item_load_optical_media_from_service(state_media):
|
|||
assert service_item.media_length == 17.694, 'Media length should be 17.694'
|
||||
|
||||
|
||||
def test_service_item_load_song_and_audio_from_service(state_media, settings, service_item_env):
|
||||
@patch('openlp.core.lib.serviceitem.sha256_file_hash')
|
||||
def test_service_item_load_song_and_audio_from_service(mock_sha256_file_hash, state_media, settings, service_item_env):
|
||||
"""
|
||||
Test the Service Item - adding a song slide from a saved service
|
||||
"""
|
||||
|
@ -420,6 +421,7 @@ def test_service_item_load_song_and_audio_from_service(state_media, settings, se
|
|||
service_item = ServiceItem(None)
|
||||
service_item.add_icon = MagicMock()
|
||||
FormattingTags.load_tags()
|
||||
mock_sha256_file_hash.return_value = 'abcd'
|
||||
|
||||
# WHEN: We add a custom from a saved service
|
||||
line = convert_file_service_item(TEST_PATH, 'serviceitem-song-linked-audio.osj')
|
||||
|
@ -436,8 +438,8 @@ def test_service_item_load_song_and_audio_from_service(state_media, settings, se
|
|||
'"Amazing Grace! how sweet the s" has been returned as the title'
|
||||
assert '’Twas grace that taught my hea' == service_item.get_frame_title(1), \
|
||||
'"’Twas grace that taught my hea" has been returned as the title'
|
||||
assert Path('/test/amazing_grace.mp3') == service_item.background_audio[0], \
|
||||
'"/test/amazing_grace.mp3" should be in the background_audio list'
|
||||
assert (Path('/test/amazing_grace.mp3'), 'abcd') == service_item.background_audio[0], \
|
||||
'The tuple ("/test/abcd.mp3", "abcd") should be in the background_audio list'
|
||||
|
||||
|
||||
def test_service_item_get_theme_data_global_level(settings):
|
||||
|
@ -692,7 +694,8 @@ def test_get_transition_delay_slow(settings):
|
|||
assert delay == 2
|
||||
|
||||
|
||||
def test_to_dict_text_item(state_media, settings, service_item_env):
|
||||
@patch('openlp.core.lib.serviceitem.sha256_file_hash')
|
||||
def test_to_dict_text_item(mocked_sha256_file_hash, state_media, settings, service_item_env):
|
||||
"""
|
||||
Test that the to_dict() method returns the correct data for the service item
|
||||
"""
|
||||
|
@ -701,6 +704,7 @@ def test_to_dict_text_item(state_media, settings, service_item_env):
|
|||
mocked_plugin.name = 'songs'
|
||||
service_item = ServiceItem(mocked_plugin)
|
||||
service_item.add_icon = MagicMock()
|
||||
mocked_sha256_file_hash.return_value = 'abcd'
|
||||
FormattingTags.load_tags()
|
||||
line = convert_file_service_item(TEST_PATH, 'serviceitem-song-linked-audio.osj')
|
||||
if is_win():
|
||||
|
@ -713,11 +717,11 @@ def test_to_dict_text_item(state_media, settings, service_item_env):
|
|||
result = service_item.to_dict()
|
||||
|
||||
# THEN: The correct dictionary should be returned
|
||||
expected_fake_path = str(fake_path / 'amazing_grace.mp3')
|
||||
expected_fake_path = fake_path / 'amazing_grace.mp3'
|
||||
expected_dict = {
|
||||
|
||||
'audit': ['Amazing Grace', ['John Newton'], '', ''],
|
||||
'backgroundAudio': [expected_fake_path],
|
||||
'backgroundAudio': [(expected_fake_path, 'abcd')],
|
||||
'capabilities': [2, 1, 5, 8, 9, 13, 15],
|
||||
'footer': ['Amazing Grace', 'Written by: John Newton'],
|
||||
'fromPlugin': False,
|
||||
|
@ -726,11 +730,10 @@ def test_to_dict_text_item(state_media, settings, service_item_env):
|
|||
'notes': '',
|
||||
'slides': [
|
||||
{
|
||||
'chords': '<span class="nochordline">'
|
||||
'Amazing Grace! how sweet the sound\n'
|
||||
'chords': 'Amazing Grace! how sweet the sound\n'
|
||||
'That saved a wretch like me;\n'
|
||||
'I once was lost, but now am found,\n'
|
||||
'Was blind, but now I see.</span>',
|
||||
'Was blind, but now I see.',
|
||||
'html': 'Amazing Grace! how sweet the sound\n'
|
||||
'That saved a wretch like me;\n'
|
||||
'I once was lost, but now am found,\n'
|
||||
|
@ -745,11 +748,10 @@ def test_to_dict_text_item(state_media, settings, service_item_env):
|
|||
'footer': 'Amazing Grace<br>Written by: John Newton'
|
||||
},
|
||||
{
|
||||
'chords': '<span class="nochordline">'
|
||||
'’Twas grace that taught my heart to fear,\n'
|
||||
'chords': '’Twas grace that taught my heart to fear,\n'
|
||||
'And grace my fears relieved;\n'
|
||||
'How precious did that grace appear,\n'
|
||||
'The hour I first believed!</span>',
|
||||
'The hour I first believed!',
|
||||
'html': '’Twas grace that taught my heart to fear,\n'
|
||||
'And grace my fears relieved;\n'
|
||||
'How precious did that grace appear,\n'
|
||||
|
@ -764,11 +766,10 @@ def test_to_dict_text_item(state_media, settings, service_item_env):
|
|||
'footer': 'Amazing Grace<br>Written by: John Newton'
|
||||
},
|
||||
{
|
||||
'chords': '<span class="nochordline">'
|
||||
'Through many dangers, toils and snares\n'
|
||||
'chords': 'Through many dangers, toils and snares\n'
|
||||
'I have already come;\n'
|
||||
'’Tis grace that brought me safe thus far,\n'
|
||||
'And grace will lead me home.</span>',
|
||||
'And grace will lead me home.',
|
||||
'html': 'Through many dangers, toils and snares\n'
|
||||
'I have already come;\n'
|
||||
'’Tis grace that brought me safe thus far,\n'
|
||||
|
@ -783,11 +784,10 @@ def test_to_dict_text_item(state_media, settings, service_item_env):
|
|||
'footer': 'Amazing Grace<br>Written by: John Newton'
|
||||
},
|
||||
{
|
||||
'chords': '<span class="nochordline">'
|
||||
'The Lord has promised good to me,\n'
|
||||
'chords': 'The Lord has promised good to me,\n'
|
||||
'His word my hope secures;\n'
|
||||
'He will my shield and portion be\n'
|
||||
'As long as life endures.</span>',
|
||||
'As long as life endures.',
|
||||
'html': 'The Lord has promised good to me,\n'
|
||||
'His word my hope secures;\n'
|
||||
'He will my shield and portion be\n'
|
||||
|
@ -802,11 +802,10 @@ def test_to_dict_text_item(state_media, settings, service_item_env):
|
|||
'footer': 'Amazing Grace<br>Written by: John Newton'
|
||||
},
|
||||
{
|
||||
'chords': '<span class="nochordline">'
|
||||
'Yes, when this heart and flesh shall fail,\n'
|
||||
'chords': 'Yes, when this heart and flesh shall fail,\n'
|
||||
'And mortal life shall cease,\n'
|
||||
'I shall possess within the veil\n'
|
||||
'A life of joy and peace.</span>',
|
||||
'A life of joy and peace.',
|
||||
'html': 'Yes, when this heart and flesh shall fail,\n'
|
||||
'And mortal life shall cease,\n'
|
||||
'I shall possess within the veil\n'
|
||||
|
@ -821,11 +820,10 @@ def test_to_dict_text_item(state_media, settings, service_item_env):
|
|||
'footer': 'Amazing Grace<br>Written by: John Newton'
|
||||
},
|
||||
{
|
||||
'chords': '<span class="nochordline">'
|
||||
'When we’ve been there a thousand years,\n'
|
||||
'chords': 'When we’ve been there a thousand years,\n'
|
||||
'Bright shining as the sun,\n'
|
||||
'We’ve no less days to sing God’s praise\n'
|
||||
'Than when we first begun.</span>',
|
||||
'Than when we first begun.',
|
||||
'html': 'When we’ve been there a thousand years,\n'
|
||||
'Bright shining as the sun,\n'
|
||||
'We’ve no less days to sing God’s praise\n'
|
||||
|
|
|
@ -86,7 +86,7 @@ def projector_manager_mtdb(settings):
|
|||
def fake_pjlink():
|
||||
faker = FakePJLink()
|
||||
yield faker
|
||||
del(faker)
|
||||
del faker
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
|
|
|
@ -72,6 +72,7 @@ def main_window(state, settings, mocked_qapp):
|
|||
mocked_qapp.primaryScreen.return_value = mocked_screen
|
||||
ScreenList.create(mocked_qapp)
|
||||
mainwindow = MainWindow()
|
||||
mainwindow.activateWindow = MagicMock()
|
||||
yield mainwindow
|
||||
del mainwindow
|
||||
renderer_patcher.stop()
|
||||
|
@ -348,10 +349,10 @@ def test_mainwindow_configuration(main_window):
|
|||
# WHEN: you check the started functions
|
||||
|
||||
# THEN: the following registry functions should have been registered
|
||||
expected_service_list = ['settings', 'settings_thread', 'application', 'main_window', 'http_server',
|
||||
'authentication_token', 'settings_form', 'service_manager', 'theme_manager',
|
||||
'projector_manager']
|
||||
expected_functions_list = ['bootstrap_initialise', 'bootstrap_post_set_up', 'bootstrap_completion',
|
||||
expected_service_list = ['settings', 'settings_thread', 'application', 'main_window', 'settings_form',
|
||||
'service_manager', 'theme_manager', 'projector_manager', 'http_server',
|
||||
'authentication_token', 'web_socket_server']
|
||||
expected_functions_list = ['bootstrap_post_set_up', 'bootstrap_initialise', 'bootstrap_completion',
|
||||
'config_screen_changed', 'theme_change_global']
|
||||
assert list(Registry().service_list.keys()) == expected_service_list, \
|
||||
'The service list should have been {}'.format(Registry().service_list.keys())
|
||||
|
@ -579,8 +580,63 @@ def test_projector_manager_dock_unlocked(main_window_reduced):
|
|||
projector_dock.setFeatures.assert_called_with(7)
|
||||
|
||||
|
||||
@patch('openlp.core.ui.mainwindow.MainWindow.open_cmd_line_files')
|
||||
@patch('openlp.core.ui.mainwindow.MainWindow.set_view_mode')
|
||||
def test_load_settings_view_mode_default_mode(mocked_view_mode, main_window, settings):
|
||||
def test_show_cmd_line_args(mocked_view_mode, mocked_open_cmd_line_files, main_window, settings):
|
||||
"""Test that command line arguments are loaded on show"""
|
||||
# GIVEN: A newly opened OpenLP instance with some command line arguments
|
||||
main_window.application.args = ['-disable-web-security', 'new_file_name.osz']
|
||||
|
||||
# WHEN: MainWindow.show() is called
|
||||
main_window.show()
|
||||
|
||||
# THEN: The command line arguments are searched for a file name
|
||||
mocked_open_cmd_line_files.assert_called_once_with(main_window.application.args)
|
||||
|
||||
|
||||
@patch('openlp.core.ui.mainwindow.MainWindow.set_view_mode')
|
||||
def test_show_load_last_file(mocked_view_mode, main_window, settings):
|
||||
"""Test that the last file opened is loaded on show"""
|
||||
# GIVEN: A newly opened OpenLP instance with some command line arguments
|
||||
main_window.service_manager_contents.load_last_file = MagicMock()
|
||||
main_window.settings.setValue('core/auto open', True)
|
||||
|
||||
# WHEN: MainWindow.show() is called
|
||||
main_window.show()
|
||||
|
||||
# THEN: The command line arguments are searched for a file name
|
||||
main_window.service_manager_contents.load_last_file.assert_called_once_with()
|
||||
|
||||
|
||||
@patch('openlp.core.ui.mainwindow.MainWindow.set_view_mode')
|
||||
def test_load_settings_custom_layout(mocked_view_mode, main_window, settings):
|
||||
"""Test that the view mode is called with the correct parameters for default mode"""
|
||||
# GIVEN a newly opened OpenLP instance, mocked screens and settings for a valid window position
|
||||
# mock out some other calls in load_settings()
|
||||
main_window.control_splitter = MagicMock()
|
||||
main_window._live_controller = MagicMock()
|
||||
main_window._preview_controller = MagicMock()
|
||||
main_window.activateWindow = MagicMock()
|
||||
main_window.settings.setValue('core/view mode', 'default')
|
||||
main_window.settings.setValue('user interface/is preset layout', False)
|
||||
main_window.settings.setValue('user interface/show library', True)
|
||||
main_window.settings.setValue('user interface/show service', False)
|
||||
main_window.settings.setValue('user interface/show themes', True)
|
||||
main_window.settings.setValue('user interface/show projectors', False)
|
||||
main_window.settings.setValue('user interface/live panel', True)
|
||||
main_window.settings.setValue('user interface/preview panel', False)
|
||||
|
||||
# WHEN: we call to show method
|
||||
main_window.show()
|
||||
|
||||
# THEN:
|
||||
# The default mode should have been called.
|
||||
mocked_view_mode.assert_called_with(True, True, True, False, True, False)
|
||||
|
||||
|
||||
@patch('openlp.core.ui.mainwindow.QtWidgets.QWidget.show')
|
||||
@patch('openlp.core.ui.mainwindow.MainWindow.set_view_mode')
|
||||
def test_load_settings_view_mode_default_mode(mocked_view_mode, mocked_show, main_window, settings):
|
||||
"""
|
||||
Test that the view mode is called with the correct parameters for default mode
|
||||
"""
|
||||
|
@ -589,10 +645,11 @@ def test_load_settings_view_mode_default_mode(mocked_view_mode, main_window, set
|
|||
main_window.control_splitter = MagicMock()
|
||||
main_window._live_controller = MagicMock()
|
||||
main_window._preview_controller = MagicMock()
|
||||
main_window.activateWindow = MagicMock()
|
||||
main_window.settings.setValue('core/view mode', 'default')
|
||||
main_window.settings.setValue('user interface/is preset layout', True)
|
||||
|
||||
# WHENL we call to show method
|
||||
# WHEN: we call to show method
|
||||
main_window.show()
|
||||
|
||||
# THEN:
|
||||
|
@ -600,10 +657,14 @@ def test_load_settings_view_mode_default_mode(mocked_view_mode, main_window, set
|
|||
mocked_view_mode.assert_called_with(True, True, True, True, True, True)
|
||||
|
||||
|
||||
@patch('openlp.core.ui.mainwindow.QtWidgets.QWidget.show')
|
||||
@patch('openlp.core.ui.mainwindow.MainWindow.set_view_mode')
|
||||
def test_load_settings_view_mode_setup_mode(mocked_view_mode, main_window, settings):
|
||||
def test_load_settings_view_mode_setup_mode(mocked_view_mode, mocked_show, main_window, settings):
|
||||
"""
|
||||
Test that the view mode is called with the correct parameters for setup mode
|
||||
|
||||
Note: for some reason, only when the test is running, the QWidget.show() method resets the layout setting,
|
||||
so this is mocked out to prevent it from interferring in the loading of the view layout
|
||||
"""
|
||||
# GIVEN a newly opened OpenLP instance, mocked screens and settings for a valid window position
|
||||
# mock out some other calls in load_settings()
|
||||
|
@ -613,7 +674,7 @@ def test_load_settings_view_mode_setup_mode(mocked_view_mode, main_window, setti
|
|||
main_window.settings.setValue('core/view mode', 'setup')
|
||||
main_window.settings.setValue('user interface/is preset layout', True)
|
||||
|
||||
# WHENL we call to show method
|
||||
# WHEN: we call to show method
|
||||
main_window.show()
|
||||
|
||||
# THEN:
|
||||
|
@ -621,10 +682,14 @@ def test_load_settings_view_mode_setup_mode(mocked_view_mode, main_window, setti
|
|||
mocked_view_mode.assert_called_with(True, True, False, True, False, True)
|
||||
|
||||
|
||||
@patch('openlp.core.ui.mainwindow.QtWidgets.QWidget.show')
|
||||
@patch('openlp.core.ui.mainwindow.MainWindow.set_view_mode')
|
||||
def test_load_settings_view_mode_live_mode(mocked_view_mode, main_window, settings):
|
||||
def test_load_settings_view_mode_live_mode(mocked_view_mode, mocked_show, main_window, settings):
|
||||
"""
|
||||
Test that the view mode is called with the correct parameters for live mode
|
||||
|
||||
Note: for some reason, only when the test is running, the QWidget.show() method resets the layout setting,
|
||||
so this is mocked out to prevent it from interferring in the loading of the view layout
|
||||
"""
|
||||
# GIVEN a newly opened OpenLP instance, mocked screens and settings for a valid window position
|
||||
# mock out some other calls in load_settings()
|
||||
|
@ -634,7 +699,7 @@ def test_load_settings_view_mode_live_mode(mocked_view_mode, main_window, settin
|
|||
main_window.settings.setValue('core/view mode', 'live')
|
||||
main_window.settings.setValue('user interface/is preset layout', True)
|
||||
|
||||
# WHENL we call to show method
|
||||
# WHEN: we call to show method
|
||||
main_window.show()
|
||||
|
||||
# THEN:
|
||||
|
@ -652,11 +717,12 @@ def test_load_settings_view_mode_preview(mocked_view_mode, main_window, settings
|
|||
main_window.control_splitter = MagicMock()
|
||||
main_window._live_controller = MagicMock()
|
||||
main_window._preview_controller = MagicMock()
|
||||
main_window.activateWindow = MagicMock()
|
||||
main_window.settings.setValue('core/view mode', 'default')
|
||||
main_window.settings.setValue('user interface/is preset layout', False)
|
||||
main_window.settings.setValue('user interface/preview panel', False)
|
||||
|
||||
# WHENL we call to show method
|
||||
# WHEN: we call to show method
|
||||
main_window.show()
|
||||
|
||||
# THEN:
|
||||
|
@ -674,11 +740,12 @@ def test_load_settings_view_mode_live(mocked_view_mode, main_window, settings):
|
|||
main_window.control_splitter = MagicMock()
|
||||
main_window._live_controller = MagicMock()
|
||||
main_window._preview_controller = MagicMock()
|
||||
main_window.activateWindow = MagicMock()
|
||||
main_window.settings.setValue('core/view mode', 'default')
|
||||
main_window.settings.setValue('user interface/is preset layout', False)
|
||||
main_window.settings.setValue('user interface/live panel', False)
|
||||
|
||||
# WHENL we call to show method
|
||||
# WHEN: we call to show method
|
||||
main_window.show()
|
||||
|
||||
# THEN:
|
||||
|
|
|
@ -1267,6 +1267,83 @@ def test_load_service_modified_saved_with_file_path(registry):
|
|||
service_manager.load_file.assert_called_once_with(Path.home() / 'service.osz')
|
||||
|
||||
|
||||
@patch('openlp.core.ui.servicemanager.FileDialog.getOpenFileName')
|
||||
def test_load_service_no_file_path_passed_or_selected(mocked_get_open_file_name, mock_settings):
|
||||
"""Test that the load_service() method exits early when no file is passed and no file is selected in the dialog"""
|
||||
# GIVEN: A modified ServiceManager
|
||||
service_manager = ServiceManager(None)
|
||||
mocked_get_open_file_name.return_value = (None, None)
|
||||
|
||||
# WHEN: A service is loaded
|
||||
result = service_manager.load_service()
|
||||
|
||||
# THEN: The result should be False because of an early exit
|
||||
assert result is False, 'The method did not exit early'
|
||||
|
||||
|
||||
@patch('openlp.core.ui.servicemanager.FileDialog.getOpenFileName')
|
||||
def test_load_service_no_file_path_passed_file_selected(mocked_get_open_file_name, mock_settings):
|
||||
"""Test that the load_service() method loads a file chosen in the dialog when no file is passed"""
|
||||
# GIVEN: A modified ServiceManager
|
||||
service_manager = ServiceManager(None)
|
||||
mocked_get_open_file_name.return_value = (Path.home() / 'service.osz', None)
|
||||
service_manager.load_file = MagicMock()
|
||||
|
||||
# WHEN: A service is loaded
|
||||
service_manager.load_service()
|
||||
|
||||
# THEN: The service should be loaded
|
||||
service_manager.load_file.assert_called_once_with(Path.home() / 'service.osz')
|
||||
|
||||
|
||||
def test_on_recent_service_clicked_modified_cancel_save(registry):
|
||||
"""Test that the on_recent_service_clicked() method exits early when the service is modified,
|
||||
but the save is canceled"""
|
||||
# GIVEN: A modified ServiceManager
|
||||
service_manager = ServiceManager(None)
|
||||
service_manager.is_modified = MagicMock(return_value=True)
|
||||
service_manager.save_modified_service = MagicMock(return_value=QtWidgets.QMessageBox.Cancel)
|
||||
|
||||
# WHEN: on_recent_service_clicked is called
|
||||
result = service_manager.on_recent_service_clicked(True)
|
||||
|
||||
# THEN: The result should be False because of an early exit
|
||||
assert result is False, 'The method did not exit early'
|
||||
|
||||
|
||||
def test_on_recent_service_clicked_modified_saved_with_file_path(registry):
|
||||
"""Test that the on_recent_service_clicked() method saves the file and loads the file"""
|
||||
# GIVEN: A modified ServiceManager
|
||||
mocked_settings = MagicMock()
|
||||
registry.register('settings', mocked_settings)
|
||||
service_manager = ServiceManager(None)
|
||||
service_manager.is_modified = MagicMock(return_value=True)
|
||||
service_manager.save_modified_service = MagicMock(return_value=QtWidgets.QMessageBox.Save)
|
||||
service_manager.decide_save_method = MagicMock()
|
||||
service_manager.load_file = MagicMock()
|
||||
service_manager.sender = MagicMock(return_value=MagicMock())
|
||||
|
||||
# WHEN: on_recent_service_clicked is called
|
||||
service_manager.on_recent_service_clicked(True)
|
||||
|
||||
# THEN: The recent service should be loaded
|
||||
service_manager.decide_save_method.assert_called_once_with()
|
||||
service_manager.load_file.assert_called_once()
|
||||
|
||||
|
||||
def test_on_recent_service_clicked_unmodified(registry):
|
||||
# GIVEN: A modified ServiceManager
|
||||
service_manager = ServiceManager(None)
|
||||
service_manager.load_file = MagicMock()
|
||||
service_manager.sender = MagicMock(return_value=MagicMock())
|
||||
|
||||
# WHEN: on_recent_service_clicked is called
|
||||
service_manager.on_recent_service_clicked(True)
|
||||
|
||||
# THEN: The recent service should be loaded
|
||||
service_manager.load_file.assert_called_once()
|
||||
|
||||
|
||||
@patch('openlp.core.ui.servicemanager.Path', autospec=True)
|
||||
def test_service_manager_load_file_str(MockPath, registry):
|
||||
"""Test the service manager's load_file method when it is given a str"""
|
||||
|
|
|
@ -32,6 +32,7 @@ from PyQt5 import QtWidgets
|
|||
|
||||
from openlp.core.common.registry import Registry
|
||||
from openlp.core.common.settings import Settings
|
||||
from openlp.core.lib.theme import Theme
|
||||
from openlp.core.ui.thememanager import ThemeManager
|
||||
from tests.utils.constants import RESOURCE_PATH
|
||||
|
||||
|
@ -265,7 +266,7 @@ def test_save_theme_missing_new(mocked_paths, mocked_delete, mocked_log_warning,
|
|||
theme_manager.save_theme(mocked_theme)
|
||||
|
||||
# THEN: A warning should have happened due to attempting to copy a missing file
|
||||
mocked_log_warning.assert_called_once_with('Background does not exist, retaining cached background')
|
||||
mocked_log_warning.assert_called_once_with('Background source does not exist, retaining cached background')
|
||||
|
||||
|
||||
@patch('openlp.core.ui.thememanager.shutil')
|
||||
|
@ -292,7 +293,7 @@ def test_save_theme_background_override(mocked_paths, mocked_delete, mocked_shut
|
|||
override_background = MagicMock()
|
||||
|
||||
# WHEN: Calling save_theme with a background override
|
||||
theme_manager.save_theme(mocked_theme, background_override=override_background)
|
||||
theme_manager.save_theme(mocked_theme, background_file=override_background)
|
||||
|
||||
# THEN: The override_background should have been copied rather than the background_source
|
||||
mocked_shutil.copyfile.assert_called_once_with(override_background, mocked_theme.background_filename)
|
||||
|
@ -506,3 +507,27 @@ def test_bootstrap_post(mocked_rename_form, mocked_theme_form, theme_manager):
|
|||
assert theme_manager.file_rename_form is not None
|
||||
theme_manager.upgrade_themes.assert_called_once()
|
||||
theme_manager.load_themes.assert_called_once()
|
||||
|
||||
|
||||
def test_clone_theme_data(theme_manager):
|
||||
"""Test that cloning the theme data works correctly"""
|
||||
# GIVEN: A theme manager, a theme (without a background source) and a new theme name
|
||||
existing_theme = Theme()
|
||||
existing_theme.theme_name = 'Existing Theme'
|
||||
existing_theme.background_type = 'image'
|
||||
existing_theme.background_filename = Path('Existing Theme', 'background.jpg')
|
||||
|
||||
theme_manager.theme_path = Path('openlp', 'themes')
|
||||
theme_manager.save_theme = MagicMock()
|
||||
theme_manager.update_preview_images = MagicMock()
|
||||
theme_manager.load_themes = MagicMock()
|
||||
|
||||
# WHEN: The theme is cloned
|
||||
theme_manager.clone_theme_data(existing_theme, 'New Theme')
|
||||
|
||||
# THEN: The theme data should have been updated
|
||||
assert existing_theme.theme_name == 'New Theme'
|
||||
theme_manager.save_theme.assert_called_once_with(existing_theme, background_file=Path('Existing Theme',
|
||||
'background.jpg'))
|
||||
theme_manager.update_preview_images.assert_called_once_with(['New Theme'])
|
||||
theme_manager.load_themes.assert_called_once_with()
|
||||
|
|
|
@ -31,6 +31,7 @@ from tests.utils.constants import RESOURCE_PATH
|
|||
TEST_PATH = RESOURCE_PATH / 'bibles'
|
||||
INDEX_PAGE = (TEST_PATH / 'wordproject_index.htm').read_bytes().decode()
|
||||
CHAPTER_PAGE = (TEST_PATH / 'wordproject_chapter.htm').read_bytes().decode()
|
||||
CORRUPTED_CHAPTER_PAGE = (TEST_PATH / 'wordproject_chapter_corrupted.htm').read_bytes().decode()
|
||||
|
||||
|
||||
@patch.object(Path, 'read_text')
|
||||
|
@ -91,6 +92,35 @@ def test_process_chapters(mocked_read_text, settings):
|
|||
assert mocked_process_verses.call_args_list == expected_process_verses_calls
|
||||
|
||||
|
||||
@patch.object(Path, 'read_text')
|
||||
def test_process_chapters_corrupted_header(mocked_read_text, settings):
|
||||
"""
|
||||
Test the process_chapters() method when there's a "corrupted" header with a span instead of a p
|
||||
"""
|
||||
# GIVEN: A WordProject importer and a bunch of mocked things
|
||||
importer = WordProjectBible(MagicMock(), path='.', name='.', file_path=Path('kj.zip'))
|
||||
importer.base_path = Path()
|
||||
importer.stop_import_flag = False
|
||||
importer.language_id = 'en'
|
||||
mocked_read_text.return_value = CORRUPTED_CHAPTER_PAGE
|
||||
mocked_db_book = MagicMock()
|
||||
mocked_db_book.name = 'Genesis'
|
||||
book_id = 1
|
||||
book_link = '01/1.htm'
|
||||
|
||||
# WHEN: process_chapters() is called
|
||||
with patch.object(importer, 'set_current_chapter') as mocked_set_current_chapter, \
|
||||
patch.object(importer, 'process_verses') as mocked_process_verses:
|
||||
importer.process_chapters(mocked_db_book, book_id, book_link)
|
||||
|
||||
# THEN: The right methods should have been called
|
||||
expected_set_current_chapter_calls = [call('Genesis', ch) for ch in range(1, 51)]
|
||||
expected_process_verses_calls = [call(mocked_db_book, 1, ch) for ch in range(1, 51)]
|
||||
mocked_read_text.assert_called_once_with(encoding='utf-8', errors='ignore')
|
||||
assert mocked_set_current_chapter.call_args_list == expected_set_current_chapter_calls
|
||||
assert mocked_process_verses.call_args_list == expected_process_verses_calls
|
||||
|
||||
|
||||
@patch.object(Path, 'read_text')
|
||||
def test_process_verses(mocked_read_text, settings):
|
||||
"""
|
||||
|
|
|
@ -159,6 +159,23 @@ class TestMacLOController(TestCase, TestMixin):
|
|||
controller._client.shutdown.assert_called_once_with()
|
||||
controller.server_process.kill.assert_called_once_with()
|
||||
|
||||
@patch('openlp.plugins.presentations.lib.maclocontroller.MacLOController._start_server')
|
||||
def test_kill_client_already_closed(self, mocked_start_server):
|
||||
"""
|
||||
Test the kill() method when the client is already closed
|
||||
"""
|
||||
# GIVEN: A controller and a client
|
||||
controller = MacLOController(plugin=self.mock_plugin)
|
||||
controller._client = MagicMock(**{'shutdown.side_effect': Exception})
|
||||
controller.server_process = MagicMock()
|
||||
|
||||
# WHEN: start_process() is called
|
||||
controller.kill()
|
||||
|
||||
# THEN: The client's start_process() should have been called
|
||||
controller._client.shutdown.assert_called_once_with()
|
||||
controller.server_process.kill.assert_called_once_with()
|
||||
|
||||
|
||||
class TestMacLODocument(TestCase):
|
||||
"""
|
||||
|
|
|
@ -24,8 +24,11 @@ This module contains tests for the lib submodule of the Presentations plugin.
|
|||
from pathlib import Path
|
||||
from unittest.mock import MagicMock, PropertyMock, call, patch
|
||||
|
||||
from PyQt5 import QtCore, QtWidgets
|
||||
|
||||
from openlp.core.lib import ServiceItemContext
|
||||
from openlp.core.lib.serviceitem import ItemCapabilities
|
||||
from openlp.plugins.presentations.lib.db import Folder, Item
|
||||
from openlp.plugins.presentations.lib.mediaitem import PresentationMediaItem
|
||||
|
||||
|
||||
|
@ -128,7 +131,60 @@ def test_clean_up_thumbnails_missing_file(media_item):
|
|||
mocked_doc.assert_has_calls([call.get_thumbnail_path(1, True), call.presentation_deleted()], True)
|
||||
|
||||
|
||||
def test_pdf_generate_slide_data(media_item):
|
||||
def test_generate_slide_data_from_folder(media_item):
|
||||
"""
|
||||
Test that the generate slide data function exits early when the item is a Folder instead of an Item
|
||||
"""
|
||||
# GIVEN: A Folder instance
|
||||
media_item.list_view = MagicMock()
|
||||
mocked_service_item = MagicMock()
|
||||
folder = Folder(id=1, name='Mock folder')
|
||||
list_item = QtWidgets.QTreeWidgetItem(None)
|
||||
list_item.setData(0, QtCore.Qt.UserRole, folder)
|
||||
|
||||
# WHEN: generate_slide_data is called
|
||||
result = media_item.generate_slide_data(mocked_service_item, item=list_item)
|
||||
|
||||
# THEN: The result should be false
|
||||
assert result is False
|
||||
|
||||
|
||||
def test_generate_slide_data_from_list_view(media_item):
|
||||
"""
|
||||
Test that the generate slide data function exits early when there are more than 1 items selected
|
||||
"""
|
||||
# GIVEN: A Folder instance
|
||||
mocked_service_item = MagicMock()
|
||||
list_item = QtWidgets.QTreeWidgetItem(None)
|
||||
media_item.list_view = MagicMock(selectedItems=MagicMock(return_value=[list_item, list_item]))
|
||||
|
||||
# WHEN: generate_slide_data is called
|
||||
result = media_item.generate_slide_data(mocked_service_item)
|
||||
|
||||
# THEN: The result should be false
|
||||
assert result is False
|
||||
|
||||
|
||||
def test_generate_slide_data_with_file_path_from_item(media_item):
|
||||
"""
|
||||
Test that the generate slide data function exits early when there is no display type combobox text
|
||||
"""
|
||||
# GIVEN: A Folder instance
|
||||
media_item.list_view = MagicMock()
|
||||
media_item.display_type_combo_box = MagicMock(currentText=MagicMock(return_value=''))
|
||||
mocked_service_item = MagicMock()
|
||||
item = Item(id=1, file_path='path/to/presentation.odp')
|
||||
list_item = QtWidgets.QTreeWidgetItem(None)
|
||||
list_item.setData(0, QtCore.Qt.UserRole, item)
|
||||
|
||||
# WHEN: generate_slide_data is called
|
||||
result = media_item.generate_slide_data(mocked_service_item, item=list_item)
|
||||
|
||||
# THEN: The result should be false
|
||||
assert result is False
|
||||
|
||||
|
||||
def test_generate_slide_data_from_pdf(media_item):
|
||||
"""
|
||||
Test that the generate slide data function makes the correct ajustments to a pdf service item.
|
||||
"""
|
||||
|
|
|
@ -24,7 +24,7 @@ Package to test the openlp.plugins.songs.forms.songmaintenanceform package.
|
|||
import pytest
|
||||
import os
|
||||
|
||||
from unittest.mock import MagicMock, call, patch, ANY
|
||||
from unittest.mock import MagicMock, call, create_autospec, patch, ANY
|
||||
|
||||
from PyQt5 import QtCore, QtWidgets
|
||||
|
||||
|
@ -208,10 +208,43 @@ def test_delete_item(mocked_critical_error_message_box, form_env):
|
|||
mocked_get_current_item_id.assert_called_once_with(mocked_list_widget)
|
||||
mocked_manager.get_object.assert_called_once_with(mocked_item_class, 1)
|
||||
mocked_critical_error_message_box.assert_called_once_with(dialog_title, delete_text, form, True)
|
||||
mocked_manager.delete_object(mocked_item_class, 1)
|
||||
mocked_manager.delete_object.assert_called_once_with(mocked_item_class, 1)
|
||||
mocked_reset_func.assert_called_once_with()
|
||||
|
||||
|
||||
@patch('openlp.plugins.songs.forms.songmaintenanceform.critical_error_message_box')
|
||||
def test_delete_book_assigned(mocked_critical_error_message_box, form_env):
|
||||
"""
|
||||
Test the _delete_item() method
|
||||
"""
|
||||
# GIVEN: Some mocked items
|
||||
form = form_env[0]
|
||||
mocked_manager = form_env[1]
|
||||
mocked_item = create_autospec(Book, spec_set=True)
|
||||
mocked_item.id = 1
|
||||
mocked_manager.get_object.return_value = mocked_item
|
||||
mocked_critical_error_message_box.return_value = QtWidgets.QMessageBox.Yes
|
||||
mocked_item_class = MagicMock()
|
||||
mocked_list_widget = MagicMock()
|
||||
mocked_reset_func = MagicMock()
|
||||
dialog_title = 'Delete Book'
|
||||
delete_text = 'Are you sure you want to delete the selected book?'
|
||||
error_text = 'This book cannot be deleted, it is currenty assigned to at least one song.'
|
||||
|
||||
# WHEN: _delete_item() is called
|
||||
with patch.object(form, '_get_current_item_id') as mocked_get_current_item_id:
|
||||
mocked_get_current_item_id.return_value = 1
|
||||
form._delete_item(mocked_item_class, mocked_list_widget, mocked_reset_func, dialog_title, delete_text,
|
||||
error_text)
|
||||
|
||||
# THEN: The right things should have been called
|
||||
mocked_get_current_item_id.assert_called_once_with(mocked_list_widget)
|
||||
mocked_manager.get_object.assert_called_once_with(mocked_item_class, 1)
|
||||
mocked_critical_error_message_box.assert_called_once_with(dialog_title, error_text)
|
||||
mocked_manager.delete_object.assert_not_called()
|
||||
mocked_reset_func.assert_not_called()
|
||||
|
||||
|
||||
@patch('openlp.plugins.songs.forms.songmaintenanceform.QtWidgets.QListWidgetItem')
|
||||
@patch('openlp.plugins.songs.forms.songmaintenanceform.Author')
|
||||
def test_reset_authors(MockedAuthor, MockedQListWidgetItem, form_env):
|
||||
|
|
|
@ -23,6 +23,8 @@ This module contains tests for the ZionWorx song importer.
|
|||
"""
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from openlp.plugins.songs.lib.importers.songimport import SongImport
|
||||
from openlp.plugins.songs.lib.importers.zionworx import ZionWorxImport
|
||||
from tests.helpers.songfileimport import SongImportTestHelper
|
||||
|
@ -47,10 +49,11 @@ def test_create_importer(registry):
|
|||
assert isinstance(importer, SongImport)
|
||||
|
||||
|
||||
def test_zion_wrox(mock_settings):
|
||||
|
||||
@pytest.mark.parametrize('filebase', ['zionworx', 'amazing-grace-arabic'])
|
||||
def test_zion_wrox(filebase, mock_settings):
|
||||
"""Test that the ZionWorx importer correctly imports songs"""
|
||||
test_file_import = SongImportTestHelper('ZionWorxImport', 'zionworx')
|
||||
test_file_import.setUp()
|
||||
test_file_import.file_import(TEST_PATH / 'zionworx.csv',
|
||||
test_file_import.load_external_result_data(TEST_PATH / 'zionworx.json'))
|
||||
test_file_import.file_import(TEST_PATH / f'{filebase}.csv',
|
||||
test_file_import.load_external_result_data(TEST_PATH / f'{filebase}.json'))
|
||||
test_file_import.tearDown()
|
||||
|
|
|
@ -0,0 +1,248 @@
|
|||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<title>Creation of the world, Genesis Chapter 1</title>
|
||||
<meta name="description" content="Creation of the world, Genesis Chapter 1" />
|
||||
<meta name="keywords" content="Holy Bible, Old Testament, scriptures, Creation, faith, heaven, hell, God, Jesus" />
|
||||
<!-- Mobile viewport optimisation -->
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="../_assets/css/css.css" />
|
||||
<link rel="stylesheet" type="text/css" href="../_assets/css/style.css" />
|
||||
<link rel="stylesheet" type="text/css" href="../_assets/css/page-player.css" />
|
||||
|
||||
<!--[if lte IE 7]>
|
||||
<link href="../_assets/css/iehacks.css" rel="stylesheet" type="text/css" />
|
||||
<![endif]-->
|
||||
<!-- google analytics -->
|
||||
<script type="text/javascript">
|
||||
|
||||
var _gaq = _gaq || [];
|
||||
_gaq.push(['_setAccount', 'UA-39700598-1']);
|
||||
_gaq.push(['_trackPageview']);
|
||||
|
||||
(function() {
|
||||
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
|
||||
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
|
||||
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
|
||||
})();
|
||||
|
||||
</script>
|
||||
|
||||
<!--[if lt IE 9]>
|
||||
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
|
||||
<![endif]-->
|
||||
<!-- google analytics -->
|
||||
<script type="text/javascript">
|
||||
|
||||
var _gaq = _gaq || [];
|
||||
_gaq.push(['_setAccount', 'UA-39700598-1']);
|
||||
_gaq.push(['_trackPageview']);
|
||||
|
||||
(function() {
|
||||
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
|
||||
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
|
||||
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
|
||||
})();
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
</head>
|
||||
<a name="mytop"></a>
|
||||
<body>
|
||||
<header class="ym-noprint">
|
||||
<div class="ym-wrapper">
|
||||
<div class="ym-wbox">
|
||||
<h1><strong>Word</strong><em>Project</em></h1>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<!--lang nav-->
|
||||
<!--nav id="nav">
|
||||
<div class="ym-wrapper">
|
||||
<div class="ym-hlist">
|
||||
<ul>
|
||||
<li><a title="Home" href="../../../index.htm" target="_top">Home</a></li>
|
||||
<li class="active"><a title="Bibles" href="../../../bibles/index.htm" target="_self">Bibles</a></li>
|
||||
<li><a title="Audio Bible" href="../../../bibles/audio/01_english/b01.htm" target="_top">Audio</a></li>
|
||||
<li><a title="Selected Bible Verses" href="../../../bibles/verses/english/index.htm" target="_top">Verses</a></li>
|
||||
<li><a title="Parallel Bibles" href="../../../bibles/parallel/index.htm" target="_top">Multi</a></li>
|
||||
<li><a title="Resourcces" href="../../../bibles/resources/index.htm" target="_top">Resources</a></li>
|
||||
<li><a title="Search" href="../../../bibles/search/index.htm" target="_top">Search</a></li>
|
||||
<li><a title="Download this Bible [language]" href="../../../download/bibles/index.htm" target="_top">Download</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav-->
|
||||
<div class="ym-wrapper ym-noprint">
|
||||
<div class="ym-wbox">
|
||||
<!--share buttons-->
|
||||
<div style="margin: 10px 1px 5px 20px;" align="right">
|
||||
<!-- Facebook -->
|
||||
<a title="Click to share on Facebook" href="http://www.facebook.com/sharer.php?u=http://wordproject.org/bibles/kj/01/1.htm" target="_blank"><img src="../_assets/img/facebook_2.png" alt="facebook" /></a>
|
||||
<!-- Twitter -->
|
||||
<a title="Click to share on Twitter" href="http://twitter.com/share?url=http://wordproject.org/bibles/kj/01/1.htm&text=Read this page &hashtags=wordproject" target="_blank"><img src="../_assets/img/twitter_2.png" alt="twitter" /></a>
|
||||
<!-- Google+ -->
|
||||
<a title="Click to share on Google plus" href="https://plus.google.com/share?url=http://wordproject.org/bibles/kj/01/1.htm" target="_blank"><img src="../_assets/img/google+_2.png" alt="google" /></a>
|
||||
<!-- LinkedIn -->
|
||||
<a title="Click to share on Linkedin" href="http://www.linkedin.com/shareArticle?mini=true&url=http://www.wordproject.org" target="_blank"><img src="../_assets/img/linkin_2.png" alt="linkin" /></a></p>
|
||||
</div>
|
||||
<!--/share buttons-->
|
||||
<div class=" ym-grid">
|
||||
<div class="ym-g62 ym-gl breadCrumbs"> <!--a title="Home" href="http://www.wordproject.org/index.htm" target="_top">Home</a> / <a title="Bibles" href="../../index.htm" target="_self">Bibles</a--> / <a href="../index.htm" target="_self">KJV</a></div>
|
||||
<div class="ym-g38 ym-gr alignRight ym-noprint"><a class="decreaseFont ym-button">-</a><a class="resetFont ym-button">Reset</a><a class="increaseFont ym-button">+</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="main" class="ym-clearfix" role="main">
|
||||
<div class="ym-wrapper">
|
||||
<div class="ym-wbox">
|
||||
<div class="textOptions">
|
||||
<div class="textHeader">
|
||||
<h2>Genesis</h2>
|
||||
<a name="0"></a>
|
||||
<span class="ym-noprint"> Chapter:
|
||||
|
||||
<span class="c1">1</span>
|
||||
<a href="2.htm#0">2</a>
|
||||
<a href="3.htm#0">3</a>
|
||||
<a href="4.htm#0">4</a>
|
||||
<a href="5.htm#0">5</a>
|
||||
<a href="6.htm#0">6</a>
|
||||
<a href="7.htm#0">7</a>
|
||||
<a href="8.htm#0">8</a>
|
||||
<a href="9.htm#0">9</a>
|
||||
<a href="10.htm#0">10</a>
|
||||
<a href="11.htm#0">11</a>
|
||||
<a href="12.htm#0">12</a>
|
||||
<a href="13.htm#0">13</a>
|
||||
<a href="14.htm#0">14</a>
|
||||
<a href="15.htm#0">15</a>
|
||||
<a href="16.htm#0">16</a>
|
||||
<a href="17.htm#0">17</a>
|
||||
<a href="18.htm#0">18</a>
|
||||
<a href="19.htm#0">19</a>
|
||||
<a href="20.htm#0">20</a>
|
||||
<a href="21.htm#0">21</a>
|
||||
<a href="22.htm#0">22</a>
|
||||
<a href="23.htm#0">23</a>
|
||||
<a href="24.htm#0">24</a>
|
||||
<a href="25.htm#0">25</a>
|
||||
<a href="26.htm#0">26</a>
|
||||
<a href="27.htm#0">27</a>
|
||||
<a href="28.htm#0">28</a>
|
||||
<a href="29.htm#0">29</a>
|
||||
<a href="30.htm#0">30</a>
|
||||
<a href="31.htm#0">31</a>
|
||||
<a href="32.htm#0">32</a>
|
||||
<a href="33.htm#0">33</a>
|
||||
<a href="34.htm#0">34</a>
|
||||
<a href="35.htm#0">35</a>
|
||||
<a href="36.htm#0">36</a>
|
||||
<a href="37.htm#0">37</a>
|
||||
<a href="38.htm#0">38</a>
|
||||
<a href="39.htm#0">39</a>
|
||||
<a href="40.htm#0">40</a>
|
||||
<a href="41.htm#0">41</a>
|
||||
<a href="42.htm#0">42</a>
|
||||
<a href="43.htm#0">43</a>
|
||||
<a href="44.htm#0">44</a>
|
||||
<a href="45.htm#0">45</a>
|
||||
<a href="46.htm#0">46</a>
|
||||
<a href="47.htm#0">47</a>
|
||||
<a href="48.htm#0">48</a>
|
||||
<a href="49.htm#0">49</a>
|
||||
<a href="50.htm#0">50</a>
|
||||
<!--end of chapters-->
|
||||
</p>
|
||||
</div>
|
||||
<div class="textAudio ym-noprint"><ul class="playlist">
|
||||
<li class="noMargin">
|
||||
<!--start audio link--><a href="http://audio2.wordproject.com/bibles/app/audio/1/1/1.mp3">Genesis - Chapter 1 </a></li><!--/audioRef-->
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!--end audio-->
|
||||
<hr />
|
||||
<div class="textBody" id="textBody">
|
||||
<h3>Chapter 1</h3>
|
||||
|
||||
<!--... the Word of God:--></a>
|
||||
<p><span class="verse" id="1">1</span> In the beginning God created the heaven and the earth.
|
||||
<br /><span class="verse" id="2">2</span> And the earth was without form, and void; and darkness was upon the face of the deep. And the Spirit of God moved upon the face of the waters.
|
||||
<br /><span class="verse" id="3">3</span> And God said, Let there be light: and there was light.
|
||||
<br /><span class="verse" id="4">4</span> And God saw the light, that it was good: and God divided the light from the darkness.
|
||||
<br /><span class="verse" id="5">5</span> And God called the light Day, and the darkness he called Night. And the evening and the morning were the first day.
|
||||
<br /><span class="verse" id="6">6</span> And God said, Let there be a firmament in the midst of the waters, and let it divide the waters from the waters.
|
||||
<br /><span class="verse" id="7">7</span> And God made the firmament, and divided the waters which were under the firmament from the waters which were above the firmament: and it was so.
|
||||
<br /><span class="verse" id="8">8</span> And God called the firmament Heaven. And the evening and the morning were the second day.
|
||||
<br /><span class="verse" id="9">9</span> And God said, Let the waters under the heaven be gathered together unto one place, and let the dry land appear: and it was so.
|
||||
<br /><span class="verse" id="10">10</span> And God called the dry land Earth; and the gathering together of the waters called he Seas: and God saw that it was good.
|
||||
<br /><span class="verse" id="11">11</span> And God said, Let the earth bring forth grass, the herb yielding seed, and the fruit tree yielding fruit after his kind, whose seed is in itself, upon the earth: and it was so.
|
||||
<br /><span class="verse" id="12">12</span> And the earth brought forth grass, and herb yielding seed after his kind, and the tree yielding fruit, whose seed was in itself, after his kind: and God saw that it was good.
|
||||
<br /><span class="verse" id="13">13</span> And the evening and the morning were the third day.
|
||||
<br /><span class="verse" id="14">14</span> And God said, Let there be lights in the firmament of the heaven to divide the day from the night; and let them be for signs, and for seasons, and for days, and years:
|
||||
<br /><span class="verse" id="15">15</span> And let them be for lights in the firmament of the heaven to give light upon the earth: and it was so.
|
||||
<br /><span class="verse" id="16">16</span> And God made two great lights; the greater light to rule the day, and the lesser light to rule the night: he made the stars also.
|
||||
<br /><span class="verse" id="17">17</span> And God set them in the firmament of the heaven to give light upon the earth,
|
||||
<br /><span class="verse" id="18">18</span> And to rule over the day and over the night, and to divide the light from the darkness: and God saw that it was good.
|
||||
<br /><span class="verse" id="19">19</span> And the evening and the morning were the fourth day.
|
||||
<br /><span class="verse" id="20">20</span> And God said, Let the waters bring forth abundantly the moving creature that hath life, and fowl that may fly above the earth in the open firmament of heaven.
|
||||
<br /><span class="verse" id="21">21</span> And God created great whales, and every living creature that moveth, which the waters brought forth abundantly, after their kind, and every winged fowl after his kind: and God saw that it was good.
|
||||
<br /><span class="verse" id="22">22</span> And God blessed them, saying, Be fruitful, and multiply, and fill the waters in the seas, and let fowl multiply in the earth.
|
||||
<br /><span class="verse" id="23">23</span> And the evening and the morning were the fifth day.
|
||||
<br /><span class="verse" id="24">24</span> And God said, Let the earth bring forth the living creature after his kind, cattle, and creeping thing, and beast of the earth after his kind: and it was so.
|
||||
<br /><span class="verse" id="25">25</span> And God made the beast of the earth after his kind, and cattle after their kind, and every thing that creepeth upon the earth after his kind: and God saw that it was good.
|
||||
<br /><span class="verse" id="26">26</span> And God said, Let us make man in our image, after our likeness: and let them have dominion over the fish of the sea, and over the fowl of the air, and over the cattle, and over all the earth, and over every creeping thing that creepeth upon the earth.
|
||||
<br /><span class="verse" id="27">27</span> So God created man in his own image, in the image of God created he him; male and female created he them.
|
||||
<br /><span class="verse" id="28">28</span> And God blessed them, and God said unto them, Be fruitful, and multiply, and replenish the earth, and subdue it: and have dominion over the fish of the sea, and over the fowl of the air, and over every living thing that moveth upon the earth.
|
||||
<br /><span class="verse" id="29">29</span> And God said, Behold, I have given you every herb bearing seed, which is upon the face of all the earth, and every tree, in the which is the fruit of a tree yielding seed; to you it shall be for meat.
|
||||
<br /><span class="verse" id="30">30</span> And to every beast of the earth, and to every fowl of the air, and to every thing that creepeth upon the earth, wherein there is life, I have given every green herb for meat: and it was so.
|
||||
<br /><span class="verse" id="31">31</span> And God saw every thing that he had made, and, behold, it was very good. And the evening and the morning were the sixth day.
|
||||
</p>
|
||||
<!--... sharper than any twoedged sword... -->
|
||||
</div>
|
||||
</div><!-- ym-wbox end -->
|
||||
</div><!-- ym-wrapper end -->
|
||||
</div><!-- ym-wrapper end -->
|
||||
</div><!-- ym-wrapper end -->
|
||||
<!--..sharper than any twoedged sword...-->
|
||||
<div class="ym-wrapper">
|
||||
<div class="ym-wbox">
|
||||
<div class="alignRight ym-noprint">
|
||||
<p><a title="Print this page" href="javascript:window.print()" class="ym-button"> <img src="../_assets/img/printer.gif" alt="printer" width="25" height="25" align="absbottom" /> </a>
|
||||
<a class="ym-button" title="Page TOP" href="#mytop"> <img src="../_assets/img/arrow_up.png" alt="arrowup" width="25" height="25" align="absbottom" /> </a>
|
||||
<!--next chapter start-->
|
||||
<a class="ym-button" title="Next chapter" href="2.htm#0"> <img src="../_assets/img/arrow_right.png" alt="arrowright" align="absbottom" /> </a></p>
|
||||
<!--next chapter end-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<footer>
|
||||
<div class="ym-wrapper">
|
||||
<div class="ym-wbox">
|
||||
<p class="alignCenter">Wordproject® is a registered name of the <a href="http://www.wordproject.org">International Biblical Association</a>, a non-profit organization registered in Macau, China. </p>
|
||||
<p class="alignCenter"><a href="http://www.wordproject.org/contact/new/index.htm" target="_top">Contact</a> | <a href="http://www.wordproject.org/contact/new/disclaim.htm" target="_top"> Disclaimer</a> |
|
||||
<a href="http://www.wordproject.org/contact/new/state.htm" target="_top">Statement of Faith</a> |
|
||||
<a href="http://www.wordproject.org/contact/new/mstate.htm" target="_top">Mission</a> |
|
||||
<a href="http://www.wordproject.org/contact/new/copyrights.htm" target="_top">Copyrights</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
</script><script type="text/javascript" src="../_assets/js/jquery-1.8.0.min.js"></script>
|
||||
<script type="text/javascript" src="../_assets/js/soundmanager2.js"></script>
|
||||
<script type="text/javascript" src="../_assets/js/page-player.js"></script>
|
||||
<script type="text/javascript" src="../_assets/js/script.js"></script>
|
||||
<script type="text/javascript">
|
||||
|
||||
soundManager.setup({
|
||||
url: '../_assets/swf/'
|
||||
});
|
||||
|
||||
</script>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
"1","النعمة المذهلة",,"النعمة المذهلة
|
||||
كم هو شديَ ذلك الصوت
|
||||
الذي أنقذ شخصا تعيسا مثلي !
|
||||
كنت يوما ضائعا ، أما الآن فقد وُجدت
|
||||
كنت أعمى ، أما الآن فأنا أرى
|
||||
|
||||
إن النعمة هي التي علمت قلبي الخوف
|
||||
وإن النعمة هي التي أزالت مخاوفي
|
||||
كم بدت لي تلك النعمة ثمينة
|
||||
ساعة ما آمنت لأول مرة
|
||||
|
||||
بكثير من المخاطر والمحن والفتن مررت
|
||||
وها أنا ذا قد اجتزتها
|
||||
إن النعمة هي التي ساقتني آمنا حتى هنا
|
||||
وإن النعمة هي التي ستقودني حتى البيت
|
||||
|
||||
حتى لو مكثنا هنا عشرة ألف عام
|
||||
نشع بمثل سطوع الشمس
|
||||
لن ينقص ذلك من الأيام التي علينا أن نغني فيها تمجيدا للرب
|
||||
ولا يوما واحدا منذ ابتدينا","John Newton","Public Domain",,
|
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"authors": [
|
||||
"John Newton"
|
||||
],
|
||||
"copyright": "Public Domain",
|
||||
"title": "النعمة المذهلة",
|
||||
"verse_order_list": [],
|
||||
"verses": [
|
||||
[
|
||||
"النعمة المذهلة\nكم هو شديَ ذلك الصوت\nالذي أنقذ شخصا تعيسا مثلي !\nكنت يوما ضائعا ، أما الآن فقد وُجدت\nكنت أعمى ، أما الآن فأنا أرى\n",
|
||||
"v"
|
||||
],
|
||||
[
|
||||
"إن النعمة هي التي علمت قلبي الخوف\nوإن النعمة هي التي أزالت مخاوفي\nكم بدت لي تلك النعمة ثمينة\nساعة ما آمنت لأول مرة\n",
|
||||
"v"
|
||||
],
|
||||
[
|
||||
"بكثير من المخاطر والمحن والفتن مررت\nوها أنا ذا قد اجتزتها\nإن النعمة هي التي ساقتني آمنا حتى هنا\nوإن النعمة هي التي ستقودني حتى البيت\n",
|
||||
"v"
|
||||
],
|
||||
[
|
||||
"حتى لو مكثنا هنا عشرة ألف عام\nنشع بمثل سطوع الشمس\nلن ينقص ذلك من الأيام التي علينا أن نغني فيها تمجيدا للرب\nولا يوما واحدا منذ ابتدينا\n",
|
||||
"v"
|
||||
]
|
||||
]
|
||||
}
|
Loading…
Reference in New Issue