- Merged trunk on 11.5.16

This commit is contained in:
suutari-olli 2016-05-11 18:37:58 +03:00
commit 7e5b49ffdf
11 changed files with 208 additions and 89 deletions

View File

@ -247,7 +247,7 @@ class MainDisplay(OpenLPMixin, Display, RegistryProperties):
""" """
Set up and build the output screen Set up and build the output screen
""" """
self.log_debug('Start MainDisplay setup (live = %s)' % self.is_live) self.log_debug('Start MainDisplay setup (live = {islive})'.format(islive=self.is_live))
self.screen = self.screens.current self.screen = self.screens.current
self.setVisible(False) self.setVisible(False)
Display.setup(self) Display.setup(self)
@ -288,7 +288,9 @@ class MainDisplay(OpenLPMixin, Display, RegistryProperties):
self.application.process_events() self.application.process_events()
self.setGeometry(self.screen['size']) self.setGeometry(self.screen['size'])
if animate: if animate:
self.frame.evaluateJavaScript('show_text("%s")' % slide.replace('\\', '\\\\').replace('\"', '\\\"')) # NOTE: Verify this works with ''.format()
_text = slide.replace('\\', '\\\\').replace('\"', '\\\"')
self.frame.evaluateJavaScript('show_text("{text}")'.format(text=_text))
else: else:
# This exists for https://bugs.launchpad.net/openlp/+bug/1016843 # This exists for https://bugs.launchpad.net/openlp/+bug/1016843
# For unknown reasons if evaluateJavaScript is called # For unknown reasons if evaluateJavaScript is called
@ -309,10 +311,10 @@ class MainDisplay(OpenLPMixin, Display, RegistryProperties):
text_prepared = expand_tags(html.escape(text)).replace('\\', '\\\\').replace('\"', '\\\"') text_prepared = expand_tags(html.escape(text)).replace('\\', '\\\\').replace('\"', '\\\"')
if self.height() != self.screen['size'].height() or not self.isVisible(): if self.height() != self.screen['size'].height() or not self.isVisible():
shrink = True shrink = True
js = 'show_alert("%s", "%s")' % (text_prepared, 'top') js = 'show_alert("{text}", "{top}")'.format(text=text_prepared, top='top')
else: else:
shrink = False shrink = False
js = 'show_alert("%s", "")' % text_prepared js = 'show_alert("{text}", "")'.format(text=text_prepared)
height = self.frame.evaluateJavaScript(js) height = self.frame.evaluateJavaScript(js)
if shrink: if shrink:
if text: if text:
@ -368,7 +370,7 @@ class MainDisplay(OpenLPMixin, Display, RegistryProperties):
""" """
self.setGeometry(self.screen['size']) self.setGeometry(self.screen['size'])
if image: if image:
js = 'show_image("data:image/png;base64,%s");' % image js = 'show_image("data:image/png;base64,{image}");'.format(image=image)
else: else:
js = 'show_image("");' js = 'show_image("");'
self.frame.evaluateJavaScript(js) self.frame.evaluateJavaScript(js)
@ -492,7 +494,7 @@ class MainDisplay(OpenLPMixin, Display, RegistryProperties):
:param mode: How the screen is to be hidden :param mode: How the screen is to be hidden
""" """
self.log_debug('hide_display mode = %d' % mode) self.log_debug('hide_display mode = {mode:d}'.format(mode=mode))
if self.screens.display_count == 1: if self.screens.display_count == 1:
# Only make visible if setting enabled. # Only make visible if setting enabled.
if not Settings().value('core/display on monitor'): if not Settings().value('core/display on monitor'):

View File

@ -622,11 +622,10 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
:param version: The Version to be displayed. :param version: The Version to be displayed.
""" """
log.debug('version_notice') log.debug('version_notice')
version_text = translate('OpenLP.MainWindow', 'Version %s of OpenLP is now available for download (you are ' version_text = translate('OpenLP.MainWindow', 'Version {new} of OpenLP is now available for download (you are '
'currently running version %s). \n\nYou can download the latest version from ' 'currently running version {current}). \n\nYou can download the latest version from '
'http://openlp.org/.') 'http://openlp.org/.').format(new=version, current=get_application_version()[u'full'])
QtWidgets.QMessageBox.question(self, translate('OpenLP.MainWindow', 'OpenLP Version Updated'), QtWidgets.QMessageBox.question(self, translate('OpenLP.MainWindow', 'OpenLP Version Updated'), version_text)
version_text % (version, get_application_version()[u'full']))
def show(self): def show(self):
""" """
@ -642,7 +641,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
self.service_manager_contents.load_last_file() self.service_manager_contents.load_last_file()
# This will store currently used layout preset so it remains enabled on next startup. # This will store currently used layout preset so it remains enabled on next startup.
# If any panel is enabled/disabled after preset is set, this setting is not saved. # If any panel is enabled/disabled after preset is set, this setting is not saved.
view_mode = Settings().value('%s/view mode' % self.general_settings_section) view_mode = Settings().value('{section}/view mode'.format(section=self.general_settings_section))
if view_mode == 'default' and Settings().value('user interface/is preset layout'): if view_mode == 'default' and Settings().value('user interface/is preset layout'):
self.mode_default_item.setChecked(True) self.mode_default_item.setChecked(True)
elif view_mode == 'setup' and Settings().value('user interface/is preset layout'): elif view_mode == 'setup' and Settings().value('user interface/is preset layout'):
@ -731,8 +730,8 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
""" """
settings = Settings() settings = Settings()
self.live_controller.main_display_set_background() self.live_controller.main_display_set_background()
if settings.value('%s/screen blank' % self.general_settings_section): if settings.value('{section}/screen blank'.format(section=self.general_settings_section)):
if settings.value('%s/blank warning' % self.general_settings_section): if settings.value('{section}/blank warning'.format(section=self.general_settings_section)):
QtWidgets.QMessageBox.question(self, translate('OpenLP.MainWindow', 'OpenLP Main Display Blanked'), QtWidgets.QMessageBox.question(self, translate('OpenLP.MainWindow', 'OpenLP Main Display Blanked'),
translate('OpenLP.MainWindow', 'The Main Display has been blanked out')) translate('OpenLP.MainWindow', 'The Main Display has been blanked out'))
@ -924,9 +923,9 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
try: try:
value = import_settings.value(section_key) value = import_settings.value(section_key)
except KeyError: except KeyError:
log.warning('The key "%s" does not exist (anymore), so it will be skipped.' % section_key) log.warning('The key "{key}" does not exist (anymore), so it will be skipped.'.format(key=section_key))
if value is not None: if value is not None:
settings.setValue('%s' % (section_key), value) settings.setValue('{key}'.format(key=section_key), value)
now = datetime.now() now = datetime.now()
settings.beginGroup(self.header_section) settings.beginGroup(self.header_section)
settings.setValue('file_imported', import_file_name) settings.setValue('file_imported', import_file_name)
@ -1003,9 +1002,9 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
key_value = settings.value(section_key) key_value = settings.value(section_key)
except KeyError: except KeyError:
QtWidgets.QMessageBox.critical(self, translate('OpenLP.MainWindow', 'Export setting error'), QtWidgets.QMessageBox.critical(self, translate('OpenLP.MainWindow', 'Export setting error'),
translate('OpenLP.MainWindow', 'The key "%s" does not have a default ' translate('OpenLP.MainWindow', 'The key "{key}" does not have a default '
'value so it will be skipped in this ' 'value so it will be skipped in this '
'export.') % section_key, 'export.').format(key=section_key),
QtWidgets.QMessageBox.StandardButtons(QtWidgets.QMessageBox.Ok)) QtWidgets.QMessageBox.StandardButtons(QtWidgets.QMessageBox.Ok))
key_value = None key_value = None
if key_value is not None: if key_value is not None:
@ -1027,8 +1026,9 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
os.remove(temp_file) os.remove(temp_file)
except OSError as ose: except OSError as ose:
QtWidgets.QMessageBox.critical(self, translate('OpenLP.MainWindow', 'Export setting error'), QtWidgets.QMessageBox.critical(self, translate('OpenLP.MainWindow', 'Export setting error'),
translate('OpenLP.MainWindow', 'An error occurred while exporting the ' translate('OpenLP.MainWindow',
'settings: %s') % ose.strerror, 'An error occurred while exporting the '
'settings: {err}').format(err=ose.strerror),
QtWidgets.QMessageBox.StandardButtons(QtWidgets.QMessageBox.Ok)) QtWidgets.QMessageBox.StandardButtons(QtWidgets.QMessageBox.Ok))
def on_mode_default_item_clicked(self): def on_mode_default_item_clicked(self):
@ -1061,7 +1061,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
""" """
if mode: if mode:
settings = Settings() settings = Settings()
settings.setValue('%s/view mode' % self.general_settings_section, mode) settings.setValue('{section}/view mode'.format(section=self.general_settings_section), mode)
self.media_manager_dock.setVisible(media) self.media_manager_dock.setVisible(media)
self.service_manager_dock.setVisible(service) self.service_manager_dock.setVisible(service)
self.theme_manager_dock.setVisible(theme) self.theme_manager_dock.setVisible(theme)
@ -1168,9 +1168,9 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
:param file_name: The file name of the service file. :param file_name: The file name of the service file.
""" """
if modified: if modified:
title = '%s - %s*' % (UiStrings().OLPV2x, file_name) title = '{title} - {name}*'.format(title=UiStrings().OLPV2x, name=file_name)
else: else:
title = '%s - %s' % (UiStrings().OLPV2x, file_name) title = '{title} - {name}'.format(title=UiStrings().OLPV2x, name=file_name)
self.setWindowTitle(title) self.setWindowTitle(title)
def show_status_message(self, message): def show_status_message(self, message):
@ -1183,8 +1183,9 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
""" """
Update the default theme indicator in the status bar Update the default theme indicator in the status bar
""" """
self.default_theme_label.setText(translate('OpenLP.MainWindow', 'Default Theme: %s') % theme_name = Settings().value('themes/global theme')
Settings().value('themes/global theme')) self.default_theme_label.setText(translate('OpenLP.MainWindow',
'Default Theme: {theme}').format(theme=theme_name))
def toggle_media_manager(self): def toggle_media_manager(self):
""" """
@ -1331,7 +1332,8 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
recent_files_to_display = existing_recent_files[0:recent_file_count] recent_files_to_display = existing_recent_files[0:recent_file_count]
self.recent_files_menu.clear() self.recent_files_menu.clear()
for file_id, filename in enumerate(recent_files_to_display): for file_id, filename in enumerate(recent_files_to_display):
log.debug('Recent file name: %s', filename) log.debug('Recent file name: {name}'.format(name=filename))
# TODO: Verify ''.format() before committing
action = create_action(self, '', text='&%d %s' % (file_id + 1, action = create_action(self, '', text='&%d %s' % (file_id + 1,
os.path.splitext(os.path.basename(str(filename)))[0]), data=filename, os.path.splitext(os.path.basename(str(filename)))[0]), data=filename,
triggers=self.service_manager_contents.on_recent_service_clicked) triggers=self.service_manager_contents.on_recent_service_clicked)
@ -1424,7 +1426,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
""" """
Change the data directory. Change the data directory.
""" """
log.info('Changing data path to %s' % self.new_data_path) log.info('Changing data path to {newpath}'.format(newpath=self.new_data_path))
old_data_path = str(AppLocation.get_data_path()) old_data_path = str(AppLocation.get_data_path())
# Copy OpenLP data to new location if requested. # Copy OpenLP data to new location if requested.
self.application.set_busy_cursor() self.application.set_busy_cursor()
@ -1432,17 +1434,17 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties):
log.info('Copying data to new path') log.info('Copying data to new path')
try: try:
self.show_status_message( self.show_status_message(
translate('OpenLP.MainWindow', 'Copying OpenLP data to new data directory location - %s ' translate('OpenLP.MainWindow', 'Copying OpenLP data to new data directory location - {path} '
'- Please wait for copy to finish').replace('%s', self.new_data_path)) '- Please wait for copy to finish').format(path=self.new_data_path))
dir_util.copy_tree(old_data_path, self.new_data_path) dir_util.copy_tree(old_data_path, self.new_data_path)
log.info('Copy successful') log.info('Copy successful')
except (IOError, os.error, DistutilsFileError) as why: except (IOError, os.error, DistutilsFileError) as why:
self.application.set_normal_cursor() self.application.set_normal_cursor()
log.exception('Data copy failed %s' % str(why)) log.exception('Data copy failed {err}'.format(err=str(why)))
err_text = translate('OpenLP.MainWindow',
'OpenLP Data directory copy failed\n\n{err}').format(err=str(why)),
QtWidgets.QMessageBox.critical(self, translate('OpenLP.MainWindow', 'New Data Directory Error'), QtWidgets.QMessageBox.critical(self, translate('OpenLP.MainWindow', 'New Data Directory Error'),
translate('OpenLP.MainWindow', err_text,
'OpenLP Data directory copy failed\n\n%s').
replace('%s', str(why)),
QtWidgets.QMessageBox.StandardButtons(QtWidgets.QMessageBox.Ok)) QtWidgets.QMessageBox.StandardButtons(QtWidgets.QMessageBox.Ok))
return False return False
else: else:

View File

@ -577,7 +577,7 @@ class BibleImportForm(OpenLPWizard):
:param index: The index of the combo box. :param index: The index of the combo box.
""" """
self.web_translation_combo_box.clear() self.web_translation_combo_box.clear()
if self.web_bible_list: if self.web_bible_list and index in self.web_bible_list:
bibles = list(self.web_bible_list[index].keys()) bibles = list(self.web_bible_list[index].keys())
bibles.sort(key=get_locale_key) bibles.sort(key=get_locale_key)
self.web_translation_combo_box.addItems(bibles) self.web_translation_combo_box.addItems(bibles)

View File

@ -520,7 +520,7 @@ class CWExtract(RegistryProperties):
returns a list in the form [(biblename, biblekey, language_code)] returns a list in the form [(biblename, biblekey, language_code)]
""" """
log.debug('CWExtract.get_bibles_from_http') log.debug('CWExtract.get_bibles_from_http')
bible_url = 'http://www.biblestudytools.com/search/bible-search.part/' bible_url = 'http://www.biblestudytools.com/'
soup = get_soup_for_bible_ref(bible_url) soup = get_soup_for_bible_ref(bible_url)
if not soup: if not soup:
return None return None
@ -528,7 +528,7 @@ class CWExtract(RegistryProperties):
if not bible_select: if not bible_select:
log.debug('No select tags found - did site change?') log.debug('No select tags found - did site change?')
return None return None
option_tags = bible_select.find_all('option') option_tags = bible_select.find_all('option', {'class': 'log-translation'})
if not option_tags: if not option_tags:
log.debug('No option tags found - did site change?') log.debug('No option tags found - did site change?')
return None return None

View File

@ -71,8 +71,12 @@ class MediaPlugin(Plugin):
:return: true or false :return: true or false
""" """
log.debug('check_installed Mediainfo') log.debug('check_installed Mediainfo')
# Use the user defined program if given # Try to find mediainfo in the path
return process_check_binary('mediainfo') exists = process_check_binary('mediainfo')
# If mediainfo is not in the path, try to find it in the application folder
if not exists:
exists = process_check_binary(os.path.join(AppLocation.get_directory(AppLocation.AppDir), 'mediainfo'))
return exists
def app_startup(self): def app_startup(self):
""" """
@ -160,7 +164,6 @@ def process_check_binary(program_path):
""" """
program_type = None program_type = None
runlog = check_binary_exists(program_path) runlog = check_binary_exists(program_path)
print(runlog, type(runlog))
# Analyse the output to see it the program is mediainfo # Analyse the output to see it the program is mediainfo
for line in runlog.splitlines(): for line in runlog.splitlines():
decoded_line = line.decode() decoded_line = line.decode()

View File

@ -77,6 +77,12 @@ class PdfController(PresentationController):
if found_mudraw: if found_mudraw:
program_type = 'mudraw' program_type = 'mudraw'
break break
found_mutool = re.search('usage: mutool.*', decoded_line, re.IGNORECASE)
if found_mutool:
# Test that mutool contains mudraw
if re.search('draw\s+--\s+convert document.*', runlog.decode(), re.IGNORECASE | re.MULTILINE):
program_type = 'mutool'
break
found_gs = re.search('GPL Ghostscript.*', decoded_line, re.IGNORECASE) found_gs = re.search('GPL Ghostscript.*', decoded_line, re.IGNORECASE)
if found_gs: if found_gs:
program_type = 'gs' program_type = 'gs'
@ -101,6 +107,7 @@ class PdfController(PresentationController):
""" """
log.debug('check_installed Pdf') log.debug('check_installed Pdf')
self.mudrawbin = '' self.mudrawbin = ''
self.mutoolbin = ''
self.gsbin = '' self.gsbin = ''
self.also_supports = [] self.also_supports = []
# Use the user defined program if given # Use the user defined program if given
@ -111,27 +118,36 @@ class PdfController(PresentationController):
self.gsbin = pdf_program self.gsbin = pdf_program
elif program_type == 'mudraw': elif program_type == 'mudraw':
self.mudrawbin = pdf_program self.mudrawbin = pdf_program
elif program_type == 'mutool':
self.mutoolbin = pdf_program
else: else:
# Fallback to autodetection # Fallback to autodetection
application_path = AppLocation.get_directory(AppLocation.AppDir) application_path = AppLocation.get_directory(AppLocation.AppDir)
if is_win(): if is_win():
# for windows we only accept mudraw.exe in the base folder # for windows we only accept mudraw.exe or mutool.exe in the base folder
application_path = AppLocation.get_directory(AppLocation.AppDir) application_path = AppLocation.get_directory(AppLocation.AppDir)
if os.path.isfile(os.path.join(application_path, 'mudraw.exe')): if os.path.isfile(os.path.join(application_path, 'mudraw.exe')):
self.mudrawbin = os.path.join(application_path, 'mudraw.exe') self.mudrawbin = os.path.join(application_path, 'mudraw.exe')
elif os.path.isfile(os.path.join(application_path, 'mutool.exe')):
self.mutoolbin = os.path.join(application_path, 'mutool.exe')
else: else:
DEVNULL = open(os.devnull, 'wb') DEVNULL = open(os.devnull, 'wb')
# First try to find mupdf # First try to find mudraw
self.mudrawbin = which('mudraw') self.mudrawbin = which('mudraw')
# if mupdf isn't installed, fallback to ghostscript # if mudraw isn't installed, try mutool
if not self.mudrawbin: if not self.mudrawbin:
self.gsbin = which('gs') self.mutoolbin = which('mutool')
# Last option: check if mudraw is placed in OpenLP base folder # Check we got a working mutool
if not self.mudrawbin and not self.gsbin: if not self.mutoolbin or self.process_check_binary(self.mutoolbin) != 'mutool':
self.gsbin = which('gs')
# Last option: check if mudraw or mutool is placed in OpenLP base folder
if not self.mudrawbin and not self.mutoolbin and not self.gsbin:
application_path = AppLocation.get_directory(AppLocation.AppDir) application_path = AppLocation.get_directory(AppLocation.AppDir)
if os.path.isfile(os.path.join(application_path, 'mudraw')): if os.path.isfile(os.path.join(application_path, 'mudraw')):
self.mudrawbin = os.path.join(application_path, 'mudraw') self.mudrawbin = os.path.join(application_path, 'mudraw')
if self.mudrawbin: elif os.path.isfile(os.path.join(application_path, 'mutool')):
self.mutoolbin = os.path.join(application_path, 'mutool')
if self.mudrawbin or self.mutoolbin:
self.also_supports = ['xps', 'oxps'] self.also_supports = ['xps', 'oxps']
return True return True
elif self.gsbin: elif self.gsbin:
@ -238,10 +254,18 @@ class PdfDocument(PresentationDocument):
if not os.path.isdir(self.get_temp_folder()): if not os.path.isdir(self.get_temp_folder()):
os.makedirs(self.get_temp_folder()) os.makedirs(self.get_temp_folder())
if self.controller.mudrawbin: if self.controller.mudrawbin:
log.debug('loading presentation using mudraw')
runlog = check_output([self.controller.mudrawbin, '-w', str(size.width()), '-h', str(size.height()), runlog = check_output([self.controller.mudrawbin, '-w', str(size.width()), '-h', str(size.height()),
'-o', os.path.join(self.get_temp_folder(), 'mainslide%03d.png'), self.file_path], '-o', os.path.join(self.get_temp_folder(), 'mainslide%03d.png'), self.file_path],
startupinfo=self.startupinfo) startupinfo=self.startupinfo)
elif self.controller.mutoolbin:
log.debug('loading presentation using mutool')
runlog = check_output([self.controller.mutoolbin, 'draw', '-w', str(size.width()), '-h',
str(size.height()),
'-o', os.path.join(self.get_temp_folder(), 'mainslide%03d.png'), self.file_path],
startupinfo=self.startupinfo)
elif self.controller.gsbin: elif self.controller.gsbin:
log.debug('loading presentation using gs')
resolution = self.gs_get_resolution(size) resolution = self.gs_get_resolution(size)
runlog = check_output([self.controller.gsbin, '-dSAFER', '-dNOPAUSE', '-dBATCH', '-sDEVICE=png16m', runlog = check_output([self.controller.gsbin, '-dSAFER', '-dNOPAUSE', '-dBATCH', '-sDEVICE=png16m',
'-r' + str(resolution), '-dTextAlphaBits=4', '-dGraphicsAlphaBits=4', '-r' + str(resolution), '-dTextAlphaBits=4', '-dGraphicsAlphaBits=4',

View File

@ -235,7 +235,7 @@ class PresentationTab(SettingsTab):
self, translate('PresentationPlugin.PresentationTab', 'Select mudraw or ghostscript binary.'), self, translate('PresentationPlugin.PresentationTab', 'Select mudraw or ghostscript binary.'),
self.pdf_program_path.text()) self.pdf_program_path.text())
if filename: if filename:
program_type = PdfController.check_binary(filename) program_type = PdfController.process_check_binary(filename)
if not program_type: if not program_type:
critical_error_message_box(UiStrings().Error, critical_error_message_box(UiStrings().Error,
translate('PresentationPlugin.PresentationTab', translate('PresentationPlugin.PresentationTab',

View File

@ -21,7 +21,6 @@
############################################################################### ###############################################################################
import logging import logging
import re
import os import os
import shutil import shutil
@ -207,9 +206,11 @@ class SongMediaItem(MediaManagerItem):
search_keywords = search_keywords.rpartition(' ') search_keywords = search_keywords.rpartition(' ')
search_book = search_keywords[0] + '%' search_book = search_keywords[0] + '%'
search_entry = search_keywords[2] + '%' search_entry = search_keywords[2] + '%'
search_results = (self.plugin.manager.session.query(SongBookEntry) search_results = (self.plugin.manager.session.query(SongBookEntry.entry, Book.name, Song.title, Song.id)
.join(Song)
.join(Book) .join(Book)
.filter(Book.name.like(search_book), SongBookEntry.entry.like(search_entry)).all()) .filter(Book.name.like(search_book), SongBookEntry.entry.like(search_entry),
Song.temporary.is_(False)).all())
self.display_results_book(search_results) self.display_results_book(search_results)
elif search_type == SongSearch.Themes: elif search_type == SongSearch.Themes:
log.debug('Theme Search') log.debug('Theme Search')
@ -313,23 +314,20 @@ class SongMediaItem(MediaManagerItem):
""" """
Display the song search results in the media manager list, grouped by book and entry Display the song search results in the media manager list, grouped by book and entry
:param search_results: A list of db SongBookEntry objects :param search_results: A tuple containing (songbook entry, book name, song title, song id)
:return: None :return: None
""" """
def get_songbook_key(songbook_entry): def get_songbook_key(result):
"""Get the key to sort by""" """Get the key to sort by"""
return (get_natural_key(songbook_entry.songbook.name), get_natural_key(songbook_entry.entry)) return (get_natural_key(result[1]), get_natural_key(result[0]), get_natural_key(result[2]))
log.debug('display results Book') log.debug('display results Book')
self.list_view.clear() self.list_view.clear()
search_results.sort(key=get_songbook_key) search_results.sort(key=get_songbook_key)
for songbook_entry in search_results: for result in search_results:
# Do not display temporary songs song_detail = '%s #%s: %s' % (result[1], result[0], result[2])
if songbook_entry.song.temporary:
continue
song_detail = '%s #%s: %s' % (songbook_entry.songbook.name, songbook_entry.entry, songbook_entry.song.title)
song_name = QtWidgets.QListWidgetItem(song_detail) song_name = QtWidgets.QListWidgetItem(song_detail)
song_name.setData(QtCore.Qt.UserRole, songbook_entry.song.id) song_name.setData(QtCore.Qt.UserRole, result[3])
self.list_view.addItem(song_name) self.list_view.addItem(song_name)
def display_results_topic(self, search_results): def display_results_topic(self, search_results):

View File

@ -28,7 +28,7 @@ PREREQUISITE: add_record() and get_all() functions validated.
import os import os
from unittest import TestCase from unittest import TestCase
from openlp.core.lib.projector.db import Projector, ProjectorDB, ProjectorSource from openlp.core.lib.projector.db import Manufacturer, Model, Projector, ProjectorDB, ProjectorSource
from tests.functional import MagicMock, patch from tests.functional import MagicMock, patch
from tests.resources.projector.data import TEST_DB, TEST1_DATA, TEST2_DATA, TEST3_DATA from tests.resources.projector.data import TEST_DB, TEST1_DATA, TEST2_DATA, TEST3_DATA
@ -82,13 +82,13 @@ class TestProjectorDB(TestCase):
""" """
Test case for ProjectorDB Test case for ProjectorDB
""" """
def setUp(self): @patch('openlp.core.lib.projector.db.init_url')
def setUp(self, mocked_init_url):
""" """
Set up anything necessary for all tests Set up anything necessary for all tests
""" """
with patch('openlp.core.lib.projector.db.init_url') as mocked_init_url: mocked_init_url.return_value = 'sqlite:///{db}'.format(db=TEST_DB)
mocked_init_url.return_value = 'sqlite:///%s' % TEST_DB self.projector = ProjectorDB()
self.projector = ProjectorDB()
def tearDown(self): def tearDown(self):
""" """
@ -192,3 +192,17 @@ class TestProjectorDB(TestCase):
# THEN: Projector should have the same source entry # THEN: Projector should have the same source entry
item = self.projector.get_projector_by_id(item_id) item = self.projector.get_projector_by_id(item_id)
self.assertTrue(compare_source(item.source_list[0], source)) self.assertTrue(compare_source(item.source_list[0], source))
def manufacturer_repr_test(self):
"""
Test manufacturer class __repr__ text
"""
# GIVEN: Test object
manufacturer = Manufacturer()
# WHEN: Name is set
manufacturer.name = 'OpenLP Test'
# THEN: __repr__ should return a proper string
self.assertEqual(str(manufacturer), '<Manufacturer(name="OpenLP Test")>',
'Manufacturer.__repr__() should have returned a proper representation string')

View File

@ -29,7 +29,7 @@ from tempfile import mkdtemp
from PyQt5 import QtCore, QtGui from PyQt5 import QtCore, QtGui
from openlp.plugins.presentations.lib.pdfcontroller import PdfController, PdfDocument from openlp.plugins.presentations.lib.pdfcontroller import PdfController, PdfDocument
from tests.functional import MagicMock from tests.functional import MagicMock, patch
from openlp.core.common import Settings from openlp.core.common import Settings
from openlp.core.lib import ScreenList from openlp.core.lib import ScreenList
from tests.utils.constants import TEST_RESOURCES_PATH from tests.utils.constants import TEST_RESOURCES_PATH
@ -137,3 +137,74 @@ class TestPdfController(TestCase, TestMixin):
else: else:
self.assertEqual(768, image.height(), 'The height should be 768') self.assertEqual(768, image.height(), 'The height should be 768')
self.assertEqual(543, image.width(), 'The width should be 543') self.assertEqual(543, image.width(), 'The width should be 543')
@patch('openlp.plugins.presentations.lib.pdfcontroller.check_binary_exists')
def process_check_binary_mudraw_test(self, mocked_check_binary_exists):
"""
Test that the correct output from mudraw is detected
"""
# GIVEN: A mocked check_binary_exists that returns mudraw output
mudraw_output = (b'usage: mudraw [options] input [pages]\n\t-o -\toutput filename (%d for page number)n\t\tsupp'
b'orted formats: pgm, ppm, pam, png, pbmn\t-p -\tpasswordn\t-r -\tresolution in dpi (default: '
b'72)n\t-w -\twidth (in pixels) (maximum width if -r is specified)n\t-h -\theight (in pixels) '
b'(maximum height if -r is specified)')
mocked_check_binary_exists.return_value = mudraw_output
# WHEN: Calling process_check_binary
ret = PdfController.process_check_binary('test')
# THEN: mudraw should be detected
self.assertEqual('mudraw', ret, 'mudraw should have been detected')
@patch('openlp.plugins.presentations.lib.pdfcontroller.check_binary_exists')
def process_check_binary_new_motool_test(self, mocked_check_binary_exists):
"""
Test that the correct output from the new mutool is detected
"""
# GIVEN: A mocked check_binary_exists that returns new mutool output
new_mutool_output = (b'usage: mutool <command> [options]\n\tdraw\t-- convert document\n\trun\t-- run javascript'
b'\n\tclean\t-- rewrite pdf file\n\textract\t-- extract font and image resources\n\tinfo\t'
b'-- show information about pdf resources\n\tpages\t-- show information about pdf pages\n'
b'\tposter\t-- split large page into many tiles\n\tshow\t-- show internal pdf objects\n\t'
b'create\t-- create pdf document\n\tmerge\t-- merge pages from multiple pdf sources into a'
b'new pdf\n')
mocked_check_binary_exists.return_value = new_mutool_output
# WHEN: Calling process_check_binary
ret = PdfController.process_check_binary('test')
# THEN: mutool should be detected
self.assertEqual('mutool', ret, 'mutool should have been detected')
@patch('openlp.plugins.presentations.lib.pdfcontroller.check_binary_exists')
def process_check_binary_old_motool_test(self, mocked_check_binary_exists):
"""
Test that the output from the old mutool is not accepted
"""
# GIVEN: A mocked check_binary_exists that returns old mutool output
old_mutool_output = (b'usage: mutool <command> [options]\n\tclean\t-- rewrite pdf file\n\textract\t-- extract '
b'font and image resources\n\tinfo\t-- show information about pdf resources\n\tposter\t-- '
b'split large page into many tiles\n\tshow\t-- show internal pdf objects')
mocked_check_binary_exists.return_value = old_mutool_output
# WHEN: Calling process_check_binary
ret = PdfController.process_check_binary('test')
# THEN: mutool should be detected
self.assertIsNone(ret, 'old mutool should not be accepted!')
@patch('openlp.plugins.presentations.lib.pdfcontroller.check_binary_exists')
def process_check_binary_gs_test(self, mocked_check_binary_exists):
"""
Test that the correct output from gs is detected
"""
# GIVEN: A mocked check_binary_exists that returns gs output
gs_output = (b'GPL Ghostscript 9.19 (2016-03-23)\nCopyright (C) 2016 Artifex Software, Inc. All rights reserv'
b'ed.\nUsage: gs [switches] [file1.ps file2.ps ...]')
mocked_check_binary_exists.return_value = gs_output
# WHEN: Calling process_check_binary
ret = PdfController.process_check_binary('test')
# THEN: mutool should be detected
self.assertEqual('gs', ret, 'mutool should have been detected')

View File

@ -23,6 +23,7 @@
This module contains tests for the lib submodule of the Songs plugin. This module contains tests for the lib submodule of the Songs plugin.
""" """
from unittest import TestCase from unittest import TestCase
from unittest.mock import call
from PyQt5 import QtCore from PyQt5 import QtCore
@ -151,29 +152,7 @@ class TestMediaItem(TestCase, TestMixin):
# GIVEN: Search results grouped by book and entry, plus a mocked QtListWidgetItem # GIVEN: Search results grouped by book and entry, plus a mocked QtListWidgetItem
with patch('openlp.core.lib.QtWidgets.QListWidgetItem') as MockedQListWidgetItem, \ with patch('openlp.core.lib.QtWidgets.QListWidgetItem') as MockedQListWidgetItem, \
patch('openlp.core.lib.QtCore.Qt.UserRole') as MockedUserRole: patch('openlp.core.lib.QtCore.Qt.UserRole') as MockedUserRole:
mock_search_results = [] mock_search_results = [('1', 'My Book', 'My Song', 1)]
mock_songbook_entry = MagicMock()
mock_songbook_entry_temp = MagicMock()
mock_songbook = MagicMock()
mock_song = MagicMock()
mock_song_temp = MagicMock()
mock_songbook_entry.entry = '1'
mock_songbook_entry_temp.entry = '2'
mock_songbook.name = 'My Book'
mock_song.id = 1
mock_song.title = 'My Song'
mock_song.sort_key = 'My Song'
mock_song.temporary = False
mock_song_temp.id = 2
mock_song_temp.title = 'My Temporary'
mock_song_temp.sort_key = 'My Temporary'
mock_song_temp.temporary = True
mock_songbook_entry.song = mock_song
mock_songbook_entry.songbook = mock_songbook
mock_songbook_entry_temp.song = mock_song_temp
mock_songbook_entry_temp.songbook = mock_songbook
mock_search_results.append(mock_songbook_entry)
mock_search_results.append(mock_songbook_entry_temp)
mock_qlist_widget = MagicMock() mock_qlist_widget = MagicMock()
MockedQListWidgetItem.return_value = mock_qlist_widget MockedQListWidgetItem.return_value = mock_qlist_widget
@ -183,9 +162,35 @@ class TestMediaItem(TestCase, TestMixin):
# THEN: The current list view is cleared, the widget is created, and the relevant attributes set # THEN: The current list view is cleared, the widget is created, and the relevant attributes set
self.media_item.list_view.clear.assert_called_with() self.media_item.list_view.clear.assert_called_with()
MockedQListWidgetItem.assert_called_once_with('My Book #1: My Song') MockedQListWidgetItem.assert_called_once_with('My Book #1: My Song')
mock_qlist_widget.setData.assert_called_once_with(MockedUserRole, mock_songbook_entry.song.id) mock_qlist_widget.setData.assert_called_once_with(MockedUserRole, 1)
self.media_item.list_view.addItem.assert_called_once_with(mock_qlist_widget) self.media_item.list_view.addItem.assert_called_once_with(mock_qlist_widget)
def songbook_natural_sorting_test(self):
"""
Test that songbooks are sorted naturally
"""
# GIVEN: Search results grouped by book and entry, plus a mocked QtListWidgetItem
with patch('openlp.core.lib.QtWidgets.QListWidgetItem') as MockedQListWidgetItem:
mock_search_results = [('2', 'Thy Book', 'Thy Song', 50),
('2', 'My Book', 'Your Song', 7),
('10', 'My Book', 'Our Song', 12),
('1', 'My Book', 'My Song', 1),
('2', 'Thy Book', 'A Song', 8)]
mock_qlist_widget = MagicMock()
MockedQListWidgetItem.return_value = mock_qlist_widget
# WHEN: I display song search results grouped by book
self.media_item.display_results_book(mock_search_results)
# THEN: The songbooks are inserted in the right (natural) order,
# grouped first by book, then by number, then by song title
calls = [call('My Book #1: My Song'), call().setData(QtCore.Qt.UserRole, 1),
call('My Book #2: Your Song'), call().setData(QtCore.Qt.UserRole, 7),
call('My Book #10: Our Song'), call().setData(QtCore.Qt.UserRole, 12),
call('Thy Book #2: A Song'), call().setData(QtCore.Qt.UserRole, 8),
call('Thy Book #2: Thy Song'), call().setData(QtCore.Qt.UserRole, 50)]
MockedQListWidgetItem.assert_has_calls(calls)
def display_results_topic_test(self): def display_results_topic_test(self):
""" """
Test displaying song search results grouped by topic with basic song Test displaying song search results grouped by topic with basic song