merge w/ trunk

This commit is contained in:
Chris Hill 2015-02-13 17:23:30 +00:00
commit a95894fba4
28 changed files with 426 additions and 109 deletions

View File

@ -190,31 +190,32 @@ def verify_ip_address(addr):
return True if verify_ipv4(addr) else verify_ipv6(addr) return True if verify_ipv4(addr) else verify_ipv6(addr)
def md5_hash(salt, data): def md5_hash(salt, data=None):
""" """
Returns the hashed output of md5sum on salt,data Returns the hashed output of md5sum on salt,data
using Python3 hashlib using Python3 hashlib
:param salt: Initial salt :param salt: Initial salt
:param data: Data to hash :param data: OPTIONAL Data to hash
:returns: str :returns: str
""" """
log.debug('md5_hash(salt="%s")' % salt) log.debug('md5_hash(salt="%s")' % salt)
hash_obj = hashlib.new('md5') hash_obj = hashlib.new('md5')
hash_obj.update(salt.encode('ascii')) hash_obj.update(salt)
hash_obj.update(data.encode('ascii')) if data:
hash_obj.update(data)
hash_value = hash_obj.hexdigest() hash_value = hash_obj.hexdigest()
log.debug('md5_hash() returning "%s"' % hash_value) log.debug('md5_hash() returning "%s"' % hash_value)
return hash_value return hash_value
def qmd5_hash(salt, data): def qmd5_hash(salt, data=None):
""" """
Returns the hashed output of MD5Sum on salt, data Returns the hashed output of MD5Sum on salt, data
using PyQt4.QCryptographicHash. using PyQt4.QCryptographicHash.
:param salt: Initial salt :param salt: Initial salt
:param data: Data to hash :param data: OPTIONAL Data to hash
:returns: str :returns: str
""" """
log.debug('qmd5_hash(salt="%s"' % salt) log.debug('qmd5_hash(salt="%s"' % salt)
@ -223,7 +224,7 @@ def qmd5_hash(salt, data):
hash_obj.addData(data) hash_obj.addData(data)
hash_value = hash_obj.result().toHex() hash_value = hash_obj.result().toHex()
log.debug('qmd5_hash() returning "%s"' % hash_value) log.debug('qmd5_hash() returning "%s"' % hash_value)
return decode(hash_value.data(), 'ascii') return hash_value.data()
def clean_button_text(button_text): def clean_button_text(button_text):

View File

@ -213,13 +213,6 @@ class MediaManagerItem(QtGui.QWidget, RegistryProperties):
icon=':/general/general_edit.png', icon=':/general/general_edit.png',
triggers=self.on_edit_click) triggers=self.on_edit_click)
create_widget_action(self.list_view, separator=True) create_widget_action(self.list_view, separator=True)
if self.has_delete_icon:
create_widget_action(self.list_view,
'listView%s%sItem' % (self.plugin.name.title(), StringContent.Delete.title()),
text=self.plugin.get_string(StringContent.Delete)['title'],
icon=':/general/general_delete.png',
can_shortcuts=True, triggers=self.on_delete_click)
create_widget_action(self.list_view, separator=True)
create_widget_action(self.list_view, create_widget_action(self.list_view,
'listView%s%sItem' % (self.plugin.name.title(), StringContent.Preview.title()), 'listView%s%sItem' % (self.plugin.name.title(), StringContent.Preview.title()),
text=self.plugin.get_string(StringContent.Preview)['title'], text=self.plugin.get_string(StringContent.Preview)['title'],
@ -238,6 +231,13 @@ class MediaManagerItem(QtGui.QWidget, RegistryProperties):
text=self.plugin.get_string(StringContent.Service)['title'], text=self.plugin.get_string(StringContent.Service)['title'],
icon=':/general/general_add.png', icon=':/general/general_add.png',
triggers=self.on_add_click) triggers=self.on_add_click)
if self.has_delete_icon:
create_widget_action(self.list_view, separator=True)
create_widget_action(self.list_view,
'listView%s%sItem' % (self.plugin.name.title(), StringContent.Delete.title()),
text=self.plugin.get_string(StringContent.Delete)['title'],
icon=':/general/general_delete.png',
can_shortcuts=True, triggers=self.on_delete_click)
if self.add_to_service_item: if self.add_to_service_item:
create_widget_action(self.list_view, separator=True) create_widget_action(self.list_view, separator=True)
create_widget_action(self.list_view, create_widget_action(self.list_view,

View File

@ -343,7 +343,7 @@ class PJLink1(QTcpSocket):
# Authenticated login with salt # Authenticated login with salt
log.debug('(%s) Setting hash with salt="%s"' % (self.ip, data_check[2])) log.debug('(%s) Setting hash with salt="%s"' % (self.ip, data_check[2]))
log.debug('(%s) pin="%s"' % (self.ip, self.pin)) log.debug('(%s) pin="%s"' % (self.ip, self.pin))
salt = qmd5_hash(salt=data_check[2], data=self.pin) salt = qmd5_hash(salt=data_check[2].endcode('ascii'), data=self.pin.encode('ascii'))
else: else:
salt = None salt = None
# We're connected at this point, so go ahead and do regular I/O # We're connected at this point, so go ahead and do regular I/O

View File

@ -268,9 +268,11 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties):
self.web = 'http://openlp.org/files/frw/' self.web = 'http://openlp.org/files/frw/'
self.cancel_button.clicked.connect(self.on_cancel_button_clicked) self.cancel_button.clicked.connect(self.on_cancel_button_clicked)
self.no_internet_finish_button.clicked.connect(self.on_no_internet_finish_button_clicked) self.no_internet_finish_button.clicked.connect(self.on_no_internet_finish_button_clicked)
self.no_internet_cancel_button.clicked.connect(self.on_no_internet_cancel_button_clicked)
self.currentIdChanged.connect(self.on_current_id_changed) self.currentIdChanged.connect(self.on_current_id_changed)
Registry().register_function('config_screen_changed', self.update_screen_list_combo) Registry().register_function('config_screen_changed', self.update_screen_list_combo)
self.no_internet_finish_button.setVisible(False) self.no_internet_finish_button.setVisible(False)
self.no_internet_cancel_button.setVisible(False)
# Check if this is a re-run of the wizard. # Check if this is a re-run of the wizard.
self.has_run_wizard = Settings().value('core/has run wizard') self.has_run_wizard = Settings().value('core/has run wizard')
check_directory_exists(os.path.join(gettempdir(), 'openlp')) check_directory_exists(os.path.join(gettempdir(), 'openlp'))
@ -327,6 +329,10 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties):
self.next_button.setVisible(False) self.next_button.setVisible(False)
self.cancel_button.setVisible(False) self.cancel_button.setVisible(False)
self.no_internet_finish_button.setVisible(True) self.no_internet_finish_button.setVisible(True)
if self.has_run_wizard:
self.no_internet_cancel_button.setVisible(False)
else:
self.no_internet_cancel_button.setVisible(True)
elif page_id == FirstTimePage.Plugins: elif page_id == FirstTimePage.Plugins:
self.back_button.setVisible(False) self.back_button.setVisible(False)
elif page_id == FirstTimePage.Progress: elif page_id == FirstTimePage.Progress:
@ -372,6 +378,13 @@ class FirstTimeForm(QtGui.QWizard, UiFirstTimeWizard, RegistryProperties):
Settings().setValue('core/has run wizard', True) Settings().setValue('core/has run wizard', True)
self.close() self.close()
def on_no_internet_cancel_button_clicked(self):
"""
Process the triggering of the "Cancel" button on the No Internet page.
"""
self.was_cancelled = True
self.close()
def url_get_file(self, url, f_path): def url_get_file(self, url, f_path):
"""" """"
Download a file given a URL. The file is retrieved in chunks, giving the ability to cancel the download at any Download a file given a URL. The file is retrieved in chunks, giving the ability to cancel the download at any

View File

@ -59,7 +59,8 @@ class UiFirstTimeWizard(object):
first_time_wizard.resize(550, 386) first_time_wizard.resize(550, 386)
first_time_wizard.setModal(True) first_time_wizard.setModal(True)
first_time_wizard.setOptions(QtGui.QWizard.IndependentPages | QtGui.QWizard.NoBackButtonOnStartPage | first_time_wizard.setOptions(QtGui.QWizard.IndependentPages | QtGui.QWizard.NoBackButtonOnStartPage |
QtGui.QWizard.NoBackButtonOnLastPage | QtGui.QWizard.HaveCustomButton1) QtGui.QWizard.NoBackButtonOnLastPage | QtGui.QWizard.HaveCustomButton1 |
QtGui.QWizard.HaveCustomButton2)
if is_macosx(): if is_macosx():
first_time_wizard.setPixmap(QtGui.QWizard.BackgroundPixmap, first_time_wizard.setPixmap(QtGui.QWizard.BackgroundPixmap,
QtGui.QPixmap(':/wizards/openlp-osx-wizard.png')) QtGui.QPixmap(':/wizards/openlp-osx-wizard.png'))
@ -69,6 +70,7 @@ class UiFirstTimeWizard(object):
self.finish_button = self.button(QtGui.QWizard.FinishButton) self.finish_button = self.button(QtGui.QWizard.FinishButton)
self.no_internet_finish_button = self.button(QtGui.QWizard.CustomButton1) self.no_internet_finish_button = self.button(QtGui.QWizard.CustomButton1)
self.cancel_button = self.button(QtGui.QWizard.CancelButton) self.cancel_button = self.button(QtGui.QWizard.CancelButton)
self.no_internet_cancel_button = self.button(QtGui.QWizard.CustomButton2)
self.next_button = self.button(QtGui.QWizard.NextButton) self.next_button = self.button(QtGui.QWizard.NextButton)
self.back_button = self.button(QtGui.QWizard.BackButton) self.back_button = self.button(QtGui.QWizard.BackButton)
add_welcome_page(first_time_wizard, ':/wizards/wizard_firsttime.bmp') add_welcome_page(first_time_wizard, ':/wizards/wizard_firsttime.bmp')
@ -271,3 +273,4 @@ class UiFirstTimeWizard(object):
'and OpenLP is configured.')) 'and OpenLP is configured.'))
self.progress_label.setText(translate('OpenLP.FirstTimeWizard', 'Starting configuration process...')) self.progress_label.setText(translate('OpenLP.FirstTimeWizard', 'Starting configuration process...'))
first_time_wizard.setButtonText(QtGui.QWizard.CustomButton1, translate('OpenLP.FirstTimeWizard', 'Finish')) first_time_wizard.setButtonText(QtGui.QWizard.CustomButton1, translate('OpenLP.FirstTimeWizard', 'Finish'))
first_time_wizard.setButtonText(QtGui.QWizard.CustomButton2, translate('OpenLP.FirstTimeWizard', 'Cancel'))

View File

@ -33,7 +33,12 @@ import cgi
import logging import logging
from PyQt4 import QtCore, QtGui, QtWebKit, QtOpenGL from PyQt4 import QtCore, QtGui, QtWebKit, QtOpenGL
from PyQt4.phonon import Phonon
PHONON_AVAILABLE = True
try:
from PyQt4.phonon import Phonon
except ImportError:
PHONON_AVAILABLE = False
from openlp.core.common import Registry, RegistryProperties, OpenLPMixin, Settings, translate, is_macosx from openlp.core.common import Registry, RegistryProperties, OpenLPMixin, Settings, translate, is_macosx
from openlp.core.lib import ServiceItem, ImageSource, ScreenList, build_html, expand_tags, image_to_byte from openlp.core.lib import ServiceItem, ImageSource, ScreenList, build_html, expand_tags, image_to_byte
@ -139,7 +144,7 @@ class MainDisplay(OpenLPMixin, Display, RegistryProperties):
self.override = {} self.override = {}
self.retranslateUi() self.retranslateUi()
self.media_object = None self.media_object = None
if self.is_live: if self.is_live and PHONON_AVAILABLE:
self.audio_player = AudioPlayer(self) self.audio_player = AudioPlayer(self)
else: else:
self.audio_player = None self.audio_player = None

View File

@ -22,11 +22,11 @@
""" """
The :mod:`~openlp.core.ui.media.webkit` module contains our WebKit video player The :mod:`~openlp.core.ui.media.webkit` module contains our WebKit video player
""" """
from PyQt4 import QtGui from PyQt4 import QtGui, QtWebKit
import logging import logging
from openlp.core.common import Settings, is_macosx from openlp.core.common import Settings
from openlp.core.lib import translate from openlp.core.lib import translate
from openlp.core.ui.media import MediaState from openlp.core.ui.media import MediaState
from openlp.core.ui.media.mediaplayer import MediaPlayer from openlp.core.ui.media.mediaplayer import MediaPlayer
@ -222,13 +222,15 @@ class WebkitPlayer(MediaPlayer):
def check_available(self): def check_available(self):
""" """
Check the availability of the media player Check the availability of the media player.
:return: boolean. True if available
""" """
# At the moment we don't have support for webkitplayer on Mac OS X web = QtWebKit.QWebPage()
if is_macosx(): # This script should return '[object HTMLVideoElement]' if the html5 video is available in webkit. Otherwise it
return False # should return '[object HTMLUnknownElement]'
else: return web.mainFrame().evaluateJavaScript(
return True "Object.prototype.toString.call(document.createElement('video'));") == '[object HTMLVideoElement]'
def load(self, display): def load(self, display):
""" """

View File

@ -332,8 +332,7 @@ class SourceSelectTabs(QDialog):
msg = QtGui.QMessageBox() msg = QtGui.QMessageBox()
msg.setText(translate('OpenLP.SourceSelectForm', 'Delete entries for this projector')) msg.setText(translate('OpenLP.SourceSelectForm', 'Delete entries for this projector'))
msg.setInformativeText(translate('OpenLP.SourceSelectForm', msg.setInformativeText(translate('OpenLP.SourceSelectForm',
'Are you sure you want to delete ALL user-defined '), 'Are you sure you want to delete ALL user-defined '
translate('OpenLP.SourceSelectForm',
'source input text for this projector?')) 'source input text for this projector?'))
msg.setStandardButtons(msg.Cancel | msg.Ok) msg.setStandardButtons(msg.Cancel | msg.Ok)
msg.setDefaultButton(msg.Cancel) msg.setDefaultButton(msg.Cancel)
@ -471,8 +470,7 @@ class SourceSelectSingle(QDialog):
msg = QtGui.QMessageBox() msg = QtGui.QMessageBox()
msg.setText(translate('OpenLP.SourceSelectForm', 'Delete entries for this projector')) msg.setText(translate('OpenLP.SourceSelectForm', 'Delete entries for this projector'))
msg.setInformativeText(translate('OpenLP.SourceSelectForm', msg.setInformativeText(translate('OpenLP.SourceSelectForm',
'Are you sure you want to delete ALL user-defined '), 'Are you sure you want to delete ALL user-defined '
translate('OpenLP.SourceSelectForm',
'source input text for this projector?')) 'source input text for this projector?'))
msg.setStandardButtons(msg.Cancel | msg.Ok) msg.setStandardButtons(msg.Cancel | msg.Ok)
msg.setDefaultButton(msg.Cancel) msg.setDefaultButton(msg.Cancel)

View File

@ -580,6 +580,7 @@ class SlideController(DisplayController, RegistryProperties):
self.display.setup() self.display.setup()
if self.is_live: if self.is_live:
self.__add_actions_to_widget(self.display) self.__add_actions_to_widget(self.display)
if self.display.audio_player:
self.display.audio_player.connectSlot(QtCore.SIGNAL('tick(qint64)'), self.on_audio_time_remaining) self.display.audio_player.connectSlot(QtCore.SIGNAL('tick(qint64)'), self.on_audio_time_remaining)
# The SlidePreview's ratio. # The SlidePreview's ratio.
try: try:
@ -834,26 +835,28 @@ class SlideController(DisplayController, RegistryProperties):
self.slide_list = {} self.slide_list = {}
if self.is_live: if self.is_live:
self.song_menu.menu().clear() self.song_menu.menu().clear()
self.display.audio_player.reset() if self.display.audio_player:
self.set_audio_items_visibility(False) self.display.audio_player.reset()
self.audio_pause_item.setChecked(False) self.set_audio_items_visibility(False)
# If the current item has background audio self.audio_pause_item.setChecked(False)
if self.service_item.is_capable(ItemCapabilities.HasBackgroundAudio): # If the current item has background audio
self.log_debug('Starting to play...') if self.service_item.is_capable(ItemCapabilities.HasBackgroundAudio):
self.display.audio_player.add_to_playlist(self.service_item.background_audio) self.log_debug('Starting to play...')
self.track_menu.clear() self.display.audio_player.add_to_playlist(self.service_item.background_audio)
for counter in range(len(self.service_item.background_audio)): self.track_menu.clear()
action = self.track_menu.addAction(os.path.basename(self.service_item.background_audio[counter])) for counter in range(len(self.service_item.background_audio)):
action.setData(counter) action = self.track_menu.addAction(
action.triggered.connect(self.on_track_triggered) os.path.basename(self.service_item.background_audio[counter]))
self.display.audio_player.repeat = \ action.setData(counter)
Settings().value(self.main_window.general_settings_section + '/audio repeat list') action.triggered.connect(self.on_track_triggered)
if Settings().value(self.main_window.general_settings_section + '/audio start paused'): self.display.audio_player.repeat = \
self.audio_pause_item.setChecked(True) Settings().value(self.main_window.general_settings_section + '/audio repeat list')
self.display.audio_player.pause() if Settings().value(self.main_window.general_settings_section + '/audio start paused'):
else: self.audio_pause_item.setChecked(True)
self.display.audio_player.play() self.display.audio_player.pause()
self.set_audio_items_visibility(True) else:
self.display.audio_player.play()
self.set_audio_items_visibility(True)
row = 0 row = 0
width = self.main_window.control_splitter.sizes()[self.split] width = self.main_window.control_splitter.sizes()[self.split]
for frame_number, frame in enumerate(self.service_item.get_frames()): for frame_number, frame in enumerate(self.service_item.get_frames()):

View File

@ -178,7 +178,7 @@ def update_reference_separators():
default_separators = [ default_separators = [
'|'.join([ '|'.join([
translate('BiblesPlugin', ':', 'Verse identifier e.g. Genesis 1 : 1 = Genesis Chapter 1 Verse 1'), translate('BiblesPlugin', ':', 'Verse identifier e.g. Genesis 1 : 1 = Genesis Chapter 1 Verse 1'),
translate('BiblesPlugin', 'v','Verse identifier e.g. Genesis 1 v 1 = Genesis Chapter 1 Verse 1'), translate('BiblesPlugin', 'v', 'Verse identifier e.g. Genesis 1 v 1 = Genesis Chapter 1 Verse 1'),
translate('BiblesPlugin', 'V', 'Verse identifier e.g. Genesis 1 V 1 = Genesis Chapter 1 Verse 1'), translate('BiblesPlugin', 'V', 'Verse identifier e.g. Genesis 1 V 1 = Genesis Chapter 1 Verse 1'),
translate('BiblesPlugin', 'verse', 'Verse identifier e.g. Genesis 1 verse 1 = Genesis Chapter 1 Verse 1'), translate('BiblesPlugin', 'verse', 'Verse identifier e.g. Genesis 1 verse 1 = Genesis Chapter 1 Verse 1'),
translate('BiblesPlugin', 'verses', translate('BiblesPlugin', 'verses',
@ -371,7 +371,7 @@ def parse_reference(reference, bible, language_selection, book_ref_id=False):
from_chapter = from_verse from_chapter = from_verse
from_verse = None from_verse = None
if to_chapter: if to_chapter:
if to_chapter < from_chapter: if from_chapter and to_chapter < from_chapter:
continue continue
else: else:
chapter = to_chapter chapter = to_chapter
@ -387,7 +387,7 @@ def parse_reference(reference, bible, language_selection, book_ref_id=False):
from_verse = 1 from_verse = 1
if not to_verse: if not to_verse:
to_verse = -1 to_verse = -1
if to_chapter > from_chapter: if to_chapter and to_chapter > from_chapter:
ref_list.append((book_ref_id, from_chapter, from_verse, -1)) ref_list.append((book_ref_id, from_chapter, from_verse, -1))
for i in range(from_chapter + 1, to_chapter): for i in range(from_chapter + 1, to_chapter):
ref_list.append((book_ref_id, i, 1, -1)) ref_list.append((book_ref_id, i, 1, -1))

View File

@ -314,6 +314,16 @@ class ImageMediaItem(MediaManagerItem):
return True return True
return return_value return return_value
def generate_thumbnail_path(self, image):
"""
Generate a path to the thumbnail
:param image: An instance of ImageFileNames
:return: A path to the thumbnail of type str
"""
ext = os.path.splitext(image.filename)[1].lower()
return os.path.join(self.service_path, '{}{}'.format(str(image.id), ext))
def load_full_list(self, images, initial_load=False, open_group=None): def load_full_list(self, images, initial_load=False, open_group=None):
""" """
Replace the list of images and groups in the interface. Replace the list of images and groups in the interface.
@ -335,27 +345,26 @@ class ImageMediaItem(MediaManagerItem):
# Sort the images by its filename considering language specific. # Sort the images by its filename considering language specific.
# characters. # characters.
images.sort(key=lambda image_object: get_locale_key(os.path.split(str(image_object.filename))[1])) images.sort(key=lambda image_object: get_locale_key(os.path.split(str(image_object.filename))[1]))
for imageFile in images: for image_file in images:
log.debug('Loading image: %s', imageFile.filename) log.debug('Loading image: %s', image_file.filename)
filename = os.path.split(imageFile.filename)[1] filename = os.path.split(image_file.filename)[1]
ext = os.path.splitext(imageFile.filename)[1].lower() thumb = self.generate_thumbnail_path(image_file)
thumb = os.path.join(self.service_path, "%s%s" % (str(imageFile.id), ext)) if not os.path.exists(image_file.filename):
if not os.path.exists(imageFile.filename):
icon = build_icon(':/general/general_delete.png') icon = build_icon(':/general/general_delete.png')
else: else:
if validate_thumb(imageFile.filename, thumb): if validate_thumb(image_file.filename, thumb):
icon = build_icon(thumb) icon = build_icon(thumb)
else: else:
icon = create_thumb(imageFile.filename, thumb) icon = create_thumb(image_file.filename, thumb)
item_name = QtGui.QTreeWidgetItem([filename]) item_name = QtGui.QTreeWidgetItem([filename])
item_name.setText(0, filename) item_name.setText(0, filename)
item_name.setIcon(0, icon) item_name.setIcon(0, icon)
item_name.setToolTip(0, imageFile.filename) item_name.setToolTip(0, image_file.filename)
item_name.setData(0, QtCore.Qt.UserRole, imageFile) item_name.setData(0, QtCore.Qt.UserRole, image_file)
if imageFile.group_id == 0: if image_file.group_id == 0:
self.list_view.addTopLevelItem(item_name) self.list_view.addTopLevelItem(item_name)
else: else:
group_items[imageFile.group_id].addChild(item_name) group_items[image_file.group_id].addChild(item_name)
if not initial_load: if not initial_load:
self.main_window.increment_progress_bar() self.main_window.increment_progress_bar()
if not initial_load: if not initial_load:
@ -549,24 +558,24 @@ class ImageMediaItem(MediaManagerItem):
# force a nonexistent theme # force a nonexistent theme
service_item.theme = -1 service_item.theme = -1
missing_items_file_names = [] missing_items_file_names = []
images_file_names = [] images = []
# Expand groups to images # Expand groups to images
for bitem in items: for bitem in items:
if isinstance(bitem.data(0, QtCore.Qt.UserRole), ImageGroups) or bitem.data(0, QtCore.Qt.UserRole) is None: if isinstance(bitem.data(0, QtCore.Qt.UserRole), ImageGroups) or bitem.data(0, QtCore.Qt.UserRole) is None:
for index in range(0, bitem.childCount()): for index in range(0, bitem.childCount()):
if isinstance(bitem.child(index).data(0, QtCore.Qt.UserRole), ImageFilenames): if isinstance(bitem.child(index).data(0, QtCore.Qt.UserRole), ImageFilenames):
images_file_names.append(bitem.child(index).data(0, QtCore.Qt.UserRole).filename) images.append(bitem.child(index).data(0, QtCore.Qt.UserRole))
elif isinstance(bitem.data(0, QtCore.Qt.UserRole), ImageFilenames): elif isinstance(bitem.data(0, QtCore.Qt.UserRole), ImageFilenames):
images_file_names.append(bitem.data(0, QtCore.Qt.UserRole).filename) images.append(bitem.data(0, QtCore.Qt.UserRole))
# Don't try to display empty groups # Don't try to display empty groups
if not images_file_names: if not images:
return False return False
# Find missing files # Find missing files
for filename in images_file_names: for image in images:
if not os.path.exists(filename): if not os.path.exists(image.filename):
missing_items_file_names.append(filename) missing_items_file_names.append(image.filename)
# We cannot continue, as all images do not exist. # We cannot continue, as all images do not exist.
if not images_file_names: if not images:
if not remote: if not remote:
critical_error_message_box( critical_error_message_box(
translate('ImagePlugin.MediaItem', 'Missing Image(s)'), translate('ImagePlugin.MediaItem', 'Missing Image(s)'),
@ -582,9 +591,10 @@ class ImageMediaItem(MediaManagerItem):
QtGui.QMessageBox.No: QtGui.QMessageBox.No:
return False return False
# Continue with the existing images. # Continue with the existing images.
for filename in images_file_names: for image in images:
name = os.path.split(filename)[1] name = os.path.split(image.filename)[1]
service_item.add_from_image(filename, name, background, os.path.join(self.service_path, name)) thumbnail = self.generate_thumbnail_path(image)
service_item.add_from_image(image.filename, name, background, thumbnail)
return True return True
def check_group_exists(self, new_group): def check_group_exists(self, new_group):

View File

@ -225,10 +225,10 @@ class PresentationMediaItem(MediaManagerItem):
self.clean_up_thumbnails(filepath) self.clean_up_thumbnails(filepath)
self.main_window.increment_progress_bar() self.main_window.increment_progress_bar()
self.main_window.finished_progress_bar() self.main_window.finished_progress_bar()
self.application.set_busy_cursor()
for row in row_list: for row in row_list:
self.list_view.takeItem(row) self.list_view.takeItem(row)
Settings().setValue(self.settings_section + '/presentations files', self.get_file_list()) Settings().setValue(self.settings_section + '/presentations files', self.get_file_list())
self.application.set_normal_cursor()
def clean_up_thumbnails(self, filepath): def clean_up_thumbnails(self, filepath):
""" """

View File

@ -134,7 +134,7 @@ class PresentationDocument(object):
""" """
# TODO: If statment can be removed when the upgrade path from 2.0.x to 2.2.x is no longer needed # TODO: If statment can be removed when the upgrade path from 2.0.x to 2.2.x is no longer needed
if Settings().value('presentations/thumbnail_scheme') == 'md5': if Settings().value('presentations/thumbnail_scheme') == 'md5':
folder = md5_hash('', self.file_path) folder = md5_hash(self.file_path)
else: else:
folder = self.get_file_name() folder = self.get_file_name()
return os.path.join(self.controller.thumbnail_folder, folder) return os.path.join(self.controller.thumbnail_folder, folder)
@ -145,7 +145,7 @@ class PresentationDocument(object):
""" """
# TODO: If statment can be removed when the upgrade path from 2.0.x to 2.2.x is no longer needed # TODO: If statment can be removed when the upgrade path from 2.0.x to 2.2.x is no longer needed
if Settings().value('presentations/thumbnail_scheme') == 'md5': if Settings().value('presentations/thumbnail_scheme') == 'md5':
folder = md5_hash('', self.file_path) folder = md5_hash(self.file_path)
else: else:
folder = folder = self.get_file_name() folder = folder = self.get_file_name()
return os.path.join(self.controller.temp_folder, folder) return os.path.join(self.controller.temp_folder, folder)

View File

@ -143,7 +143,10 @@ class PresentationPlugin(Plugin):
super().app_startup() super().app_startup()
files_from_config = Settings().value('presentations/presentations files') files_from_config = Settings().value('presentations/presentations files')
for file in files_from_config: for file in files_from_config:
self.media_item.clean_up_thumbnails(file) try:
self.media_item.clean_up_thumbnails(file)
except AttributeError:
pass
self.media_item.list_view.clear() self.media_item.list_view.clear()
Settings().setValue('presentations/thumbnail_scheme', 'md5') Settings().setValue('presentations/thumbnail_scheme', 'md5')
self.media_item.validate_and_load(files_from_config) self.media_item.validate_and_load(files_from_config)

View File

@ -25,7 +25,9 @@ Presentationmanager song files into the current database.
""" """
import os import os
from lxml import objectify import re
import chardet
from lxml import objectify, etree
from openlp.core.ui.wizard import WizardStrings from openlp.core.ui.wizard import WizardStrings
from .songimport import SongImport from .songimport import SongImport
@ -42,7 +44,18 @@ class PresentationManagerImport(SongImport):
if self.stop_import_flag: if self.stop_import_flag:
return return
self.import_wizard.increment_progress_bar(WizardStrings.ImportingType % os.path.basename(file_path)) self.import_wizard.increment_progress_bar(WizardStrings.ImportingType % os.path.basename(file_path))
root = objectify.parse(open(file_path, 'rb')).getroot() try:
tree = etree.parse(file_path, parser=etree.XMLParser(recover=True))
except etree.XMLSyntaxError:
# Try to detect encoding and use it
file = open(file_path, mode='rb')
encoding = chardet.detect(file.read())['encoding']
file.close()
# Open file with detected encoding and remove encoding declaration
text = open(file_path, mode='r', encoding=encoding).read()
text = re.sub('.+\?>\n', '', text)
tree = etree.fromstring(text, parser=etree.XMLParser(recover=True))
root = objectify.fromstring(etree.tostring(tree))
self.process_song(root) self.process_song(root)
def process_song(self, root): def process_song(self, root):

View File

@ -86,6 +86,7 @@
<file>openlp-logo-64x64.png</file> <file>openlp-logo-64x64.png</file>
<file>openlp-logo-128x128.png</file> <file>openlp-logo-128x128.png</file>
<file>openlp-logo-256x256.png</file> <file>openlp-logo-256x256.png</file>
<file>openlp-logo.svg</file>
</qresource> </qresource>
<qresource prefix="graphics"> <qresource prefix="graphics">
<file>exception.png</file> <file>exception.png</file>

View File

@ -23,6 +23,8 @@
Package to test the openlp.core.ui.projector.networkutils package. Package to test the openlp.core.ui.projector.networkutils package.
""" """
import os
from unittest import TestCase from unittest import TestCase
from openlp.core.common import verify_ip_address, md5_hash, qmd5_hash from openlp.core.common import verify_ip_address, md5_hash, qmd5_hash
@ -30,6 +32,8 @@ from openlp.core.common import verify_ip_address, md5_hash, qmd5_hash
salt = '498e4a67' salt = '498e4a67'
pin = 'JBMIAProjectorLink' pin = 'JBMIAProjectorLink'
test_hash = '5d8409bc1c3fa39749434aa3a5c38682' test_hash = '5d8409bc1c3fa39749434aa3a5c38682'
test_non_ascii_string = '이것은 한국어 시험 문자열'
test_non_ascii_hash = 'fc00c7912976f6e9c19099b514ced201'
ip4_loopback = '127.0.0.1' ip4_loopback = '127.0.0.1'
ip4_local = '192.168.1.1' ip4_local = '192.168.1.1'
@ -120,7 +124,7 @@ class testProjectorUtilities(TestCase):
Test MD5 hash from salt+data pass (python) Test MD5 hash from salt+data pass (python)
""" """
# WHEN: Given a known salt+data # WHEN: Given a known salt+data
hash_ = md5_hash(salt=salt, data=pin) hash_ = md5_hash(salt=salt.encode('ascii'), data=pin.encode('ascii'))
# THEN: Validate return has is same # THEN: Validate return has is same
self.assertEquals(hash_, test_hash, 'MD5 should have returned a good hash') self.assertEquals(hash_, test_hash, 'MD5 should have returned a good hash')
@ -130,7 +134,7 @@ class testProjectorUtilities(TestCase):
Test MD5 hash from salt+data fail (python) Test MD5 hash from salt+data fail (python)
""" """
# WHEN: Given a different salt+hash # WHEN: Given a different salt+hash
hash_ = md5_hash(salt=pin, data=salt) hash_ = md5_hash(salt=pin.encode('ascii'), data=salt.encode('ascii'))
# THEN: return data is different # THEN: return data is different
self.assertNotEquals(hash_, test_hash, 'MD5 should have returned a bad hash') self.assertNotEquals(hash_, test_hash, 'MD5 should have returned a bad hash')
@ -140,17 +144,37 @@ class testProjectorUtilities(TestCase):
Test MD5 hash from salt+data pass (Qt) Test MD5 hash from salt+data pass (Qt)
""" """
# WHEN: Given a known salt+data # WHEN: Given a known salt+data
hash_ = qmd5_hash(salt=salt, data=pin) hash_ = qmd5_hash(salt=salt.encode('ascii'), data=pin.encode('ascii'))
# THEN: Validate return has is same # THEN: Validate return has is same
self.assertEquals(hash_, test_hash, 'Qt-MD5 should have returned a good hash') self.assertEquals(hash_.decode('ascii'), test_hash, 'Qt-MD5 should have returned a good hash')
def test_qmd5_hash_bad(self): def test_qmd5_hash_bad(self):
""" """
Test MD5 hash from salt+hash fail (Qt) Test MD5 hash from salt+hash fail (Qt)
""" """
# WHEN: Given a different salt+hash # WHEN: Given a different salt+hash
hash_ = qmd5_hash(salt=pin, data=salt) hash_ = qmd5_hash(salt=pin.encode('ascii'), data=salt.encode('ascii'))
# THEN: return data is different # THEN: return data is different
self.assertNotEquals(hash_, test_hash, 'Qt-MD5 should have returned a bad hash') self.assertNotEquals(hash_.decode('ascii'), test_hash, 'Qt-MD5 should have returned a bad hash')
def test_md5_non_ascii_string(self):
"""
Test MD5 hash with non-ascii string - bug 1417809
"""
# WHEN: Non-ascii string is hashed
hash_ = md5_hash(salt=test_non_ascii_string.encode('utf-8'), data=None)
# THEN: Valid MD5 hash should be returned
self.assertEqual(hash_, test_non_ascii_hash, 'MD5 should have returned a valid hash')
def test_qmd5_non_ascii_string(self):
"""
Test MD5 hash with non-ascii string - bug 1417809
"""
# WHEN: Non-ascii string is hashed
hash_ = md5_hash(salt=test_non_ascii_string.encode('utf-8'), data=None)
# THEN: Valid MD5 hash should be returned
self.assertEqual(hash_, test_non_ascii_hash, 'Qt-MD5 should have returned a valid hash')

View File

@ -128,3 +128,21 @@ class TestMainWindow(TestCase, TestMixin):
# THEN the main window's title should be set to the # THEN the main window's title should be set to the
self.assertEqual(self.main_window.windowTitle(), '%s - %s' % (UiStrings().OLPV2x, 'test.osz'), self.assertEqual(self.main_window.windowTitle(), '%s - %s' % (UiStrings().OLPV2x, 'test.osz'),
'The main window\'s title should be set to "<the contents of UiStrings().OLPV2x> - test.osz"') 'The main window\'s title should be set to "<the contents of UiStrings().OLPV2x> - test.osz"')
def mainwindow_configuration_test(self):
"""
Check that the Main Window initialises the Registry Correctly
"""
# GIVEN: A built main window
# WHEN: you check the started functions
# THEN: the following registry functions should have been registered
self.assertEqual(len(self.registry.service_list), 6, 'The registry should have 6 services.')
self.assertEqual(len(self.registry.functions_list), 16, 'The registry should have 16 functions')
self.assertTrue('application' in self.registry.service_list, 'The application should have been registered.')
self.assertTrue('main_window' in self.registry.service_list, 'The main_window should have been registered.')
self.assertTrue('media_controller' in self.registry.service_list, 'The media_controller should have been '
'registered.')
self.assertTrue('plugin_manager' in self.registry.service_list,
'The plugin_manager should have been registered.')

View File

@ -479,6 +479,34 @@ class TestSlideController(TestCase):
mocked_preview_widget.current_slide_number.assert_called_with() mocked_preview_widget.current_slide_number.assert_called_with()
mocked_process_item.assert_called_once_with(mocked_item, 7) mocked_process_item.assert_called_once_with(mocked_item, 7)
def on_slide_blank_test(self):
"""
Test on_slide_blank
"""
# GIVEN: An instance of SlideController and a mocked on_blank_display
slide_controller = SlideController(None)
slide_controller.on_blank_display = MagicMock()
# WHEN: Calling on_slide_blank
slide_controller.on_slide_blank()
# THEN: on_blank_display should have been called with True
slide_controller.on_blank_display.assert_called_once_with(True)
def on_slide_unblank_test(self):
"""
Test on_slide_unblank
"""
# GIVEN: An instance of SlideController and a mocked on_blank_display
slide_controller = SlideController(None)
slide_controller.on_blank_display = MagicMock()
# WHEN: Calling on_slide_unblank
slide_controller.on_slide_unblank()
# THEN: on_blank_display should have been called with False
slide_controller.on_blank_display.assert_called_once_with(False)
def on_slide_selected_index_no_service_item_test(self): def on_slide_selected_index_no_service_item_test(self):
""" """
Test that when there is no service item, the on_slide_selected_index() method returns immediately Test that when there is no service item, the on_slide_selected_index() method returns immediately

View File

@ -23,7 +23,7 @@
Package to test the openlp.core.ui.media.webkitplayer package. Package to test the openlp.core.ui.media.webkitplayer package.
""" """
from unittest import TestCase from unittest import TestCase
from tests.functional import patch from tests.functional import MagicMock, patch
from openlp.core.ui.media.webkitplayer import WebkitPlayer from openlp.core.ui.media.webkitplayer import WebkitPlayer
@ -33,32 +33,36 @@ class TestWebkitPlayer(TestCase):
Test the functions in the :mod:`webkitplayer` module. Test the functions in the :mod:`webkitplayer` module.
""" """
def check_available_mac_test(self): def check_available_video_disabled_test(self):
""" """
Simple test of webkitplayer availability on Mac OS X Test of webkit video unavailability
""" """
# GIVEN: A WebkitPlayer and a mocked is_macosx # GIVEN: A WebkitPlayer instance and a mocked QWebPage
with patch('openlp.core.ui.media.webkitplayer.is_macosx') as mocked_is_macosx: mocked_qwebpage = MagicMock()
mocked_is_macosx.return_value = True mocked_qwebpage.mainFrame().evaluateJavaScript.return_value = '[object HTMLUnknownElement]'
with patch('openlp.core.ui.media.webkitplayer.QtWebKit.QWebPage', **{'return_value': mocked_qwebpage}):
webkit_player = WebkitPlayer(None) webkit_player = WebkitPlayer(None)
# WHEN: An checking if the player is available # WHEN: An checking if the player is available
available = webkit_player.check_available() available = webkit_player.check_available()
# THEN: The player should not be available on Mac OS X # THEN: The player should not be available when '[object HTMLUnknownElement]' is returned
self.assertEqual(False, available, 'The WebkitPlayer should not be available on Mac OS X.') self.assertEqual(False, available,
'The WebkitPlayer should not be available when video feature detection fails')
def check_available_non_mac_test(self): def check_available_video_enabled_test(self):
""" """
Simple test of webkitplayer availability when not on Mac OS X Test of webkit video availability
""" """
# GIVEN: A WebkitPlayer and a mocked is_macosx # GIVEN: A WebkitPlayer instance and a mocked QWebPage
with patch('openlp.core.ui.media.webkitplayer.is_macosx') as mocked_is_macosx: mocked_qwebpage = MagicMock()
mocked_is_macosx.return_value = False mocked_qwebpage.mainFrame().evaluateJavaScript.return_value = '[object HTMLVideoElement]'
with patch('openlp.core.ui.media.webkitplayer.QtWebKit.QWebPage', **{'return_value': mocked_qwebpage}):
webkit_player = WebkitPlayer(None) webkit_player = WebkitPlayer(None)
# WHEN: An checking if the player is available # WHEN: An checking if the player is available
available = webkit_player.check_available() available = webkit_player.check_available()
# THEN: The player should be available when not on Mac OS X # THEN: The player should be available when '[object HTMLVideoElement]' is returned
self.assertEqual(True, available, 'The WebkitPlayer should be available when not on Mac OS X.') self.assertEqual(True, available,
'The WebkitPlayer should be available when video feature detection passes')

View File

@ -25,7 +25,7 @@ Functional tests to test the AppLocation class and related methods.
import os import os
from unittest import TestCase from unittest import TestCase
from openlp.core.utils import clean_filename, get_filesystem_encoding, get_locale_key, \ from openlp.core.utils import clean_filename, delete_file, get_filesystem_encoding, get_locale_key, \
get_natural_key, split_filename, _get_user_agent, get_web_page, get_uno_instance, add_actions get_natural_key, split_filename, _get_user_agent, get_web_page, get_uno_instance, add_actions
from tests.functional import MagicMock, patch from tests.functional import MagicMock, patch
@ -184,6 +184,59 @@ class TestUtils(TestCase):
# THEN: The file name should be cleaned. # THEN: The file name should be cleaned.
self.assertEqual(wanted_name, result, 'The file name should not contain any special characters.') self.assertEqual(wanted_name, result, 'The file name should not contain any special characters.')
def delete_file_no_path_test(self):
""""
Test the delete_file function when called with out a valid path
"""
# GIVEN: A blank path
# WEHN: Calling delete_file
result = delete_file('')
# THEN: delete_file should return False
self.assertFalse(result, "delete_file should return False when called with ''")
def delete_file_path_success_test(self):
""""
Test the delete_file function when it successfully deletes a file
"""
# GIVEN: A mocked os which returns True when os.path.exists is called
with patch('openlp.core.utils.os', **{'path.exists.return_value': False}):
# WHEN: Calling delete_file with a file path
result = delete_file('path/file.ext')
# THEN: delete_file should return True
self.assertTrue(result, 'delete_file should return True when it successfully deletes a file')
def delete_file_path_no_file_exists_test(self):
""""
Test the delete_file function when the file to remove does not exist
"""
# GIVEN: A mocked os which returns False when os.path.exists is called
with patch('openlp.core.utils.os', **{'path.exists.return_value': False}):
# WHEN: Calling delete_file with a file path
result = delete_file('path/file.ext')
# THEN: delete_file should return True
self.assertTrue(result, 'delete_file should return True when the file doesnt exist')
def delete_file_path_exception_test(self):
""""
Test the delete_file function when os.remove raises an exception
"""
# GIVEN: A mocked os which returns True when os.path.exists is called and raises an OSError when os.remove is
# called.
with patch('openlp.core.utils.os', **{'path.exists.return_value': True, 'path.exists.side_effect': OSError}), \
patch('openlp.core.utils.log') as mocked_log:
# WHEN: Calling delete_file with a file path
result = delete_file('path/file.ext')
# THEN: delete_file should log and exception and return False
self.assertEqual(mocked_log.exception.call_count, 1)
self.assertFalse(result, 'delete_file should return False when os.remove raises an OSError')
def get_locale_key_test(self): def get_locale_key_test(self):
""" """
Test the get_locale_key(string) function Test the get_locale_key(string) function

View File

@ -46,3 +46,5 @@ class TestPresentationManagerFileImport(SongImportTestHelper):
self.load_external_result_data(os.path.join(TEST_PATH, 'Great Is Thy Faithfulness.json'))) self.load_external_result_data(os.path.join(TEST_PATH, 'Great Is Thy Faithfulness.json')))
self.file_import([os.path.join(TEST_PATH, 'Agnus Dei.sng')], self.file_import([os.path.join(TEST_PATH, 'Agnus Dei.sng')],
self.load_external_result_data(os.path.join(TEST_PATH, 'Agnus Dei.json'))) self.load_external_result_data(os.path.join(TEST_PATH, 'Agnus Dei.json')))
self.file_import([os.path.join(TEST_PATH, 'Amazing Grace.sng')],
self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json')))

View File

@ -24,6 +24,7 @@ Package to test the openlp.core.__init__ package.
""" """
from optparse import Values from optparse import Values
import os import os
import sys
from unittest import TestCase from unittest import TestCase
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
@ -118,12 +119,27 @@ class TestInit(TestCase, TestMixin):
""" """
Test that parse_options parses short options correctly Test that parse_options parses short options correctly
""" """
# GIVEN: A list of vaild short options # GIVEN: A list of valid short options
options = ['-e', '-l', 'debug', '-pd', '-s', 'style', 'extra', 'qt', 'args'] options = ['-e', '-l', 'debug', '-pd', '-s', 'style', 'extra', 'qt', 'args']
# WHEN: Calling parse_options # WHEN: Calling parse_options
resluts = parse_options(options) results = parse_options(options)
# THEN: A tuple should be returned with the parsed options and left over args # THEN: A tuple should be returned with the parsed options and left over args
self.assertEqual(resluts, (Values({'no_error_form': True, 'dev_version': True, 'portable': True, self.assertEqual(results, (Values({'no_error_form': True, 'dev_version': True, 'portable': True,
'style': 'style', 'loglevel': 'debug'}), ['extra', 'qt', 'args']))
def parse_options_valid_argv_short_options_test(self):
"""
Test that parse_options parses valid short options correctly when passed through sys.argv
"""
# GIVEN: A list of valid options
options = ['openlp.py', '-e', '-l', 'debug', '-pd', '-s', 'style', 'extra', 'qt', 'args']
# WHEN: Passing in the options through sys.argv and calling parse_args with None
with patch.object(sys, 'argv', options):
results = parse_options(None)
# THEN: parse_args should return a tuple of valid options and of left over options that OpenLP does not use
self.assertEqual(results, (Values({'no_error_form': True, 'dev_version': True, 'portable': True,
'style': 'style', 'loglevel': 'debug'}), ['extra', 'qt', 'args'])) 'style': 'style', 'loglevel': 'debug'}), ['extra', 'qt', 'args']))

View File

@ -29,11 +29,18 @@ log = logging.getLogger(__name__)
log.debug('test_projectorsourceform loaded') log.debug('test_projectorsourceform loaded')
from unittest import TestCase from unittest import TestCase
from PyQt4 import QtGui
from PyQt4.QtGui import QDialog
from tests.functional import patch
from tests.functional.openlp_core_lib.test_projectordb import tmpfile
from tests.helpers.testmixin import TestMixin from tests.helpers.testmixin import TestMixin
from openlp.core.lib.projector.constants import PJLINK_DEFAULT_CODES, PJLINK_DEFAULT_SOURCES from tests.resources.projector.data import TEST_DB, TEST1_DATA
from openlp.core.ui.projector.sourceselectform import source_group from openlp.core.common import Registry, Settings
from openlp.core.lib.projector.db import ProjectorDB
from openlp.core.lib.projector.constants import PJLINK_DEFAULT_CODES, PJLINK_DEFAULT_SOURCES
from openlp.core.ui.projector.sourceselectform import source_group, SourceSelectSingle
def build_source_dict(): def build_source_dict():
@ -54,6 +61,37 @@ class ProjectorSourceFormTest(TestCase, TestMixin):
""" """
Test class for the Projector Source Select form module Test class for the Projector Source Select form module
""" """
@patch('openlp.core.lib.projector.db.init_url')
def setUp(self, mocked_init_url):
"""
Set up anything necessary for all tests
"""
mocked_init_url.start()
mocked_init_url.return_value = 'sqlite:///{}'.format(tmpfile)
self.build_settings()
self.setup_application()
Registry.create()
# Do not try to recreate if we've already been created from a previous test
if not hasattr(self, 'projectordb'):
self.projectordb = ProjectorDB()
# Retrieve/create a database record
self.projector = self.projectordb.get_projector_by_ip(TEST1_DATA.ip)
if not self.projector:
self.projectordb.add_projector(projector=TEST1_DATA)
self.projector = self.projectordb.get_projector_by_ip(TEST1_DATA.ip)
self.projector.dbid = self.projector.id
self.projector.db_item = self.projector
def tearDown(self):
"""
Close database session.
Delete all C++ objects at end so we don't segfault.
"""
self.projectordb.session.close()
del(self.projectordb)
del(self.projector)
self.destroy_settings()
def source_dict_test(self): def source_dict_test(self):
""" """
Test that source list dict returned from sourceselectform module is a valid dict with proper entries Test that source list dict returned from sourceselectform module is a valid dict with proper entries
@ -70,3 +108,43 @@ class ProjectorSourceFormTest(TestCase, TestMixin):
# THEN: return dictionary should match test dictionary # THEN: return dictionary should match test dictionary
self.assertEquals(check, build_source_dict(), self.assertEquals(check, build_source_dict(),
"Source group dictionary should match test dictionary") "Source group dictionary should match test dictionary")
@patch.object(QDialog, 'exec_')
def source_select_edit_button_test(self, mocked_qdialog):
"""
Test source select form edit has Ok, Cancel, Reset, and Revert buttons
"""
# GIVEN: Initial setup and mocks
self.projector.source_available = ['11', ]
self.projector.source = '11'
# WHEN we create a source select widget and set edit=True
select_form = SourceSelectSingle(parent=None, projectordb=self.projectordb)
select_form.edit = True
select_form.exec_(projector=self.projector)
projector = select_form.projector
# THEN: Verify all 4 buttons are available
self.assertEquals(len(select_form.button_box.buttons()), 4,
'SourceSelect dialog box should have "OK", "Cancel" '
'"Rest", and "Revert" buttons available')
@patch.object(QDialog, 'exec_')
def source_select_noedit_button_test(self, mocked_qdialog):
"""
Test source select form view has OK and Cancel buttons only
"""
# GIVEN: Initial setup and mocks
self.projector.source_available = ['11', ]
self.projector.source = '11'
# WHEN we create a source select widget and set edit=False
select_form = SourceSelectSingle(parent=None, projectordb=self.projectordb)
select_form.edit = False
select_form.exec_(projector=self.projector)
projector = select_form.projector
# THEN: Verify only 2 buttons are available
self.assertEquals(len(select_form.button_box.buttons()), 2,
'SourceSelect dialog box should only have "OK" '
'and "Cancel" buttons available')

View File

@ -109,3 +109,13 @@ class TestBibleManager(TestCase, TestMixin):
results = parse_reference('Raoul 1', self.manager.db_cache['tests'], MagicMock()) results = parse_reference('Raoul 1', self.manager.db_cache['tests'], MagicMock())
# THEN a verse array should be returned # THEN a verse array should be returned
self.assertEqual(False, results, "The bible Search should return False") self.assertEqual(False, results, "The bible Search should return False")
def parse_reference_five_test(self):
"""
Test the parse_reference method with 1 Timothy 1:3-end
"""
# GIVEN given a bible in the bible manager
# WHEN asking to parse the bible reference
results = parse_reference('1 Timothy 1:3-end', self.manager.db_cache['tests'], MagicMock(), 54)
# THEN a verse array should be returned
self.assertEqual([(54, 1, 3, -1)], results, "The bible verses should matches the expected results")

View File

@ -0,0 +1,29 @@
{
"title": "Amazing Grace",
"authors": [
"John Newton"
],
"verse_order_list": ["v1", "v2", "v3", "v4", "v5"],
"verses": [
[
"Amazing grace! How sweet the sound!\nThat saved a wretch like me!\nI once was lost, but now am found;\nWas blind, but now I see.",
"v1"
],
[
"'Twas grace that taught my heart to fear,\nAnd grace my fears relieved.\nHow precious did that grace appear,\nThe hour I first believed.",
"v2"
],
[
"The Lord has promised good to me,\nHis Word my hope secures.\nHe will my shield and portion be\nAs long as life endures.",
"v3"
],
[
"Thro' many dangers, toils and snares\nI have already come.\n'Tis grace that brought me safe thus far,\nAnd grace will lead me home.",
"v4"
],
[
"When we've been there ten thousand years,\nBright shining as the sun,\nWe've no less days to sing God's praise,\nThan when we first begun.",
"v5"
]
]
}

View File

@ -23,9 +23,12 @@
The :mod:`tests.resources.projector.data file contains test data The :mod:`tests.resources.projector.data file contains test data
""" """
import os
from openlp.core.lib.projector.db import Projector from openlp.core.lib.projector.db import Projector
# Test data # Test data
TEST_DB = os.path.join('tmp', 'openlp-test-projectordb.sql')
TEST1_DATA = Projector(ip='111.111.111.111', TEST1_DATA = Projector(ip='111.111.111.111',
port='1111', port='1111',
pin='1111', pin='1111',