Merge trunk

This commit is contained in:
Samuel Mehrbrodt 2014-10-13 19:40:12 +02:00
commit 806bb5973e
54 changed files with 1529 additions and 409 deletions

View File

@ -36,10 +36,9 @@ if __name__ == '__main__':
"""
Instantiate and run the application.
"""
# Mac OS X passes arguments like '-psn_XXXX' to gui application.
# This argument is process serial number. However, this causes
# conflict with other OpenLP arguments. Since we do not use this
# argument we can delete it to avoid any potential conflicts.
# Mac OS X passes arguments like '-psn_XXXX' to the application. This argument is actually a process serial number.
# However, this causes a conflict with other OpenLP arguments. Since we do not use this argument we can delete it
# to avoid any potential conflicts.
if sys.platform.startswith('darwin'):
sys.argv = [x for x in sys.argv if not x.startswith('-psn')]
main()

View File

@ -59,7 +59,7 @@ __all__ = ['OpenLP', 'main']
log = logging.getLogger()
NT_REPAIR_STYLESHEET = """
WIN_REPAIR_STYLESHEET = """
QMainWindow::separator
{
border: none;
@ -127,7 +127,7 @@ class OpenLP(OpenLPMixin, QtGui.QApplication):
'QTableWidget, QListWidget, QTreeWidget {alternate-background-color: ' + base_color.name() + ';}\n'
application_stylesheet += alternate_rows_repair_stylesheet
if is_win():
application_stylesheet += NT_REPAIR_STYLESHEET
application_stylesheet += WIN_REPAIR_STYLESHEET
if application_stylesheet:
self.setStyleSheet(application_stylesheet)
show_splash = Settings().value('core/show splash')

View File

@ -33,6 +33,7 @@ import logging
import inspect
from openlp.core.common import trace_error_handler
DO_NOT_TRACE_EVENTS = ['timerEvent', 'paintEvent', 'drag_enter_event', 'drop_event', 'on_controller_size_changed',
'preview_size_changed', 'resizeEvent']
@ -41,11 +42,8 @@ class OpenLPMixin(object):
"""
Base Calling object for OpenLP classes.
"""
def __init__(self, parent):
try:
super(OpenLPMixin, self).__init__(parent)
except TypeError:
super(OpenLPMixin, self).__init__()
def __init__(self, *args, **kwargs):
super(OpenLPMixin, self).__init__(*args, **kwargs)
self.logger = logging.getLogger("%s.%s" % (self.__module__, self.__class__.__name__))
if self.logger.getEffectiveLevel() == logging.DEBUG:
for name, m in inspect.getmembers(self, inspect.ismethod):

View File

@ -145,11 +145,13 @@ def build_icon(icon):
return button_icon
def image_to_byte(image):
def image_to_byte(image, base_64=True):
"""
Resize an image to fit on the current screen for the web and returns it as a byte stream.
:param image: The image to converted.
:param base_64: If True returns the image as Base64 bytes, otherwise the image is returned as a byte array.
To preserve original intention, this defaults to True
"""
log.debug('image_to_byte - start')
byte_array = QtCore.QByteArray()
@ -158,6 +160,8 @@ def image_to_byte(image):
buffie.open(QtCore.QIODevice.WriteOnly)
image.save(buffie, "PNG")
log.debug('image_to_byte - end')
if not base_64:
return byte_array
# convert to base64 encoding so does not get missed!
return bytes(byte_array.toBase64()).decode('utf-8')

View File

@ -106,7 +106,7 @@ class Image(object):
"""
secondary_priority = 0
def __init__(self, path, source, background):
def __init__(self, path, source, background, width=-1, height=-1):
"""
Create an image for the :class:`ImageManager`'s cache.
@ -115,7 +115,8 @@ class Image(object):
:class:`~openlp.core.lib.ImageSource` class.
:param background: A ``QtGui.QColor`` object specifying the colour to be used to fill the gabs if the image's
ratio does not match with the display ratio.
:param width: The width of the image, defaults to -1 meaning that the screen width will be used.
:param height: The height of the image, defaults to -1 meaning that the screen height will be used.
"""
self.path = path
self.image = None
@ -124,6 +125,8 @@ class Image(object):
self.source = source
self.background = background
self.timestamp = 0
self.width = width
self.height = height
# FIXME: We assume that the path exist. The caller has to take care that it exists!
if os.path.exists(path):
self.timestamp = os.stat(path).st_mtime
@ -210,13 +213,13 @@ class ImageManager(QtCore.QObject):
image.background = background
self._reset_image(image)
def update_image_border(self, path, source, background):
def update_image_border(self, path, source, background, width=-1, height=-1):
"""
Border has changed so update the image affected.
"""
log.debug('update_image_border')
# Mark the image as dirty for a rebuild by setting the image and byte stream to None.
image = self._cache[(path, source)]
image = self._cache[(path, source, width, height)]
if image.source == source:
image.background = background
self._reset_image(image)
@ -237,12 +240,12 @@ class ImageManager(QtCore.QObject):
if not self.image_thread.isRunning():
self.image_thread.start()
def get_image(self, path, source):
def get_image(self, path, source, width=-1, height=-1):
"""
Return the ``QImage`` from the cache. If not present wait for the background thread to process it.
"""
log.debug('getImage %s' % path)
image = self._cache[(path, source)]
image = self._cache[(path, source, width, height)]
if image.image is None:
self._conversion_queue.modify_priority(image, Priority.High)
# make sure we are running and if not give it a kick
@ -257,12 +260,12 @@ class ImageManager(QtCore.QObject):
self._conversion_queue.modify_priority(image, Priority.Low)
return image.image
def get_image_bytes(self, path, source):
def get_image_bytes(self, path, source, width=-1, height=-1):
"""
Returns the byte string for an image. If not present wait for the background thread to process it.
"""
log.debug('get_image_bytes %s' % path)
image = self._cache[(path, source)]
image = self._cache[(path, source, width, height)]
if image.image_bytes is None:
self._conversion_queue.modify_priority(image, Priority.Urgent)
# make sure we are running and if not give it a kick
@ -272,14 +275,14 @@ class ImageManager(QtCore.QObject):
time.sleep(0.1)
return image.image_bytes
def add_image(self, path, source, background):
def add_image(self, path, source, background, width=-1, height=-1):
"""
Add image to cache if it is not already there.
"""
log.debug('add_image %s' % path)
if not (path, source) in self._cache:
image = Image(path, source, background)
self._cache[(path, source)] = image
if not (path, source, width, height) in self._cache:
image = Image(path, source, background, width, height)
self._cache[(path, source, width, height)] = image
self._conversion_queue.put((image.priority, image.secondary_priority, image))
# Check if the there are any images with the same path and check if the timestamp has changed.
for image in list(self._cache.values()):
@ -308,7 +311,10 @@ class ImageManager(QtCore.QObject):
image = self._conversion_queue.get()[2]
# Generate the QImage for the image.
if image.image is None:
image.image = resize_image(image.path, self.width, self.height, image.background)
# Let's see if the image was requested with specific dimensions
width = self.width if image.width == -1 else image.width
height = self.height if image.height == -1 else image.height
image.image = resize_image(image.path, width, height, image.background)
# Set the priority to Lowest and stop here as we need to process more important images first.
if image.priority == Priority.Normal:
self._conversion_queue.modify_priority(image, Priority.Lowest)

View File

@ -39,8 +39,8 @@ import uuid
from PyQt4 import QtGui
from openlp.core.common import RegistryProperties, Settings, translate
from openlp.core.lib import ImageSource, build_icon, clean_tags, expand_tags
from openlp.core.common import RegistryProperties, Settings, translate, AppLocation
from openlp.core.lib import ImageSource, build_icon, clean_tags, expand_tags, create_thumb
log = logging.getLogger(__name__)
@ -112,7 +112,17 @@ class ItemCapabilities(object):
The capability to edit the title of the item
``IsOptical``
.Determines is the service_item is based on an optical device
Determines is the service_item is based on an optical device
``HasDisplayTitle``
The item contains 'displaytitle' on every frame which should be
preferred over 'title' when displaying the item
``HasNotes``
The item contains 'notes'
``HasThumbnails``
The item has related thumbnails available
"""
CanPreview = 1
@ -133,6 +143,9 @@ class ItemCapabilities(object):
CanAutoStartForLive = 16
CanEditTitle = 17
IsOptical = 18
HasDisplayTitle = 19
HasNotes = 20
HasThumbnails = 21
class ServiceItem(RegistryProperties):
@ -272,18 +285,22 @@ class ServiceItem(RegistryProperties):
self.raw_footer = []
self.foot_text = '<br>'.join([_f for _f in self.raw_footer if _f])
def add_from_image(self, path, title, background=None):
def add_from_image(self, path, title, background=None, thumbnail=None):
"""
Add an image slide to the service item.
:param path: The directory in which the image file is located.
:param title: A title for the slide in the service item.
:param background:
:param thumbnail: Optional alternative thumbnail, used for remote thumbnails.
"""
if background:
self.image_border = background
self.service_item_type = ServiceItemType.Image
self._raw_frames.append({'title': title, 'path': path})
if not thumbnail:
self._raw_frames.append({'title': title, 'path': path})
else:
self._raw_frames.append({'title': title, 'path': path, 'image': thumbnail})
self.image_manager.add_image(path, ImageSource.ImagePlugin, self.image_border)
self._new_item()
@ -301,16 +318,22 @@ class ServiceItem(RegistryProperties):
self._raw_frames.append({'title': title, 'raw_slide': raw_slide, 'verseTag': verse_tag})
self._new_item()
def add_from_command(self, path, file_name, image):
def add_from_command(self, path, file_name, image, display_title=None, notes=None):
"""
Add a slide from a command.
:param path: The title of the slide in the service item.
:param file_name: The title of the slide in the service item.
:param image: The command of/for the slide.
:param display_title: Title to show in gui/webinterface, optional.
:param notes: Notes to show in the webinteface, optional.
"""
self.service_item_type = ServiceItemType.Command
self._raw_frames.append({'title': file_name, 'image': image, 'path': path})
# If the item should have a display title but this frame doesn't have one, we make one up
if self.is_capable(ItemCapabilities.HasDisplayTitle) and not display_title:
display_title = translate('OpenLP.ServiceItem', '[slide %d]') % (len(self._raw_frames) + 1)
self._raw_frames.append({'title': file_name, 'image': image, 'path': path,
'display_title': display_title, 'notes': notes})
self._new_item()
def get_service_repr(self, lite_save):
@ -354,7 +377,8 @@ class ServiceItem(RegistryProperties):
service_data = [slide['title'] for slide in self._raw_frames]
elif self.service_item_type == ServiceItemType.Command:
for slide in self._raw_frames:
service_data.append({'title': slide['title'], 'image': slide['image'], 'path': slide['path']})
service_data.append({'title': slide['title'], 'image': slide['image'], 'path': slide['path'],
'display_title': slide['display_title'], 'notes': slide['notes']})
return {'header': service_header, 'data': service_data}
def set_from_service(self, service_item, path=None):
@ -425,7 +449,8 @@ class ServiceItem(RegistryProperties):
self.add_from_command(text_image['path'], text_image['title'], text_image['image'])
elif path:
self.has_original_files = False
self.add_from_command(path, text_image['title'], text_image['image'])
self.add_from_command(path, text_image['title'], text_image['image'],
text_image.get('display_title', ''), text_image.get('notes', ''))
else:
self.add_from_command(text_image['path'], text_image['title'], text_image['image'])
self._new_item()

View File

@ -33,7 +33,7 @@ import logging
from PyQt4 import QtCore, QtGui
from openlp.core.common import Registry, UiStrings, translate
from openlp.core.common import Registry, UiStrings, translate, is_macosx
from openlp.core.lib import build_icon
from openlp.core.utils.actions import ActionList
@ -247,6 +247,8 @@ def create_action(parent, name, **kwargs):
"""
action = QtGui.QAction(parent)
action.setObjectName(name)
if is_macosx():
action.setIconVisibleInMenu(False)
if kwargs.get('text'):
action.setText(kwargs.pop('text'))
if kwargs.get('icon'):

View File

@ -387,17 +387,21 @@ class FirstTimeForm(QtGui.QWizard, Ui_FirstTimeWizard, RegistryProperties):
self.progress_bar.setValue(self.progress_bar.maximum())
if self.has_run_wizard:
self.progress_label.setText(translate('OpenLP.FirstTimeWizard',
'Download complete. Click the finish button to return to OpenLP.'))
'Download complete. Click the %s button to return to OpenLP.') %
self.buttonText(QtGui.QWizard.FinishButton))
else:
self.progress_label.setText(translate('OpenLP.FirstTimeWizard',
'Download complete. Click the finish button to start OpenLP.'))
'Download complete. Click the %s button to start OpenLP.') %
self.buttonText(QtGui.QWizard.FinishButton))
else:
if self.has_run_wizard:
self.progress_label.setText(translate('OpenLP.FirstTimeWizard',
'Click the finish button to return to OpenLP.'))
'Click the %s button to return to OpenLP.') %
self.buttonText(QtGui.QWizard.FinishButton))
else:
self.progress_label.setText(translate('OpenLP.FirstTimeWizard',
'Click the finish button to start OpenLP.'))
'Click the %s button to start OpenLP.') %
self.buttonText(QtGui.QWizard.FinishButton))
self.finish_button.setVisible(True)
self.finish_button.setEnabled(True)
self.cancel_button.setVisible(False)

View File

@ -31,9 +31,7 @@ The UI widgets for the first time wizard.
"""
from PyQt4 import QtCore, QtGui
import sys
from openlp.core.common import translate
from openlp.core.common import translate, is_macosx
from openlp.core.lib import build_icon
from openlp.core.lib.ui import add_welcome_page
@ -64,9 +62,12 @@ class Ui_FirstTimeWizard(object):
first_time_wizard.setWindowIcon(build_icon(u':/icon/openlp-logo.svg'))
first_time_wizard.resize(550, 386)
first_time_wizard.setModal(True)
first_time_wizard.setWizardStyle(QtGui.QWizard.ModernStyle)
first_time_wizard.setOptions(QtGui.QWizard.IndependentPages | QtGui.QWizard.NoBackButtonOnStartPage |
QtGui.QWizard.NoBackButtonOnLastPage | QtGui.QWizard.HaveCustomButton1)
if is_macosx():
first_time_wizard.setPixmap(QtGui.QWizard.BackgroundPixmap,
QtGui.QPixmap(':/wizards/openlp-osx-wizard.png'))
first_time_wizard.resize(634, 386)
self.finish_button = self.button(QtGui.QWizard.FinishButton)
self.no_internet_finish_button = self.button(QtGui.QWizard.CustomButton1)
self.cancel_button = self.button(QtGui.QWizard.CancelButton)
@ -212,7 +213,8 @@ class Ui_FirstTimeWizard(object):
translate('OpenLP.FirstTimeWizard', 'Welcome to the First Time Wizard'))
self.information_label.setText(
translate('OpenLP.FirstTimeWizard', 'This wizard will help you to configure OpenLP for initial use. '
'Click the next button below to start.'))
'Click the %s button below to start.') %
self.buttonText(QtGui.QWizard.NextButton))
self.plugin_page.setTitle(translate('OpenLP.FirstTimeWizard', 'Activate required Plugins'))
self.plugin_page.setSubTitle(translate('OpenLP.FirstTimeWizard', 'Select the Plugins you wish to use. '))
self.songs_check_box.setText(translate('OpenLP.FirstTimeWizard', 'Songs'))
@ -236,7 +238,7 @@ class Ui_FirstTimeWizard(object):
'wizard by selecting "Tools/Re-run First Time Wizard" from OpenLP.')
self.cancelWizardText = translate('OpenLP.FirstTimeWizard',
'\n\nTo cancel the First Time Wizard completely (and not start OpenLP), '
'click the Cancel button now.')
'click the %s button now.') % self.buttonText(QtGui.QWizard.CancelButton)
self.songs_page.setTitle(translate('OpenLP.FirstTimeWizard', 'Sample Songs'))
self.songs_page.setSubTitle(translate('OpenLP.FirstTimeWizard', 'Select and download public domain songs.'))
self.bibles_page.setTitle(translate('OpenLP.FirstTimeWizard', 'Sample Bibles'))

View File

@ -60,6 +60,12 @@ class FormattingTagForm(QtGui.QDialog, Ui_FormattingTagDialog, FormattingTagCont
"""
super(FormattingTagForm, self).__init__(parent)
self.setupUi(self)
self._setup()
def _setup(self):
"""
Set up the class. This method is mocked out by the tests.
"""
self.services = FormattingTagController()
self.tag_table_widget.itemSelectionChanged.connect(self.on_row_selected)
self.new_button.clicked.connect(self.on_new_clicked)

View File

@ -94,8 +94,8 @@ class ListPreviewWidget(QtGui.QTableWidget, RegistryProperties):
Displays the given slide.
"""
self.service_item = service_item
self.clear()
self.setRowCount(0)
self.clear()
self.setColumnWidth(0, width)
row = 0
text = []

View File

@ -92,6 +92,8 @@ class Ui_MainWindow(object):
main_window.setObjectName('MainWindow')
main_window.setWindowIcon(build_icon(':/icon/openlp-logo.svg'))
main_window.setDockNestingEnabled(True)
if is_macosx():
main_window.setDocumentMode(True)
# Set up the main container, which contains all the other form widgets.
self.main_content = QtGui.QWidget(main_window)
self.main_content.setObjectName('main_content')
@ -118,10 +120,12 @@ class Ui_MainWindow(object):
self.recent_files_menu = QtGui.QMenu(self.file_menu)
self.recent_files_menu.setObjectName('recentFilesMenu')
self.file_import_menu = QtGui.QMenu(self.file_menu)
self.file_import_menu.setIcon(build_icon(u':/general/general_import.png'))
if not is_macosx():
self.file_import_menu.setIcon(build_icon(u':/general/general_import.png'))
self.file_import_menu.setObjectName('file_import_menu')
self.file_export_menu = QtGui.QMenu(self.file_menu)
self.file_export_menu.setIcon(build_icon(u':/general/general_export.png'))
if not is_macosx():
self.file_export_menu.setIcon(build_icon(u':/general/general_export.png'))
self.file_export_menu.setObjectName('file_export_menu')
# View Menu
self.view_menu = QtGui.QMenu(self.menu_bar)

View File

@ -1281,7 +1281,11 @@ class ServiceManager(OpenLPMixin, RegistryMixin, QtGui.QWidget, Ui_ServiceManage
# Add the children to their parent tree_widget_item.
for count, frame in enumerate(service_item_from_item.get_frames()):
child = QtGui.QTreeWidgetItem(tree_widget_item)
text = frame['title'].replace('\n', ' ')
# prefer to use a display_title
if service_item_from_item.is_capable(ItemCapabilities.HasDisplayTitle):
text = frame['display_title'].replace('\n', ' ')
else:
text = frame['title'].replace('\n', ' ')
child.setText(0, text[:40])
child.setData(0, QtCore.Qt.UserRole, count)
if service_item == item_count:

View File

@ -873,6 +873,7 @@ class SlideController(DisplayController, RegistryProperties):
if self.service_item.is_command():
Registry().execute('%s_slide' % self.service_item.name.lower(), [self.service_item, self.is_live, index])
self.update_preview()
self.selected_row = index
else:
self.preview_widget.change_slide(index)
self.slide_selected()
@ -1042,8 +1043,8 @@ class SlideController(DisplayController, RegistryProperties):
self.display.image(to_display)
# reset the store used to display first image
self.service_item.bg_image_bytes = None
self.update_preview()
self.selected_row = row
self.update_preview()
self.preview_widget.change_slide(row)
self.display.setFocus()
@ -1055,6 +1056,7 @@ class SlideController(DisplayController, RegistryProperties):
"""
self.preview_widget.change_slide(row)
self.update_preview()
self.selected_row = row
def update_preview(self):
"""

View File

@ -31,7 +31,7 @@ The Create/Edit theme wizard
"""
from PyQt4 import QtCore, QtGui
from openlp.core.common import UiStrings, translate
from openlp.core.common import UiStrings, translate, is_macosx
from openlp.core.lib import build_icon
from openlp.core.lib.theme import HorizontalType, BackgroundType, BackgroundGradientType
from openlp.core.lib.ui import add_welcome_page, create_valign_selection_widgets
@ -41,19 +41,21 @@ class Ui_ThemeWizard(object):
"""
The Create/Edit theme wizard
"""
def setupUi(self, themeWizard):
def setupUi(self, theme_wizard):
"""
Set up the UI
"""
themeWizard.setObjectName('OpenLP.ThemeWizard')
themeWizard.setWindowIcon(build_icon(u':/icon/openlp-logo.svg'))
themeWizard.setModal(True)
themeWizard.setWizardStyle(QtGui.QWizard.ModernStyle)
themeWizard.setOptions(QtGui.QWizard.IndependentPages |
QtGui.QWizard.NoBackButtonOnStartPage | QtGui.QWizard.HaveCustomButton1)
theme_wizard.setObjectName('OpenLP.ThemeWizard')
theme_wizard.setWindowIcon(build_icon(u':/icon/openlp-logo.svg'))
theme_wizard.setModal(True)
theme_wizard.setOptions(QtGui.QWizard.IndependentPages |
QtGui.QWizard.NoBackButtonOnStartPage | QtGui.QWizard.HaveCustomButton1)
if is_macosx():
theme_wizard.setPixmap(QtGui.QWizard.BackgroundPixmap, QtGui.QPixmap(':/wizards/openlp-osx-wizard.png'))
theme_wizard.resize(646, 400)
self.spacer = QtGui.QSpacerItem(10, 0, QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Minimum)
# Welcome Page
add_welcome_page(themeWizard, ':/wizards/wizard_createtheme.bmp')
add_welcome_page(theme_wizard, ':/wizards/wizard_createtheme.bmp')
# Background Page
self.background_page = QtGui.QWizardPage()
self.background_page.setObjectName('background_page')
@ -137,7 +139,7 @@ class Ui_ThemeWizard(object):
self.transparent_layout.setObjectName('Transparent_layout')
self.background_stack.addWidget(self.transparent_widget)
self.background_layout.addLayout(self.background_stack)
themeWizard.addPage(self.background_page)
theme_wizard.addPage(self.background_page)
# Main Area Page
self.main_area_page = QtGui.QWizardPage()
self.main_area_page.setObjectName('main_area_page')
@ -218,7 +220,7 @@ class Ui_ThemeWizard(object):
self.shadow_size_spin_box.setObjectName('shadow_size_spin_box')
self.shadow_layout.addWidget(self.shadow_size_spin_box)
self.main_area_layout.addRow(self.shadow_check_box, self.shadow_layout)
themeWizard.addPage(self.main_area_page)
theme_wizard.addPage(self.main_area_page)
# Footer Area Page
self.footer_area_page = QtGui.QWizardPage()
self.footer_area_page.setObjectName('footer_area_page')
@ -242,7 +244,7 @@ class Ui_ThemeWizard(object):
self.footer_size_spin_box.setObjectName('FooterSizeSpinBox')
self.footer_area_layout.addRow(self.footer_size_label, self.footer_size_spin_box)
self.footer_area_layout.setItem(3, QtGui.QFormLayout.LabelRole, self.spacer)
themeWizard.addPage(self.footer_area_page)
theme_wizard.addPage(self.footer_area_page)
# Alignment Page
self.alignment_page = QtGui.QWizardPage()
self.alignment_page.setObjectName('alignment_page')
@ -264,7 +266,7 @@ class Ui_ThemeWizard(object):
self.transitions_check_box.setObjectName('transitions_check_box')
self.alignment_layout.addRow(self.transitions_label, self.transitions_check_box)
self.alignment_layout.setItem(3, QtGui.QFormLayout.LabelRole, self.spacer)
themeWizard.addPage(self.alignment_page)
theme_wizard.addPage(self.alignment_page)
# Area Position Page
self.area_position_page = QtGui.QWizardPage()
self.area_position_page.setObjectName('area_position_page')
@ -334,7 +336,7 @@ class Ui_ThemeWizard(object):
self.footer_height_spin_box.setObjectName('footer_height_spin_box')
self.footer_position_layout.addRow(self.footer_height_label, self.footer_height_spin_box)
self.area_position_layout.addWidget(self.footer_position_group_box)
themeWizard.addPage(self.area_position_page)
theme_wizard.addPage(self.area_position_page)
# Preview Page
self.preview_page = QtGui.QWizardPage()
self.preview_page.setObjectName('preview_page')
@ -362,8 +364,8 @@ class Ui_ThemeWizard(object):
self.preview_box_label.setObjectName('preview_box_label')
self.preview_area_layout.addWidget(self.preview_box_label)
self.preview_layout.addWidget(self.preview_area)
themeWizard.addPage(self.preview_page)
self.retranslateUi(themeWizard)
theme_wizard.addPage(self.preview_page)
self.retranslateUi(theme_wizard)
QtCore.QObject.connect(self.background_combo_box, QtCore.SIGNAL('currentIndexChanged(int)'),
self.background_stack, QtCore.SLOT('setCurrentIndex(int)'))
QtCore.QObject.connect(self.outline_check_box, QtCore.SIGNAL('toggled(bool)'), self.outline_color_button,
@ -391,11 +393,11 @@ class Ui_ThemeWizard(object):
QtCore.QObject.connect(self.footer_position_check_box, QtCore.SIGNAL('toggled(bool)'),
self.footer_height_spin_box, QtCore.SLOT('setDisabled(bool)'))
def retranslateUi(self, themeWizard):
def retranslateUi(self, theme_wizard):
"""
Translate the UI on the fly
"""
themeWizard.setWindowTitle(translate('OpenLP.ThemeWizard', 'Theme Wizard'))
theme_wizard.setWindowTitle(translate('OpenLP.ThemeWizard', 'Theme Wizard'))
self.title_label.setText('<span style="font-size:14pt; font-weight:600;">%s</span>' %
translate('OpenLP.ThemeWizard', 'Welcome to the Theme Wizard'))
self.information_label.setText(
@ -484,8 +486,8 @@ class Ui_ThemeWizard(object):
self.footer_height_label.setText(translate('OpenLP.ThemeWizard', 'Height:'))
self.footer_height_spin_box.setSuffix(translate('OpenLP.ThemeWizard', 'px'))
self.footer_position_check_box.setText(translate('OpenLP.ThemeWizard', 'Use default location'))
themeWizard.setOption(QtGui.QWizard.HaveCustomButton1, False)
themeWizard.setButtonText(QtGui.QWizard.CustomButton1, translate('OpenLP.ThemeWizard', 'Layout Preview'))
theme_wizard.setOption(QtGui.QWizard.HaveCustomButton1, False)
theme_wizard.setButtonText(QtGui.QWizard.CustomButton1, translate('OpenLP.ThemeWizard', 'Layout Preview'))
self.preview_page.setTitle(translate('OpenLP.ThemeWizard', 'Preview and Save'))
self.preview_page.setSubTitle(translate('OpenLP.ThemeWizard', 'Preview the theme and save it.'))
self.theme_name_label.setText(translate('OpenLP.ThemeWizard', 'Theme name:'))

View File

@ -34,7 +34,7 @@ import os
from PyQt4 import QtGui
from openlp.core.common import Registry, RegistryProperties, Settings, UiStrings, translate
from openlp.core.common import Registry, RegistryProperties, Settings, UiStrings, translate, is_macosx
from openlp.core.lib import build_icon
from openlp.core.lib.ui import add_welcome_page
@ -121,9 +121,10 @@ class OpenLPWizard(QtGui.QWizard, RegistryProperties):
"""
self.setWindowIcon(build_icon(u':/icon/openlp-logo.svg'))
self.setModal(True)
self.setWizardStyle(QtGui.QWizard.ModernStyle)
self.setOptions(QtGui.QWizard.IndependentPages |
QtGui.QWizard.NoBackButtonOnStartPage | QtGui.QWizard.NoBackButtonOnLastPage)
if is_macosx():
self.setPixmap(QtGui.QWizard.BackgroundPixmap, QtGui.QPixmap(':/wizards/openlp-osx-wizard.png'))
add_welcome_page(self, image)
self.add_custom_pages()
if self.with_progress_page:

View File

@ -85,7 +85,7 @@ class BibleFormat(object):
BibleFormat.CSV,
BibleFormat.OpenSong,
BibleFormat.WebDownload,
BibleFormar.Zefania,
BibleFormat.Zefania,
]

View File

@ -551,6 +551,7 @@ class ImageMediaItem(MediaManagerItem):
service_item.add_capability(ItemCapabilities.CanLoop)
service_item.add_capability(ItemCapabilities.CanAppend)
service_item.add_capability(ItemCapabilities.CanEditTitle)
service_item.add_capability(ItemCapabilities.HasThumbnails)
# force a nonexistent theme
service_item.theme = -1
missing_items_file_names = []
@ -589,7 +590,7 @@ class ImageMediaItem(MediaManagerItem):
# Continue with the existing images.
for filename in images_file_names:
name = os.path.split(filename)[1]
service_item.add_from_image(filename, name, background)
service_item.add_from_image(filename, name, background, os.path.join(self.service_path, name))
return True
def check_group_exists(self, new_group):

View File

@ -28,31 +28,29 @@
###############################################################################
import os
if os.name == 'nt':
from win32com.client import Dispatch
import string
import sys
if sys.platform.startswith('linux'):
import dbus
import logging
import re
from time import sleep
from datetime import datetime
from PyQt4 import QtCore, QtGui
from openlp.core.common import translate
from openlp.core.common import translate, is_win, is_linux, is_macosx, RegistryProperties
from openlp.plugins.media.forms.mediaclipselectordialog import Ui_MediaClipSelector
from openlp.core.lib.ui import critical_error_message_box
from openlp.core.ui.media import format_milliseconds
if is_win():
from win32com.client import Dispatch
if is_linux():
import dbus
try:
from openlp.core.ui.media.vendor import vlc
except (ImportError, NameError, NotImplementedError):
pass
except OSError as e:
if sys.platform.startswith('win'):
if is_win():
if not isinstance(e, WindowsError) and e.winerror != 126:
raise
else:
@ -61,7 +59,7 @@ except OSError as e:
log = logging.getLogger(__name__)
class MediaClipSelectorForm(QtGui.QDialog, Ui_MediaClipSelector):
class MediaClipSelectorForm(QtGui.QDialog, Ui_MediaClipSelector, RegistryProperties):
"""
Class to manage the clip selection
"""
@ -144,9 +142,9 @@ class MediaClipSelectorForm(QtGui.QDialog, Ui_MediaClipSelector):
# You have to give the id of the QFrame (or similar object)
# to vlc, different platforms have different functions for this.
win_id = int(self.preview_frame.winId())
if sys.platform == "win32":
if is_win():
self.vlc_media_player.set_hwnd(win_id)
elif sys.platform == "darwin":
elif is_macosx():
# We have to use 'set_nsobject' since Qt4 on OSX uses Cocoa
# framework and not the old Carbon.
self.vlc_media_player.set_nsobject(win_id)
@ -190,7 +188,7 @@ class MediaClipSelectorForm(QtGui.QDialog, Ui_MediaClipSelector):
self.audio_cd = True
self.titles_combo_box.setDisabled(False)
self.titles_combo_box.setCurrentIndex(0)
self.on_title_combo_box_currentIndexChanged(0)
self.on_titles_combo_box_currentIndexChanged(0)
return True
@ -203,18 +201,21 @@ class MediaClipSelectorForm(QtGui.QDialog, Ui_MediaClipSelector):
"""
log.debug('on_load_disc_button_clicked')
self.disable_all()
self.application.set_busy_cursor()
path = self.media_path_combobox.currentText()
# Check if given path is non-empty and exists before starting VLC
if not path:
log.debug('no given path')
critical_error_message_box(message=translate('MediaPlugin.MediaClipSelectorForm', 'No path was given'))
self.toggle_disable_load_media(False)
self.application.set_normal_cursor()
return
if not os.path.exists(path):
log.debug('Given path does not exists')
critical_error_message_box(message=translate('MediaPlugin.MediaClipSelectorForm',
'Given path does not exists'))
self.toggle_disable_load_media(False)
self.application.set_normal_cursor()
return
# VLC behaves a bit differently on windows and linux when loading, which creates problems when trying to
# detect if we're dealing with a DVD or CD, so we use different loading approaches depending on the OS.
@ -231,6 +232,7 @@ class MediaClipSelectorForm(QtGui.QDialog, Ui_MediaClipSelector):
critical_error_message_box(message=translate('MediaPlugin.MediaClipSelectorForm',
'An error happened during initialization of VLC player'))
self.toggle_disable_load_media(False)
self.application.set_normal_cursor()
return
# put the media in the media player
self.vlc_media_player.set_media(self.vlc_media)
@ -241,6 +243,8 @@ class MediaClipSelectorForm(QtGui.QDialog, Ui_MediaClipSelector):
critical_error_message_box(message=translate('MediaPlugin.MediaClipSelectorForm',
'VLC player failed playing the media'))
self.toggle_disable_load_media(False)
self.application.set_normal_cursor()
self.vlc_media_player.audio_set_mute(False)
return
self.vlc_media_player.audio_set_mute(True)
if not self.media_state_wait(vlc.State.Playing):
@ -249,23 +253,32 @@ class MediaClipSelectorForm(QtGui.QDialog, Ui_MediaClipSelector):
critical_error_message_box(message=translate('MediaPlugin.MediaClipSelectorForm',
'VLC player failed playing the media'))
self.toggle_disable_load_media(False)
self.application.set_normal_cursor()
self.vlc_media_player.audio_set_mute(False)
return
self.vlc_media_player.audio_set_mute(True)
# pause
self.vlc_media_player.set_time(0)
self.vlc_media_player.set_pause(1)
self.media_state_wait(vlc.State.Paused)
self.toggle_disable_load_media(False)
self.application.set_normal_cursor()
self.vlc_media_player.audio_set_mute(False)
if not self.audio_cd:
# Temporarily disable signals
self.blockSignals(True)
# Get titles, insert in combobox
titles = self.vlc_media_player.video_get_title_description()
self.titles_combo_box.clear()
for title in titles:
self.titles_combo_box.addItem(title[1].decode(), title[0])
# Re-enable signals
self.blockSignals(False)
# Main title is usually title #1
if len(titles) > 1:
self.titles_combo_box.setCurrentIndex(1)
else:
self.titles_combo_box.setCurrentIndex(0)
# Enable audio track combobox if anything is in it
if len(titles) > 0:
self.titles_combo_box.setDisabled(False)
self.toggle_disable_load_media(False)
log.debug('load_disc_button end - vlc_media_player state: %s' % self.vlc_media_player.get_state())
@QtCore.pyqtSlot(bool)
@ -378,6 +391,7 @@ class MediaClipSelectorForm(QtGui.QDialog, Ui_MediaClipSelector):
if not self.vlc_media_player:
log.error('vlc_media_player was None')
return
self.application.set_busy_cursor()
if self.audio_cd:
self.vlc_media = self.audio_cd_tracks.item_at_index(index)
self.vlc_media_player.set_media(self.vlc_media)
@ -385,14 +399,14 @@ class MediaClipSelectorForm(QtGui.QDialog, Ui_MediaClipSelector):
self.vlc_media_player.play()
if not self.media_state_wait(vlc.State.Playing):
log.error('Could not start playing audio cd, needed to get track info')
self.application.set_normal_cursor()
return
self.vlc_media_player.audio_set_mute(True)
# Sleep 1 second to make sure VLC has the needed metadata
sleep(1)
# pause
self.vlc_media_player.set_time(0)
self.vlc_media_player.set_pause(1)
self.vlc_media_player.audio_set_mute(False)
self.application.set_normal_cursor()
self.toggle_disable_player(False)
else:
self.vlc_media_player.set_title(index)
@ -400,13 +414,13 @@ class MediaClipSelectorForm(QtGui.QDialog, Ui_MediaClipSelector):
self.vlc_media_player.play()
if not self.media_state_wait(vlc.State.Playing):
log.error('Could not start playing dvd, needed to get track info')
self.application.set_normal_cursor()
return
self.vlc_media_player.audio_set_mute(True)
# Sleep 1 second to make sure VLC has the needed metadata
sleep(1)
self.vlc_media_player.set_time(0)
# Get audio tracks, insert in combobox
# Get audio tracks
audio_tracks = self.vlc_media_player.audio_get_track_description()
log.debug('number of audio tracks: %d' % len(audio_tracks))
# Clear the audio track combobox, insert new tracks
self.audio_tracks_combobox.clear()
for audio_track in audio_tracks:
self.audio_tracks_combobox.addItem(audio_track[1].decode(), audio_track[0])
@ -447,6 +461,7 @@ class MediaClipSelectorForm(QtGui.QDialog, Ui_MediaClipSelector):
self.vlc_media_player.set_pause(1)
loop_count += 1
log.debug('titles_combo_box end - vlc_media_player state: %s' % self.vlc_media_player.get_state())
self.application.set_normal_cursor()
@QtCore.pyqtSlot(int)
def on_audio_tracks_combobox_currentIndexChanged(self, index):
@ -535,7 +550,7 @@ class MediaClipSelectorForm(QtGui.QDialog, Ui_MediaClipSelector):
"""
Saves the current media and trackinfo as a clip to the mediamanager
"""
log.debug('in on_save_button_clicked')
log.debug('in MediaClipSelectorForm.accept')
start_time = self.start_position_edit.time()
start_time_ms = start_time.hour() * 60 * 60 * 1000 + \
start_time.minute() * 60 * 1000 + \
@ -550,10 +565,23 @@ class MediaClipSelectorForm(QtGui.QDialog, Ui_MediaClipSelector):
path = self.media_path_combobox.currentText()
optical = ''
if self.audio_cd:
# Check for load problems
if start_time_ms is None or end_time_ms is None or title is None:
critical_error_message_box(translate('MediaPlugin.MediaClipSelectorForm', 'CD not loaded correctly'),
translate('MediaPlugin.MediaClipSelectorForm',
'The CD was not loaded correctly, please re-load and try again.'))
return
optical = 'optical:%d:-1:-1:%d:%d:' % (title, start_time_ms, end_time_ms)
else:
audio_track = self.audio_tracks_combobox.itemData(self.audio_tracks_combobox.currentIndex())
subtitle_track = self.subtitle_tracks_combobox.itemData(self.subtitle_tracks_combobox.currentIndex())
# Check for load problems
if start_time_ms is None or end_time_ms is None or title is None or audio_track is None\
or subtitle_track is None:
critical_error_message_box(translate('MediaPlugin.MediaClipSelectorForm', 'DVD not loaded correctly'),
translate('MediaPlugin.MediaClipSelectorForm',
'The DVD was not loaded correctly, please re-load and try again.'))
return
optical = 'optical:%d:%d:%d:%d:%d:' % (title, audio_track, subtitle_track, start_time_ms, end_time_ms)
# Ask for an alternative name for the mediaclip
while True:
@ -595,7 +623,7 @@ class MediaClipSelectorForm(QtGui.QDialog, Ui_MediaClipSelector):
while media_state != self.vlc_media_player.get_state():
if self.vlc_media_player.get_state() == vlc.State.Error:
return False
if (datetime.now() - start).seconds > 30:
if (datetime.now() - start).seconds > 15:
return False
return True
@ -606,7 +634,7 @@ class MediaClipSelectorForm(QtGui.QDialog, Ui_MediaClipSelector):
"""
# Clear list first
self.media_path_combobox.clear()
if os.name == 'nt':
if is_win():
# use win api to find optical drives
fso = Dispatch('scripting.filesystemobject')
for drive in fso.Drives:
@ -614,7 +642,7 @@ class MediaClipSelectorForm(QtGui.QDialog, Ui_MediaClipSelector):
# if type is 4, it is a cd-rom drive
if drive.DriveType == 4:
self.media_path_combobox.addItem('%s:\\' % drive.DriveLetter)
elif sys.platform.startswith('linux'):
elif is_linux():
# Get disc devices from dbus and find the ones that are optical
bus = dbus.SystemBus()
try:
@ -646,7 +674,7 @@ class MediaClipSelectorForm(QtGui.QDialog, Ui_MediaClipSelector):
if chr(c) != '\x00':
block_file += chr(c)
self.media_path_combobox.addItem(block_file)
elif sys.platform.startswith('darwin'):
elif is_macosx():
# Look for DVD folders in devices to find optical devices
volumes = os.listdir('/Volumes')
candidates = list()

View File

@ -65,7 +65,7 @@ from PyQt4 import QtCore
from openlp.core.lib import ScreenList
from openlp.core.utils import delete_file, get_uno_command, get_uno_instance
from .presentationcontroller import PresentationController, PresentationDocument
from .presentationcontroller import PresentationController, PresentationDocument, TextType
log = logging.getLogger(__name__)
@ -257,6 +257,7 @@ class ImpressDocument(PresentationDocument):
self.presentation.Display = ScreenList().current['number'] + 1
self.control = None
self.create_thumbnails()
self.create_titles_and_notes()
return True
def create_thumbnails(self):
@ -450,22 +451,44 @@ class ImpressDocument(PresentationDocument):
:param slide_no: The slide the notes are required for, starting at 1
"""
return self.__get_text_from_page(slide_no, True)
return self.__get_text_from_page(slide_no, TextType.Notes)
def __get_text_from_page(self, slide_no, notes=False):
def __get_text_from_page(self, slide_no, text_type=TextType.SlideText):
"""
Return any text extracted from the presentation page.
:param slide_no: The slide the notes are required for, starting at 1
:param notes: A boolean. If set the method searches the notes of the slide.
:param text_type: A TextType. Enumeration of the types of supported text.
"""
text = ''
pages = self.document.getDrawPages()
page = pages.getByIndex(slide_no - 1)
if notes:
page = page.getNotesPage()
for index in range(page.getCount()):
shape = page.getByIndex(index)
if shape.supportsService("com.sun.star.drawing.Text"):
text += shape.getString() + '\n'
if TextType.Title <= text_type <= TextType.Notes:
pages = self.document.getDrawPages()
if 0 < slide_no <= pages.getCount():
page = pages.getByIndex(slide_no - 1)
if text_type == TextType.Notes:
page = page.getNotesPage()
for index in range(page.getCount()):
shape = page.getByIndex(index)
shape_type = shape.getShapeType()
if shape.supportsService("com.sun.star.drawing.Text"):
# if they requested title, make sure it is the title
if text_type != TextType.Title or shape_type == "com.sun.star.presentation.TitleTextShape":
text += shape.getString() + '\n'
return text
def create_titles_and_notes(self):
"""
Writes the list of titles (one per slide) to 'titles.txt' and the notes to 'slideNotes[x].txt'
in the thumbnails directory
"""
titles = []
notes = []
pages = self.document.getDrawPages()
for slide_no in range(1, pages.getCount() + 1):
titles.append(self.__get_text_from_page(slide_no, TextType.Title).replace('\n', ' ') + '\n')
note = self.__get_text_from_page(slide_no, TextType.Notes)
if len(note) == 0:
note = ' '
notes.append(note)
self.save_titles_and_notes(titles, notes)

View File

@ -288,13 +288,14 @@ class PresentationMediaItem(MediaManagerItem):
os.path.join(doc.get_temp_folder(), 'mainslide001.png')):
doc.load_presentation()
i = 1
image_file = 'mainslide%03d.png' % i
image = os.path.join(doc.get_temp_folder(), image_file)
image = os.path.join(doc.get_temp_folder(), 'mainslide%03d.png' % i)
thumbnail = os.path.join(doc.get_thumbnail_folder(), 'slide%d.png' % i)
while os.path.isfile(image):
service_item.add_from_image(image, name)
service_item.add_from_image(image, name, thumbnail=thumbnail)
i += 1
image_file = 'mainslide%03d.png' % i
image = os.path.join(doc.get_temp_folder(), image_file)
image = os.path.join(doc.get_temp_folder(), 'mainslide%03d.png' % i)
thumbnail = os.path.join(doc.get_thumbnail_folder(), 'slide%d.png' % i)
service_item.add_capability(ItemCapabilities.HasThumbnails)
doc.close_presentation()
return True
else:
@ -323,8 +324,21 @@ class PresentationMediaItem(MediaManagerItem):
i = 1
img = doc.get_thumbnail_path(i, True)
if img:
# Get titles and notes
titles, notes = doc.get_titles_and_notes()
service_item.add_capability(ItemCapabilities.HasDisplayTitle)
if notes.count('') != len(notes):
service_item.add_capability(ItemCapabilities.HasNotes)
service_item.add_capability(ItemCapabilities.HasThumbnails)
while img:
service_item.add_from_command(path, name, img)
# Use title and note if available
title = ''
if titles and len(titles) >= i:
title = titles[i - 1]
note = ''
if notes and len(notes) >= i:
note = notes[i - 1]
service_item.add_from_command(path, name, img, title, note)
i += 1
img = doc.get_thumbnail_path(i, True)
doc.close_presentation()

View File

@ -27,7 +27,7 @@
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
This modul is for controlling powerpiont. PPT API documentation:
This module is for controlling powerpoint. PPT API documentation:
`http://msdn.microsoft.com/en-us/library/aa269321(office.10).aspx`_
"""
import os
@ -37,16 +37,17 @@ from openlp.core.common import is_win
if is_win():
from win32com.client import Dispatch
import win32com
import winreg
import win32ui
import pywintypes
from openlp.core.lib import ScreenList
from openlp.core.common import Registry
from openlp.core.lib.ui import UiStrings, critical_error_message_box, translate
from openlp.core.common import trace_error_handler
from .presentationcontroller import PresentationController, PresentationDocument
log = logging.getLogger(__name__)
@ -136,6 +137,7 @@ class PowerpointDocument(PresentationDocument):
self.controller.process.Presentations.Open(self.file_path, False, False, True)
self.presentation = self.controller.process.Presentations(self.controller.process.Presentations.Count)
self.create_thumbnails()
self.create_titles_and_notes()
# Powerpoint 2013 pops up when loading a file, so we minimize it again
if self.presentation.Application.Version == u'15.0':
try:
@ -392,6 +394,28 @@ class PowerpointDocument(PresentationDocument):
"""
return _get_text_from_shapes(self.presentation.Slides(slide_no).NotesPage.Shapes)
def create_titles_and_notes(self):
"""
Writes the list of titles (one per slide)
to 'titles.txt'
and the notes to 'slideNotes[x].txt'
in the thumbnails directory
"""
titles = []
notes = []
for slide in self.presentation.Slides:
try:
text = slide.Shapes.Title.TextFrame.TextRange.Text
except Exception as e:
log.exception(e)
text = ''
titles.append(text.replace('\n', ' ').replace('\x0b', ' ') + '\n')
note = _get_text_from_shapes(slide.NotesPage.Shapes)
if len(note) == 0:
note = ' '
notes.append(note)
self.save_titles_and_notes(titles, notes)
def show_error_msg(self):
"""
Stop presentation and display an error message.
@ -410,8 +434,8 @@ def _get_text_from_shapes(shapes):
:param shapes: A set of shapes to search for text.
"""
text = ''
for index in range(shapes.Count):
shape = shapes(index + 1)
if shape.HasTextFrame:
text += shape.TextFrame.TextRange.Text + '\n'
for shape in shapes:
if shape.PlaceholderFormat.Type == 2: # 2 from is enum PpPlaceholderType.ppPlaceholderBody
if shape.HasTextFrame and shape.TextFrame.HasText:
text += shape.TextFrame.TextRange.Text + '\n'
return text

View File

@ -29,6 +29,11 @@
import logging
import os
import logging
import zipfile
import re
from xml.etree import ElementTree
from openlp.core.common import is_win
@ -127,14 +132,14 @@ class PptviewDocument(PresentationDocument):
temp_folder = self.get_temp_folder()
size = ScreenList().current['size']
rect = RECT(size.x(), size.y(), size.right(), size.bottom())
file_path = os.path.normpath(self.file_path)
self.file_path = os.path.normpath(self.file_path)
preview_path = os.path.join(temp_folder, 'slide')
# Ensure that the paths are null terminated
file_path = file_path.encode('utf-16-le') + b'\0'
self.file_path = self.file_path.encode('utf-16-le') + b'\0'
preview_path = preview_path.encode('utf-16-le') + b'\0'
if not os.path.isdir(temp_folder):
os.makedirs(temp_folder)
self.ppt_id = self.controller.process.OpenPPT(file_path, None, rect, preview_path)
self.ppt_id = self.controller.process.OpenPPT(self.file_path, None, rect, preview_path)
if self.ppt_id >= 0:
self.create_thumbnails()
self.stop_presentation()
@ -154,6 +159,68 @@ class PptviewDocument(PresentationDocument):
path = '%s\\slide%s.bmp' % (self.get_temp_folder(), str(idx + 1))
self.convert_thumbnail(path, idx + 1)
def create_titles_and_notes(self):
"""
Extracts the titles and notes from the zipped file
and writes the list of titles (one per slide)
to 'titles.txt'
and the notes to 'slideNotes[x].txt'
in the thumbnails directory
"""
titles = None
notes = None
filename = os.path.normpath(self.file_path)
# let's make sure we have a valid zipped presentation
if os.path.exists(filename) and zipfile.is_zipfile(filename):
namespaces = {"p": "http://schemas.openxmlformats.org/presentationml/2006/main",
"a": "http://schemas.openxmlformats.org/drawingml/2006/main"}
# open the file
with zipfile.ZipFile(filename) as zip_file:
# find the presentation.xml to get the slide count
with zip_file.open('ppt/presentation.xml') as pres:
tree = ElementTree.parse(pres)
nodes = tree.getroot().findall(".//p:sldIdLst/p:sldId", namespaces=namespaces)
# initialize the lists
titles = ['' for i in range(len(nodes))]
notes = ['' for i in range(len(nodes))]
# loop thru the file list to find slides and notes
for zip_info in zip_file.infolist():
node_type = ''
index = -1
list_to_add = None
# check if it is a slide
match = re.search("slides/slide(.+)\.xml", zip_info.filename)
if match:
index = int(match.group(1))-1
node_type = 'ctrTitle'
list_to_add = titles
# or a note
match = re.search("notesSlides/notesSlide(.+)\.xml", zip_info.filename)
if match:
index = int(match.group(1))-1
node_type = 'body'
list_to_add = notes
# if it is one of our files, index shouldn't be -1
if index >= 0:
with zip_file.open(zip_info) as zipped_file:
tree = ElementTree.parse(zipped_file)
text = ''
nodes = tree.getroot().findall(".//p:ph[@type='" + node_type + "']../../..//p:txBody//a:t",
namespaces=namespaces)
# if we found any content
if nodes and len(nodes) > 0:
for node in nodes:
if len(text) > 0:
text += '\n'
text += node.text
# Let's remove the \n from the titles and
# just add one at the end
if node_type == 'ctrTitle':
text = text.replace('\n', ' ').replace('\x0b', ' ') + '\n'
list_to_add[index] = text
# now let's write the files
self.save_titles_and_notes(titles, notes)
def close_presentation(self):
"""
Close presentation and clean up objects. Triggered by new object being added to SlideController or OpenLP being

Binary file not shown.

View File

@ -293,6 +293,49 @@ class PresentationDocument(object):
"""
return ''
def get_titles_and_notes(self):
"""
Reads the titles from the titles file and
the notes files and returns the content in two lists
"""
titles = []
notes = []
titles_file = os.path.join(self.get_thumbnail_folder(), 'titles.txt')
if os.path.exists(titles_file):
try:
with open(titles_file) as fi:
titles = fi.read().splitlines()
except:
log.exception('Failed to open/read existing titles file')
titles = []
for slide_no, title in enumerate(titles, 1):
notes_file = os.path.join(self.get_thumbnail_folder(), 'slideNotes%d.txt' % slide_no)
note = ''
if os.path.exists(notes_file):
try:
with open(notes_file) as fn:
note = fn.read()
except:
log.exception('Failed to open/read notes file')
note = ''
notes.append(note)
return titles, notes
def save_titles_and_notes(self, titles, notes):
"""
Performs the actual persisting of titles to the titles.txt
and notes to the slideNote%.txt
"""
if titles:
titles_file = os.path.join(self.get_thumbnail_folder(), 'titles.txt')
with open(titles_file, mode='w') as fo:
fo.writelines(titles)
if notes:
for slide_no, note in enumerate(notes, 1):
notes_file = os.path.join(self.get_thumbnail_folder(), 'slideNotes%d.txt' % slide_no)
with open(notes_file, mode='w') as fn:
fn.write(note)
class PresentationController(object):
"""
@ -427,3 +470,12 @@ class PresentationController(object):
def close_presentation(self):
pass
class TextType(object):
"""
Type Enumeration for Types of Text to request
"""
Title = 0
SlideText = 1
Notes = 2

View File

@ -87,16 +87,30 @@ window.OpenLP = {
var ul = $("#slide-controller > div[data-role=content] > ul[data-role=listview]");
ul.html("");
for (idx in data.results.slides) {
var text = data.results.slides[idx]["tag"];
if (text != "") text = text + ": ";
text = text + data.results.slides[idx]["text"];
var indexInt = parseInt(idx,10);
var slide = data.results.slides[idx];
var text = slide["tag"];
if (text != "") {
text = text + ": ";
}
if (slide["title"]) {
text += slide["title"]
} else {
text += slide["text"];
}
if (slide["notes"]) {
text += ("<div style='font-size:smaller;font-weight:normal'>" + slide["notes"] + "</div>");
}
text = text.replace(/\n/g, '<br />');
var li = $("<li data-icon=\"false\">").append(
$("<a href=\"#\">").attr("value", parseInt(idx, 10)).html(text));
if (data.results.slides[idx]["selected"]) {
if (slide["img"]) {
text += "<img src='" + slide["img"].replace("/thumbnails/", "/thumbnails88x88/") + "'>";
}
var li = $("<li data-icon=\"false\">").append($("<a href=\"#\">").html(text));
if (slide["selected"]) {
li.attr("data-theme", "e");
}
li.children("a").click(OpenLP.setSlide);
li.find("*").attr("value", indexInt );
ul.append(li);
}
OpenLP.currentItem = data.results.item;

View File

@ -102,7 +102,21 @@ window.OpenLP = {
$("#verseorder span").removeClass("currenttag");
$("#tag" + OpenLP.currentTags[OpenLP.currentSlide]).addClass("currenttag");
var slide = OpenLP.currentSlides[OpenLP.currentSlide];
var text = slide["text"];
var text = "";
// use title if available
if (slide["title"]) {
text = slide["title"];
} else {
text = slide["text"];
}
// use thumbnail if available
if (slide["img"]) {
text += "<br /><img src='" + slide["img"].replace("/thumbnails/", "/thumbnails320x240/") + "'><br />";
}
// use notes if available
if (slide["notes"]) {
text += '<br />' + slide["notes"];
}
text = text.replace(/\n/g, "<br />");
$("#currentslide").html(text);
text = "";
@ -110,7 +124,11 @@ window.OpenLP = {
for (var idx = OpenLP.currentSlide + 1; idx < OpenLP.currentSlides.length; idx++) {
if (OpenLP.currentTags[idx] != OpenLP.currentTags[idx - 1])
text = text + "<p class=\"nextslide\">";
text = text + OpenLP.currentSlides[idx]["text"];
if (OpenLP.currentSlides[idx]["title"]) {
text = text + OpenLP.currentSlides[idx]["title"];
} else {
text = text + OpenLP.currentSlides[idx]["text"];
}
if (OpenLP.currentTags[idx] != OpenLP.currentTags[idx - 1])
text = text + "</p>";
else

View File

@ -125,7 +125,7 @@ from mako.template import Template
from PyQt4 import QtCore
from openlp.core.common import Registry, RegistryProperties, AppLocation, Settings, translate
from openlp.core.lib import PluginStatus, StringContent, image_to_byte
from openlp.core.lib import PluginStatus, StringContent, image_to_byte, ItemCapabilities, create_thumb
log = logging.getLogger(__name__)
FILE_TYPES = {
@ -159,6 +159,7 @@ class HttpRouter(RegistryProperties):
('^/(stage)$', {'function': self.serve_file, 'secure': False}),
('^/(main)$', {'function': self.serve_file, 'secure': False}),
(r'^/files/(.*)$', {'function': self.serve_file, 'secure': False}),
(r'^/(\w+)/thumbnails([^/]+)?/(.*)$', {'function': self.serve_thumbnail, 'secure': False}),
(r'^/api/poll$', {'function': self.poll, 'secure': False}),
(r'^/main/poll$', {'function': self.main_poll, 'secure': False}),
(r'^/main/image$', {'function': self.main_image, 'secure': False}),
@ -328,7 +329,8 @@ class HttpRouter(RegistryProperties):
'no_results': translate('RemotePlugin.Mobile', 'No Results'),
'options': translate('RemotePlugin.Mobile', 'Options'),
'service': translate('RemotePlugin.Mobile', 'Service'),
'slides': translate('RemotePlugin.Mobile', 'Slides')
'slides': translate('RemotePlugin.Mobile', 'Slides'),
'settings': translate('RemotePlugin.Mobile', 'Settings'),
}
def serve_file(self, file_name=None):
@ -380,6 +382,42 @@ class HttpRouter(RegistryProperties):
content_type = FILE_TYPES.get(ext, 'text/plain')
return ext, content_type
def serve_thumbnail(self, controller_name=None, dimensions=None, file_name=None):
"""
Serve an image file. If not found return 404.
"""
log.debug('serve thumbnail %s/thumbnails%s/%s' % (controller_name, dimensions, file_name))
supported_controllers = ['presentations', 'images']
# -1 means use the default dimension in ImageManager
width = -1
height = -1
if dimensions:
match = re.search('(\d+)x(\d+)', dimensions)
if match:
# let's make sure that the dimensions are within reason
width = sorted([10, int(match.group(1)), 1000])[1]
height = sorted([10, int(match.group(2)), 1000])[1]
content = ''
content_type = None
if controller_name and file_name:
if controller_name in supported_controllers:
full_path = urllib.parse.unquote(file_name)
if '..' not in full_path: # no hacking please
full_path = os.path.normpath(os.path.join(AppLocation.get_section_data_path(controller_name),
'thumbnails/' + full_path))
if os.path.exists(full_path):
path, just_file_name = os.path.split(full_path)
self.image_manager.add_image(full_path, just_file_name, None, width, height)
ext, content_type = self.get_content_type(full_path)
image = self.image_manager.get_image(full_path, just_file_name, width, height)
content = image_to_byte(image, False)
if len(content) == 0:
return self.do_not_found()
self.send_response(200)
self.send_header('Content-type', content_type)
self.end_headers()
return content
def poll(self):
"""
Poll OpenLP to determine the current slide number and item name.
@ -458,6 +496,7 @@ class HttpRouter(RegistryProperties):
if current_item:
for index, frame in enumerate(current_item.get_frames()):
item = {}
# Handle text (songs, custom, bibles)
if current_item.is_text():
if frame['verseTag']:
item['tag'] = str(frame['verseTag'])
@ -465,11 +504,37 @@ class HttpRouter(RegistryProperties):
item['tag'] = str(index + 1)
item['text'] = str(frame['text'])
item['html'] = str(frame['html'])
else:
# Handle images, unless a custom thumbnail is given or if thumbnails is disabled
elif current_item.is_image() and not frame.get('image', '') and Settings().value('remotes/thumbnails'):
item['tag'] = str(index + 1)
thumbnail_path = os.path.join('images', 'thumbnails', frame['title'])
full_thumbnail_path = os.path.join(AppLocation.get_data_path(), thumbnail_path)
# Create thumbnail if it doesn't exists
if not os.path.exists(full_thumbnail_path):
create_thumb(current_item.get_frame_path(index), full_thumbnail_path, False)
item['img'] = urllib.request.pathname2url(os.path.sep + thumbnail_path)
item['text'] = str(frame['title'])
item['html'] = str(frame['title'])
else:
# Handle presentation etc.
item['tag'] = str(index + 1)
if current_item.is_capable(ItemCapabilities.HasDisplayTitle):
item['title'] = str(frame['display_title'])
if current_item.is_capable(ItemCapabilities.HasNotes):
item['notes'] = str(frame['notes'])
if current_item.is_capable(ItemCapabilities.HasThumbnails) and \
Settings().value('remotes/thumbnails'):
# If the file is under our app directory tree send the portion after the match
data_path = AppLocation.get_data_path()
print(frame)
if frame['image'][0:len(data_path)] == data_path:
item['img'] = urllib.request.pathname2url(frame['image'][len(data_path):])
item['text'] = str(frame['title'])
item['html'] = str(frame['title'])
item['selected'] = (self.live_controller.selected_row == index)
if current_item.notes:
item['notes'] = item.get('notes', '') + '\n' + current_item.notes
print(item)
data.append(item)
json_data = {'results': {'slides': data}}
if current_item:

View File

@ -144,6 +144,7 @@ class OpenLPServer(RegistryProperties):
try:
self.httpd = server_class((address, port), CustomHandler)
log.debug("Server started for class %s %s %d" % (server_class, address, port))
break
except OSError:
log.debug("failed to start http server thread state %d %s" %
(loop, self.http_thread.isRunning()))
@ -151,6 +152,8 @@ class OpenLPServer(RegistryProperties):
time.sleep(0.1)
except:
log.error('Failed to start server ')
loop += 1
time.sleep(0.1)
def stop_server(self):
"""

View File

@ -62,6 +62,9 @@ class RemoteTab(SettingsTab):
self.twelve_hour_check_box = QtGui.QCheckBox(self.server_settings_group_box)
self.twelve_hour_check_box.setObjectName('twelve_hour_check_box')
self.server_settings_layout.addRow(self.twelve_hour_check_box)
self.thumbnails_check_box = QtGui.QCheckBox(self.server_settings_group_box)
self.thumbnails_check_box.setObjectName('thumbnails_check_box')
self.server_settings_layout.addRow(self.thumbnails_check_box)
self.left_layout.addWidget(self.server_settings_group_box)
self.http_settings_group_box = QtGui.QGroupBox(self.left_column)
self.http_settings_group_box.setObjectName('http_settings_group_box')
@ -163,6 +166,7 @@ class RemoteTab(SettingsTab):
self.left_layout.addStretch()
self.right_layout.addStretch()
self.twelve_hour_check_box.stateChanged.connect(self.on_twelve_hour_check_box_changed)
self.thumbnails_check_box.stateChanged.connect(self.on_thumbnails_check_box_changed)
self.address_edit.textChanged.connect(self.set_urls)
self.port_spin_box.valueChanged.connect(self.set_urls)
self.https_port_spin_box.valueChanged.connect(self.set_urls)
@ -176,6 +180,8 @@ class RemoteTab(SettingsTab):
self.stage_url_label.setText(translate('RemotePlugin.RemoteTab', 'Stage view URL:'))
self.live_url_label.setText(translate('RemotePlugin.RemoteTab', 'Live view URL:'))
self.twelve_hour_check_box.setText(translate('RemotePlugin.RemoteTab', 'Display stage time in 12h format'))
self.thumbnails_check_box.setText(translate('RemotePlugin.RemoteTab',
'Show thumbnails of non-text slides in remote and stage view.'))
self.android_app_group_box.setTitle(translate('RemotePlugin.RemoteTab', 'Android App'))
self.qr_description_label.setText(
translate('RemotePlugin.RemoteTab', 'Scan the QR code or click <a href="https://play.google.com/store/'
@ -240,6 +246,8 @@ class RemoteTab(SettingsTab):
self.address_edit.setText(Settings().value(self.settings_section + '/ip address'))
self.twelve_hour = Settings().value(self.settings_section + '/twelve hour')
self.twelve_hour_check_box.setChecked(self.twelve_hour)
self.thumbnails = Settings().value(self.settings_section + '/thumbnails')
self.thumbnails_check_box.setChecked(self.thumbnails)
local_data = AppLocation.get_directory(AppLocation.DataDir)
if not os.path.exists(os.path.join(local_data, 'remotes', 'openlp.crt')) or \
not os.path.exists(os.path.join(local_data, 'remotes', 'openlp.key')):
@ -271,6 +279,7 @@ class RemoteTab(SettingsTab):
Settings().setValue(self.settings_section + '/https enabled', self.https_settings_group_box.isChecked())
Settings().setValue(self.settings_section + '/ip address', self.address_edit.text())
Settings().setValue(self.settings_section + '/twelve hour', self.twelve_hour)
Settings().setValue(self.settings_section + '/thumbnails', self.thumbnails)
Settings().setValue(self.settings_section + '/authentication enabled', self.user_login_group_box.isChecked())
Settings().setValue(self.settings_section + '/user id', self.user_id.text())
Settings().setValue(self.settings_section + '/password', self.password.text())
@ -285,6 +294,15 @@ class RemoteTab(SettingsTab):
if check_state == QtCore.Qt.Checked:
self.twelve_hour = True
def on_thumbnails_check_box_changed(self, check_state):
"""
Toggle the thumbnail check box.
"""
self.thumbnails = False
# we have a set value convert to True/False
if check_state == QtCore.Qt.Checked:
self.thumbnails = True
def https_changed(self):
"""
Invert the HTTP group box based on Https group settings

View File

@ -44,7 +44,8 @@ __default_settings__ = {
'remotes/user id': 'openlp',
'remotes/password': 'password',
'remotes/authentication enabled': False,
'remotes/ip address': '0.0.0.0'
'remotes/ip address': '0.0.0.0',
'remotes/thumbnails': True
}

View File

@ -31,8 +31,6 @@ The :mod:`db` module provides the database and schema that is the backend for
the Songs plugin
"""
import re
from sqlalchemy import Column, ForeignKey, Table, types
from sqlalchemy.orm import mapper, relation, reconstructor
from sqlalchemy.sql.expression import func, text
@ -329,7 +327,9 @@ def init_schema(url):
Column('topic_id', types.Integer(), ForeignKey('topics.id'), primary_key=True)
)
mapper(Author, authors_table)
mapper(Author, authors_table, properties={
'songs': relation(Song, secondary=authors_songs_table, viewonly=True)
})
mapper(AuthorSong, authors_songs_table, properties={
'author': relation(Author)
})
@ -339,7 +339,8 @@ def init_schema(url):
# Use the authors_songs relation when you need access to the 'author_type' attribute
# or when creating new relations
'authors_songs': relation(AuthorSong, cascade="all, delete-orphan"),
'authors': relation(Author, secondary=authors_songs_table, viewonly=True),
# Use lazy='joined' to always load authors when the song is fetched from the database (bug 1366198)
'authors': relation(Author, secondary=authors_songs_table, viewonly=True, lazy='joined'),
'book': relation(Book, backref='songs'),
'media_files': relation(MediaFile, backref='songs', order_by=media_files_table.c.weight),
'topics': relation(Topic, backref='songs', secondary=songs_topics_table)

View File

@ -99,6 +99,7 @@
<file>export_load.png</file>
</qresource>
<qresource prefix="wizards">
<file>openlp-osx-wizard.png</file>
<file>wizard_exportsong.bmp</file>
<file>wizard_importsong.bmp</file>
<file>wizard_importbible.bmp</file>
@ -151,10 +152,10 @@
<file>messagebox_warning.png</file>
</qresource>
<qresource prefix="remote">
<file>network_server.png</file>
<file>network_server.png</file>
<file>network_ssl.png</file>
<file>network_auth.png</file>
</qresource>
<file>network_auth.png</file>
</qresource>
<qresource prefix="songusage">
<file>song_usage_active.png</file>
<file>song_usage_inactive.png</file>

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

View File

@ -162,9 +162,9 @@ class TestAppLocation(TestCase):
patch('openlp.core.common.applocation.os.path.abspath') as mocked_abspath, \
patch('openlp.core.common.applocation.os.path.split') as mocked_split, \
patch('openlp.core.common.applocation.sys') as mocked_sys:
mocked_abspath.return_value = 'plugins/dir'
mocked_abspath.return_value = os.path.join('plugins', 'dir')
mocked_split.return_value = ['openlp']
mocked_get_frozen_path.return_value = 'plugins/dir'
mocked_get_frozen_path.return_value = os.path.join('plugins', 'dir')
mocked_sys.frozen = 1
mocked_sys.argv = ['openlp']
@ -172,7 +172,7 @@ class TestAppLocation(TestCase):
directory = AppLocation.get_directory(AppLocation.PluginsDir)
# THEN: The correct directory should be returned
self.assertEqual('plugins/dir', directory, 'Directory should be "plugins/dir"')
self.assertEqual(os.path.join('plugins', 'dir'), directory, 'Directory should be "plugins/dir"')
def get_frozen_path_in_unfrozen_app_test(self):
"""

View File

@ -69,16 +69,17 @@ class TestImageManager(TestCase, TestMixin):
Test the Image Manager setup basic functionality
"""
# GIVEN: the an image add to the image manager
self.image_manager.add_image(TEST_PATH, 'church.jpg', None)
full_path = os.path.normpath(os.path.join(TEST_PATH, 'church.jpg'))
self.image_manager.add_image(full_path, 'church.jpg', None)
# WHEN the image is retrieved
image = self.image_manager.get_image(TEST_PATH, 'church.jpg')
image = self.image_manager.get_image(full_path, 'church.jpg')
# THEN returned record is a type of image
self.assertEqual(isinstance(image, QtGui.QImage), True, 'The returned object should be a QImage')
# WHEN: The image bytes are requested.
byte_array = self.image_manager.get_image_bytes(TEST_PATH, 'church.jpg')
byte_array = self.image_manager.get_image_bytes(full_path, 'church.jpg')
# THEN: Type should be a str.
self.assertEqual(isinstance(byte_array, str), True, 'The returned object should be a str')
@ -89,6 +90,38 @@ class TestImageManager(TestCase, TestMixin):
self.image_manager.get_image(TEST_PATH, 'church1.jpg')
self.assertNotEquals(context.exception, '', 'KeyError exception should have been thrown for missing image')
def different_dimension_image_test(self):
"""
Test the Image Manager with dimensions
"""
# GIVEN: add an image with specific dimensions
full_path = os.path.normpath(os.path.join(TEST_PATH, 'church.jpg'))
self.image_manager.add_image(full_path, 'church.jpg', None, 80, 80)
# WHEN: the image is retrieved
image = self.image_manager.get_image(full_path, 'church.jpg', 80, 80)
# THEN: The return should be of type image
self.assertEqual(isinstance(image, QtGui.QImage), True, 'The returned object should be a QImage')
# WHEN: adding the same image with different dimensions
self.image_manager.add_image(full_path, 'church.jpg', None, 100, 100)
# THEN: the cache should contain two pictures
self.assertEqual(len(self.image_manager._cache), 2,
'Image manager should consider two dimensions of the same picture as different')
# WHEN: adding the same image with first dimensions
self.image_manager.add_image(full_path, 'church.jpg', None, 80, 80)
# THEN: the cache should still contain only two pictures
self.assertEqual(len(self.image_manager._cache), 2, 'Same dimensions should not be added again')
# WHEN: calling with correct image, but wrong dimensions
with self.assertRaises(KeyError) as context:
self.image_manager.get_image(full_path, 'church.jpg', 120, 120)
self.assertNotEquals(context.exception, '', 'KeyError exception should have been thrown for missing dimension')
def process_cache_test(self):
"""
Test the process_cache method
@ -151,7 +184,7 @@ class TestImageManager(TestCase, TestMixin):
:param image: The name of the image. E. g. ``image1``
"""
return self.image_manager._cache[(TEST_PATH, image)].priority
return self.image_manager._cache[(TEST_PATH, image, -1, -1)].priority
def mocked_resize_image(self, *args):
"""

View File

@ -32,13 +32,11 @@ Package to test the openlp.core.lib package.
import os
from unittest import TestCase
from tests.functional import MagicMock, patch
from tests.utils import assert_length, convert_file_service_item
from openlp.core.common import Registry
from openlp.core.lib import ItemCapabilities, ServiceItem
from openlp.core.lib import ItemCapabilities, ServiceItem, ServiceItemType
VERSE = 'The Lord said to {r}Noah{/r}: \n'\
'There\'s gonna be a {su}floody{/su}, {sb}floody{/sb}\n'\
@ -120,13 +118,17 @@ class TestServiceItem(TestCase):
# WHEN: adding an image from a saved Service and mocked exists
line = convert_file_service_item(TEST_PATH, 'serviceitem_image_1.osj')
with patch('openlp.core.ui.servicemanager.os.path.exists') as mocked_exists:
with patch('openlp.core.ui.servicemanager.os.path.exists') as mocked_exists,\
patch('openlp.core.lib.serviceitem.create_thumb') as mocked_create_thumb,\
patch('openlp.core.lib.serviceitem.AppLocation.get_section_data_path') as \
mocked_get_section_data_path:
mocked_exists.return_value = True
mocked_get_section_data_path.return_value = os.path.normpath('/path/')
service_item.set_from_service(line, TEST_PATH)
# THEN: We should get back a valid service item
self.assertTrue(service_item.is_valid, 'The new service item should be valid')
self.assertEqual(test_file, service_item.get_rendered_frame(0),
self.assertEqual(os.path.normpath(test_file), os.path.normpath(service_item.get_rendered_frame(0)),
'The first frame should match the path to the image')
self.assertEqual(frame_array, service_item.get_frames()[0],
'The return should match frame array1')
@ -153,8 +155,8 @@ class TestServiceItem(TestCase):
# GIVEN: A new service item and a mocked add icon function
image_name1 = 'image_1.jpg'
image_name2 = 'image_2.jpg'
test_file1 = os.path.join('/home/openlp', image_name1)
test_file2 = os.path.join('/home/openlp', image_name2)
test_file1 = os.path.normpath(os.path.join('/home/openlp', image_name1))
test_file2 = os.path.normpath(os.path.join('/home/openlp', image_name2))
frame_array1 = {'path': test_file1, 'title': image_name1}
frame_array2 = {'path': test_file2, 'title': image_name2}
@ -168,8 +170,12 @@ class TestServiceItem(TestCase):
line = convert_file_service_item(TEST_PATH, 'serviceitem_image_2.osj')
line2 = convert_file_service_item(TEST_PATH, 'serviceitem_image_2.osj', 1)
with patch('openlp.core.ui.servicemanager.os.path.exists') as mocked_exists:
with patch('openlp.core.ui.servicemanager.os.path.exists') as mocked_exists, \
patch('openlp.core.lib.serviceitem.create_thumb') as mocked_create_thumb, \
patch('openlp.core.lib.serviceitem.AppLocation.get_section_data_path') as \
mocked_get_section_data_path:
mocked_exists.return_value = True
mocked_get_section_data_path.return_value = os.path.normpath('/path/')
service_item2.set_from_service(line2)
service_item.set_from_service(line)
@ -207,6 +213,44 @@ class TestServiceItem(TestCase):
self.assertTrue(service_item.is_capable(ItemCapabilities.CanAppend),
'This service item should be able to have new items added to it')
def add_from_command_for_a_presentation_test(self):
"""
Test the Service Item - adding a presentation
"""
# GIVEN: A service item, a mocked icon and presentation data
service_item = ServiceItem(None)
presentation_name = 'test.pptx'
image = MagicMock()
display_title = 'DisplayTitle'
notes = 'Note1\nNote2\n'
frame = {'title': presentation_name, 'image': image, 'path': TEST_PATH,
'display_title': display_title, 'notes': notes}
# WHEN: adding presentation to service_item
service_item.add_from_command(TEST_PATH, presentation_name, image, display_title, notes)
# THEN: verify that it is setup as a Command and that the frame data matches
self.assertEqual(service_item.service_item_type, ServiceItemType.Command, 'It should be a Command')
self.assertEqual(service_item.get_frames()[0], frame, 'Frames should match')
def add_from_comamnd_without_display_title_and_notes_test(self):
"""
Test the Service Item - add from command, but not presentation
"""
# GIVEN: A new service item, a mocked icon and image data
service_item = ServiceItem(None)
image_name = 'test.img'
image = MagicMock()
frame = {'title': image_name, 'image': image, 'path': TEST_PATH,
'display_title': None, 'notes': None}
# WHEN: adding image to service_item
service_item.add_from_command(TEST_PATH, image_name, image)
# THEN: verify that it is setup as a Command and that the frame data matches
self.assertEqual(service_item.service_item_type, ServiceItemType.Command, 'It should be a Command')
self.assertEqual(service_item.get_frames()[0], frame, 'Frames should match')
def service_item_load_optical_media_from_service_test(self):
"""
Test the Service Item - load an optical media item

View File

@ -51,7 +51,7 @@ class TestTheme(TestCase):
"""
pass
def test_new_theme(self):
def new_theme_test(self):
"""
Test the theme creation - basic test
"""

View File

@ -29,10 +29,14 @@
"""
Package to test the openlp.core.lib.ui package.
"""
from PyQt4 import QtGui
from PyQt4 import QtCore, QtGui
from unittest import TestCase
from openlp.core.lib.ui import *
from openlp.core.common import UiStrings, translate
from openlp.core.lib.ui import add_welcome_page, create_button_box, create_horizontal_adjusting_combo_box, \
create_button, create_action, create_valign_selection_widgets, find_and_set_in_combo_box, create_widget_action, \
set_case_insensitive_completer
from tests.functional import MagicMock, patch
class TestUi(TestCase):
@ -40,7 +44,7 @@ class TestUi(TestCase):
Test the functions in the ui module
"""
def test_add_welcome_page(self):
def add_welcome_page_test(self):
"""
Test appending a welcome page to a wizard
"""
@ -54,7 +58,7 @@ class TestUi(TestCase):
self.assertEqual(1, len(wizard.pageIds()), 'The wizard should have one page.')
self.assertIsInstance(wizard.page(0).pixmap(QtGui.QWizard.WatermarkPixmap), QtGui.QPixmap)
def test_create_button_box(self):
def create_button_box_test(self):
"""
Test creating a button box for a dialog
"""
@ -82,7 +86,7 @@ class TestUi(TestCase):
self.assertEqual(1, len(btnbox.buttons()))
self.assertEqual(QtGui.QDialogButtonBox.HelpRole, btnbox.buttonRole(btnbox.buttons()[0]))
def test_create_horizontal_adjusting_combo_box(self):
def create_horizontal_adjusting_combo_box_test(self):
"""
Test creating a horizontal adjusting combo box
"""
@ -97,7 +101,7 @@ class TestUi(TestCase):
self.assertEqual('combo1', combo.objectName())
self.assertEqual(QtGui.QComboBox.AdjustToMinimumContentsLength, combo.sizeAdjustPolicy())
def test_create_button(self):
def create_button_test(self):
"""
Test creating a button
"""
@ -129,7 +133,7 @@ class TestUi(TestCase):
self.assertEqual('my_btn', btn.objectName())
self.assertTrue(btn.isEnabled())
def test_create_action(self):
def create_action_test(self):
"""
Test creating an action
"""
@ -154,9 +158,46 @@ class TestUi(TestCase):
self.assertEqual('my tooltip', action.toolTip())
self.assertEqual('my statustip', action.statusTip())
def test_create_checked_enabled_visible_action(self):
def create_action_on_mac_osx_test(self):
"""
Test creating an action with the 'checked', 'enabled' and 'visible' properties.
Test creating an action on OS X calls the correct method
"""
# GIVEN: A dialog and a mocked out is_macosx() method to always return True
with patch('openlp.core.lib.ui.is_macosx') as mocked_is_macosx, \
patch('openlp.core.lib.ui.QtGui.QAction') as MockedQAction:
mocked_is_macosx.return_value = True
mocked_action = MagicMock()
MockedQAction.return_value = mocked_action
dialog = QtGui.QDialog()
# WHEN: An action is created
create_action(dialog, 'my_action')
# THEN: setIconVisibleInMenu should be called
mocked_action.setIconVisibleInMenu.assert_called_with(False)
def create_action_not_on_mac_osx_test(self):
"""
Test creating an action on something other than OS X doesn't call the method
"""
# GIVEN: A dialog and a mocked out is_macosx() method to always return True
with patch('openlp.core.lib.ui.is_macosx') as mocked_is_macosx, \
patch('openlp.core.lib.ui.QtGui.QAction') as MockedQAction:
mocked_is_macosx.return_value = False
mocked_action = MagicMock()
MockedQAction.return_value = mocked_action
dialog = QtGui.QDialog()
# WHEN: An action is created
create_action(dialog, 'my_action')
# THEN: setIconVisibleInMenu should not be called
self.assertEqual(0, mocked_action.setIconVisibleInMenu.call_count,
'setIconVisibleInMenu should not have been called')
def create_checked_disabled_invisible_action_test(self):
"""
Test that an invisible, disabled, checked action is created correctly
"""
# GIVEN: A dialog
dialog = QtGui.QDialog()
@ -165,11 +206,24 @@ class TestUi(TestCase):
action = create_action(dialog, 'my_action', checked=True, enabled=False, visible=False)
# THEN: These properties should be set
self.assertEqual(True, action.isChecked())
self.assertEqual(False, action.isEnabled())
self.assertEqual(False, action.isVisible())
self.assertTrue(action.isChecked(), 'The action should be checked')
self.assertFalse(action.isEnabled(), 'The action should be disabled')
self.assertFalse(action.isVisible(), 'The action should be invisble')
def test_create_valign_selection_widgets(self):
def create_action_separator_test(self):
"""
Test creating an action as separator
"""
# GIVEN: A dialog
dialog = QtGui.QDialog()
# WHEN: We create an action as a separator
action = create_action(dialog, 'my_action', separator=True)
# THEN: The action should be a separator
self.assertTrue(action.isSeparator(), 'The action should be a separator')
def create_valign_selection_widgets_test(self):
"""
Test creating a combo box for valign selection
"""
@ -186,7 +240,7 @@ class TestUi(TestCase):
for text in [UiStrings().Top, UiStrings().Middle, UiStrings().Bottom]:
self.assertTrue(combo.findText(text) >= 0)
def test_find_and_set_in_combo_box(self):
def find_and_set_in_combo_box_test(self):
"""
Test finding a string in a combo box and setting it as the selected item if present
"""
@ -213,7 +267,7 @@ class TestUi(TestCase):
# THEN: The index should have changed
self.assertEqual(2, combo.currentIndex())
def test_create_widget_action(self):
def create_widget_action_test(self):
"""
Test creating an action for a widget
"""
@ -227,7 +281,7 @@ class TestUi(TestCase):
self.assertIsInstance(action, QtGui.QAction)
self.assertEqual(action.objectName(), 'some action')
def test_set_case_insensitive_completer(self):
def set_case_insensitive_completer_test(self):
"""
Test setting a case insensitive completer on a widget
"""

View File

@ -47,7 +47,7 @@ class TestFirstTimeForm(TestCase, TestMixin):
Registry().register('application', self.app)
self.first_time_form = FirstTimeForm(screens)
def test_access_to_config(self):
def access_to_config_test(self):
"""
Test if we can access the First Time Form's config file
"""
@ -59,7 +59,7 @@ class TestFirstTimeForm(TestCase, TestMixin):
self.assertTrue(self.first_time_form.web_access,
'First Time Wizard\'s web configuration file should be available')
def test_parsable_config(self):
def parsable_config_test(self):
"""
Test if the First Time Form's config file is parsable
"""

View File

@ -39,7 +39,7 @@ class TestFormattingTagController(TestCase):
def setUp(self):
self.services = FormattingTagController()
def test_strip(self):
def strip_test(self):
"""
Test that the _strip strips the correct chars
"""
@ -52,7 +52,7 @@ class TestFormattingTagController(TestCase):
# THEN: The tag should be returned with the wrappers removed.
self.assertEqual(result, 'tag', 'FormattingTagForm._strip should return u\'tag\' when called with u\'{tag}\'')
def test_end_tag_changed_processes_correctly(self):
def end_tag_changed_processes_correctly_test(self):
"""
Test that the end html tags are generated correctly
"""
@ -77,7 +77,7 @@ class TestFormattingTagController(TestCase):
self.assertTrue(error == test['valid'], 'Function should not generate unexpected error messages : %s ' %
error)
def test_start_tag_changed_processes_correctly(self):
def start_tag_changed_processes_correctly_test(self):
"""
Test that the end html tags are generated correctly
"""
@ -100,7 +100,7 @@ class TestFormattingTagController(TestCase):
self.assertTrue(error == test['valid'], 'Function should not generate unexpected error messages : %s ' %
error)
def test_start_html_to_end_html(self):
def start_html_to_end_html_test(self):
"""
Test that the end html tags are generated correctly
"""

View File

@ -29,17 +29,17 @@
"""
Package to test the openlp.core.ui.formattingtagsform package.
"""
from PyQt4 import QtGui
from unittest import TestCase
from openlp.core.common import translate
from tests.functional import MagicMock, patch
from tests.functional import MagicMock, patch, call
from openlp.core.ui.formattingtagform import FormattingTagForm
# TODO: Tests Still TODO
# __init__
# exec_
# on_new_clicked
# on_delete_clicked
# on_saved_clicked
# _reloadTable
@ -47,30 +47,60 @@ from openlp.core.ui.formattingtagform import FormattingTagForm
class TestFormattingTagForm(TestCase):
def setUp(self):
self.init_patcher = patch('openlp.core.ui.formattingtagform.FormattingTagForm.__init__')
self.qdialog_patcher = patch('openlp.core.ui.formattingtagform.QtGui.QDialog')
self.ui_formatting_tag_dialog_patcher = patch('openlp.core.ui.formattingtagform.Ui_FormattingTagDialog')
self.mocked_init = self.init_patcher.start()
self.mocked_qdialog = self.qdialog_patcher.start()
self.mocked_ui_formatting_tag_dialog = self.ui_formatting_tag_dialog_patcher.start()
self.mocked_init.return_value = None
"""
Mock out stuff for all the tests
"""
self.setup_patcher = patch('openlp.core.ui.formattingtagform.FormattingTagForm._setup')
self.setup_patcher.start()
def tearDown(self):
self.init_patcher.stop()
self.qdialog_patcher.stop()
self.ui_formatting_tag_dialog_patcher.stop()
def test_on_text_edited(self):
"""
Test that the appropriate actions are preformed when on_text_edited is called
Remove the mocks
"""
self.setup_patcher.stop()
def on_row_selected_test(self):
"""
Test that the appropriate actions are preformed when on_row_selected is called
"""
# GIVEN: An instance of the Formatting Tag Form and a mocked delete_button
form = FormattingTagForm(None)
form.delete_button = MagicMock()
# WHEN: on_row_selected is called
form.on_row_selected()
# THEN: setEnabled and should have been called on delete_button
form.delete_button.setEnabled.assert_called_with(True)
def on_new_clicked_test(self):
"""
Test that clicking the Add a new tag button does the right thing
"""
# GIVEN: An instance of the Formatting Tag Form and a mocked save_push_button
form = FormattingTagForm()
form.save_button = MagicMock()
# GIVEN: A formatting tag form and a mocked out tag table widget
form = FormattingTagForm(None)
form.tag_table_widget = MagicMock()
row_count = 5
form.tag_table_widget.rowCount.return_value = row_count
# WHEN: on_text_edited is called with an arbitrary value
# form.on_text_edited('text')
# WHEN: on_new_clicked is run (i.e. the Add new button was clicked)
with patch('openlp.core.ui.formattingtagform.QtGui.QTableWidgetItem') as MockedQTableWidgetItem:
mocked_table_widget = MagicMock()
MockedQTableWidgetItem.return_value = mocked_table_widget
form.on_new_clicked()
# THEN: setEnabled and setDefault should have been called on save_push_button
# form.save_button.setEnabled.assert_called_with(True)
# THEN: A new row should be added to the table
form.tag_table_widget.rowCount.assert_called_with()
form.tag_table_widget.insertRow.assert_called_with(row_count)
expected_set_item_calls = [
call(row_count, 0, mocked_table_widget),
call(row_count, 1, mocked_table_widget),
call(row_count, 2, mocked_table_widget),
call(row_count, 3, mocked_table_widget)
]
self.assertEqual(expected_set_item_calls, form.tag_table_widget.setItem.call_args_list,
'setItem should have been called correctly')
form.tag_table_widget.resizeRowsToContents.assert_called_with()
form.tag_table_widget.scrollToBottom.assert_called_with()
form.tag_table_widget.selectRow.assert_called_with(row_count)

View File

@ -57,17 +57,18 @@ class TestThemeManager(TestCase):
# GIVEN: A new ThemeManager instance.
theme_manager = ThemeManager()
theme_manager.path = os.path.join(TEST_RESOURCES_PATH, 'themes')
zipfile.ZipFile.__init__ = MagicMock()
zipfile.ZipFile.__init__.return_value = None
zipfile.ZipFile.write = MagicMock()
with patch('zipfile.ZipFile.__init__') as mocked_zipfile_init, \
patch('zipfile.ZipFile.write') as mocked_zipfile_write:
mocked_zipfile_init.return_value = None
# WHEN: The theme is exported
theme_manager._export_theme(os.path.join('some', 'path'), 'Default')
# WHEN: The theme is exported
theme_manager._export_theme(os.path.join('some', 'path'), 'Default')
# THEN: The zipfile should be created at the given path
zipfile.ZipFile.__init__.assert_called_with(os.path.join('some', 'path', 'Default.otz'), 'w')
zipfile.ZipFile.write.assert_called_with(os.path.join(TEST_RESOURCES_PATH, 'themes', 'Default', 'Default.xml'),
os.path.join('Default', 'Default.xml'))
# THEN: The zipfile should be created at the given path
mocked_zipfile_init.assert_called_with(os.path.join('some', 'path', 'Default.otz'), 'w')
mocked_zipfile_write.assert_called_with(os.path.join(TEST_RESOURCES_PATH, 'themes',
'Default', 'Default.xml'),
os.path.join('Default', 'Default.xml'))
def initial_theme_manager_test(self):
"""

View File

@ -0,0 +1,229 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2014 Raoul Snyman #
# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan #
# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, #
# Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
Functional tests to test the Impress class and related methods.
"""
from unittest import TestCase
import os
import shutil
from tempfile import mkdtemp
from tests.functional import patch, MagicMock
from tests.utils.constants import TEST_RESOURCES_PATH
from tests.helpers.testmixin import TestMixin
from openlp.plugins.presentations.lib.impresscontroller import \
ImpressController, ImpressDocument, TextType
class TestImpressController(TestCase, TestMixin):
"""
Test the ImpressController Class
"""
def setUp(self):
"""
Set up the patches and mocks need for all tests.
"""
self.get_application()
self.build_settings()
self.mock_plugin = MagicMock()
self.temp_folder = mkdtemp()
self.mock_plugin.settings_section = self.temp_folder
def tearDown(self):
"""
Stop the patches
"""
self.destroy_settings()
shutil.rmtree(self.temp_folder)
def constructor_test(self):
"""
Test the Constructor from the ImpressController
"""
# GIVEN: No presentation controller
controller = None
# WHEN: The presentation controller object is created
controller = ImpressController(plugin=self.mock_plugin)
# THEN: The name of the presentation controller should be correct
self.assertEqual('Impress', controller.name,
'The name of the presentation controller should be correct')
class TestImpressDocument(TestCase):
"""
Test the ImpressDocument Class
"""
def setUp(self):
mocked_plugin = MagicMock()
mocked_plugin.settings_section = 'presentations'
self.file_name = os.path.join(TEST_RESOURCES_PATH, 'presentations', 'test.pptx')
self.ppc = ImpressController(mocked_plugin)
self.doc = ImpressDocument(self.ppc, self.file_name)
def create_titles_and_notes_test(self):
"""
Test ImpressDocument.create_titles_and_notes
"""
# GIVEN: mocked PresentationController.save_titles_and_notes with
# 0 pages and the LibreOffice Document
self.doc.save_titles_and_notes = MagicMock()
self.doc.document = MagicMock()
self.doc.document.getDrawPages.return_value = MagicMock()
self.doc.document.getDrawPages().getCount.return_value = 0
# WHEN reading the titles and notes
self.doc.create_titles_and_notes()
# THEN save_titles_and_notes should have been called with empty arrays
self.doc.save_titles_and_notes.assert_called_once_with([], [])
# GIVEN: reset mock and set it to 2 pages
self.doc.save_titles_and_notes.reset_mock()
self.doc.document.getDrawPages().getCount.return_value = 2
# WHEN: a new call to create_titles_and_notes
self.doc.create_titles_and_notes()
# THEN: save_titles_and_notes should have been called once with
# two arrays of two elements
self.doc.save_titles_and_notes.assert_called_once_with(['\n', '\n'], [' ', ' '])
def get_text_from_page_out_of_bound_test(self):
"""
Test ImpressDocument.__get_text_from_page with out-of-bounds index
"""
# GIVEN: mocked LibreOffice Document with one slide,
# two notes and three texts
self.doc.document = self._mock_a_LibreOffice_document(1, 2, 3)
# WHEN: __get_text_from_page is called with an index of 0x00
result = self.doc._ImpressDocument__get_text_from_page(0, TextType.Notes)
# THEN: the result should be an empty string
self.assertEqual(result, '', 'Result should be an empty string')
# WHEN: regardless of the type of text, index 0x00 is out of bounds
result = self.doc._ImpressDocument__get_text_from_page(0, TextType.Title)
# THEN: result should be an empty string
self.assertEqual(result, '', 'Result should be an empty string')
# WHEN: when called with 2, it should also be out of bounds
result = self.doc._ImpressDocument__get_text_from_page(2, TextType.SlideText)
# THEN: result should be an empty string ... and, getByIndex should
# have never been called
self.assertEqual(result, '', 'Result should be an empty string')
self.assertEqual(self.doc.document.getDrawPages().getByIndex.call_count, 0,
'There should be no call to getByIndex')
def get_text_from_page_wrong_type_test(self):
"""
Test ImpressDocument.__get_text_from_page with wrong TextType
"""
# GIVEN: mocked LibreOffice Document with one slide, two notes and
# three texts
self.doc.document = self._mock_a_LibreOffice_document(1, 2, 3)
# WHEN: called with TextType 3
result = self.doc._ImpressDocument__get_text_from_page(1, 3)
# THEN: result should be an empty string
self.assertEqual(result, '', 'Result should be and empty string')
self.assertEqual(self.doc.document.getDrawPages().getByIndex.call_count, 0,
'There should be no call to getByIndex')
def get_text_from_page_valid_params_test(self):
"""
Test ImpressDocument.__get_text_from_page with valid parameters
"""
# GIVEN: mocked LibreOffice Document with one slide,
# two notes and three texts
self.doc.document = self._mock_a_LibreOffice_document(1, 2, 3)
# WHEN: __get_text_from_page is called to get the Notes
result = self.doc._ImpressDocument__get_text_from_page(1, TextType.Notes)
# THEN: result should be 'Note\nNote\n'
self.assertEqual(result, 'Note\nNote\n', 'Result should be \'Note\\n\' times the count of notes in the page')
# WHEN: get the Title
result = self.doc._ImpressDocument__get_text_from_page(1, TextType.Title)
# THEN: result should be 'Title\n'
self.assertEqual(result, 'Title\n', 'Result should be exactly \'Title\\n\'')
# WHEN: get all text
result = self.doc._ImpressDocument__get_text_from_page(1, TextType.SlideText)
# THEN: result should be 'Title\nString\nString\n'
self.assertEqual(result, 'Title\nString\nString\n', 'Result should be exactly \'Title\\nString\\nString\\n\'')
def _mock_a_LibreOffice_document(self, page_count, note_count, text_count):
"""
Helper function, creates a mock libreoffice document.
:param page_count: Number of pages in the document
:param note_count: Number of note pages in the document
:param text_count: Number of text pages in the document
"""
pages = MagicMock()
page = MagicMock()
pages.getByIndex.return_value = page
notes_page = MagicMock()
notes_page.getCount.return_value = note_count
shape = MagicMock()
shape.supportsService.return_value = True
shape.getString.return_value = 'Note'
notes_page.getByIndex.return_value = shape
page.getNotesPage.return_value = notes_page
page.getCount.return_value = text_count
page.getByIndex.side_effect = self._get_page_shape_side_effect
pages.getCount.return_value = page_count
document = MagicMock()
document.getDrawPages.return_value = pages
document.getByIndex.return_value = page
return document
def _get_page_shape_side_effect(*args):
"""
Helper function.
"""
page_shape = MagicMock()
page_shape.supportsService.return_value = True
if args[1] == 0:
page_shape.getShapeType.return_value = 'com.sun.star.presentation.TitleTextShape'
page_shape.getString.return_value = 'Title'
else:
page_shape.getString.return_value = 'String'
return page_shape

View File

@ -30,16 +30,20 @@
Functional tests to test the PowerPointController class and related methods.
"""
import os
if os.name == 'nt':
import pywintypes
import shutil
from unittest import TestCase
from tempfile import mkdtemp
from tests.functional import patch, MagicMock
from tests.helpers.testmixin import TestMixin
from tests.utils.constants import TEST_RESOURCES_PATH
from openlp.plugins.presentations.lib.powerpointcontroller import PowerpointController, PowerpointDocument
from openlp.plugins.presentations.lib.powerpointcontroller import PowerpointController, PowerpointDocument,\
_get_text_from_shapes
from openlp.core.common import is_win
if is_win():
import pywintypes
class TestPowerpointController(TestCase, TestMixin):
@ -79,7 +83,7 @@ class TestPowerpointController(TestCase, TestMixin):
'The name of the presentation controller should be correct')
class TestPowerpointDocument(TestCase):
class TestPowerpointDocument(TestCase, TestMixin):
"""
Test the PowerpointDocument Class
"""
@ -88,6 +92,11 @@ class TestPowerpointDocument(TestCase):
"""
Set up the patches and mocks need for all tests.
"""
self.get_application()
self.build_settings()
self.mock_plugin = MagicMock()
self.temp_folder = mkdtemp()
self.mock_plugin.settings_section = self.temp_folder
self.powerpoint_document_stop_presentation_patcher = patch(
'openlp.plugins.presentations.lib.powerpointcontroller.PowerpointDocument.stop_presentation')
self.presentation_document_get_temp_folder_patcher = patch(
@ -100,6 +109,8 @@ class TestPowerpointDocument(TestCase):
self.mock_controller = MagicMock()
self.mock_presentation = MagicMock()
self.mock_presentation_document_get_temp_folder.return_value = 'temp folder'
self.file_name = os.path.join(TEST_RESOURCES_PATH, 'presentations', 'test.pptx')
self.real_controller = PowerpointController(self.mock_plugin)
def tearDown(self):
"""
@ -108,12 +119,14 @@ class TestPowerpointDocument(TestCase):
self.powerpoint_document_stop_presentation_patcher.stop()
self.presentation_document_get_temp_folder_patcher.stop()
self.presentation_document_setup_patcher.stop()
self.destroy_settings()
shutil.rmtree(self.temp_folder)
def show_error_msg_test(self):
"""
Test the PowerpointDocument.show_error_msg() method gets called on com exception
"""
if os.name == 'nt':
if is_win():
# GIVEN: A PowerpointDocument with mocked controller and presentation
with patch('openlp.plugins.presentations.lib.powerpointcontroller.critical_error_message_box') as \
mocked_critical_error_message_box:
@ -129,3 +142,95 @@ class TestPowerpointDocument(TestCase):
'integration and the presentation will be stopped.'
' Restart the presentation if you wish to '
'present it.')
# add _test to the following if necessary
def verify_loading_document(self):
"""
Test loading a document in PowerPoint
"""
if is_win() and self.real_controller.check_available():
# GIVEN: A PowerpointDocument and a presentation
doc = PowerpointDocument(self.real_controller, self.file_name)
# WHEN: loading the filename
doc.load_presentation()
result = doc.is_loaded()
# THEN: result should be true
self.assertEqual(result, True, 'The result should be True')
else:
self.skipTest('Powerpoint not available, skipping test.')
def create_titles_and_notes_test(self):
"""
Test creating the titles from PowerPoint
"""
if is_win() and self.real_controller.check_available():
# GIVEN: mocked save_titles_and_notes, _get_text_from_shapes and two mocked slides
self.doc = PowerpointDocument(self.real_controller, self.file_name)
self.doc.save_titles_and_notes = MagicMock()
self.doc._PowerpointDocument__get_text_from_shapes = MagicMock()
slide = MagicMock()
slide.Shapes.Title.TextFrame.TextRange.Text = 'SlideText'
pres = MagicMock()
pres.Slides = [slide, slide]
self.doc.presentation = pres
# WHEN reading the titles and notes
self.doc.create_titles_and_notes()
# THEN the save should have been called exactly once with 2 titles and 2 notes
self.doc.save_titles_and_notes.assert_called_once_with(['SlideText\n', 'SlideText\n'], [' ', ' '])
else:
self.skipTest('Powerpoint not available, skipping test.')
def create_titles_and_notes_with_no_slides_test(self):
"""
Test creating the titles from PowerPoint when it returns no slides
"""
if is_win() and self.real_controller.check_available():
# GIVEN: mocked save_titles_and_notes, _get_text_from_shapes and two mocked slides
doc = PowerpointDocument(self.real_controller, self.file_name)
doc.save_titles_and_notes = MagicMock()
doc._PowerpointDocument__get_text_from_shapes = MagicMock()
pres = MagicMock()
pres.Slides = []
doc.presentation = pres
# WHEN reading the titles and notes
doc.create_titles_and_notes()
# THEN the save should have been called exactly once with empty titles and notes
doc.save_titles_and_notes.assert_called_once_with([], [])
else:
self.skipTest('Powerpoint not available, skipping test.')
def get_text_from_shapes_test(self):
"""
Test getting text from powerpoint shapes
"""
# GIVEN: mocked shapes
shape = MagicMock()
shape.PlaceholderFormat.Type = 2
shape.HasTextFrame = shape.TextFrame.HasText = True
shape.TextFrame.TextRange.Text = 'slideText'
shapes = [shape, shape]
# WHEN: getting the text
result = _get_text_from_shapes(shapes)
# THEN: it should return the text
self.assertEqual(result, 'slideText\nslideText\n', 'result should match \'slideText\nslideText\n\'')
def get_text_from_shapes_with_no_shapes_test(self):
"""
Test getting text from powerpoint shapes with no shapes
"""
# GIVEN: empty shapes array
shapes = []
# WHEN: getting the text
result = _get_text_from_shapes(shapes)
# THEN: it should not fail but return empty string
self.assertEqual(result, '', 'result should be empty')

View File

@ -4,8 +4,8 @@
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2013 Raoul Snyman #
# Portions copyright (c) 2008-2013 Tim Bentley, Gerald Britton, Jonathan #
# Copyright (c) 2008-2014 Raoul Snyman #
# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan #
# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
@ -31,16 +31,19 @@ This module contains tests for the pptviewcontroller module of the Presentations
"""
import os
import shutil
if os.name == 'nt':
from ctypes import cdll
from tempfile import mkdtemp
from unittest import TestCase
from tests.functional import MagicMock, patch
from tests.helpers.testmixin import TestMixin
from tests.utils.constants import TEST_RESOURCES_PATH
from openlp.plugins.presentations.lib.pptviewcontroller import PptviewDocument, PptviewController
from openlp.core.common import is_win
if is_win():
from ctypes import cdll
class TestPptviewController(TestCase, TestMixin):
@ -98,7 +101,7 @@ class TestPptviewController(TestCase, TestMixin):
available = controller.check_available()
# THEN: On windows it should return True, on other platforms False
if os.name == 'nt':
if is_win():
self.assertTrue(available, 'check_available should return True on windows.')
else:
self.assertFalse(available, 'check_available should return False when not on windows.')
@ -130,7 +133,7 @@ class TestPptviewDocument(TestCase):
"""
Set up the patches and mocks need for all tests.
"""
self.os_patcher = patch('openlp.plugins.presentations.lib.pptviewcontroller.os')
self.os_isdir_patcher = patch('openlp.plugins.presentations.lib.pptviewcontroller.os.path.isdir')
self.pptview_document_create_thumbnails_patcher = patch(
'openlp.plugins.presentations.lib.pptviewcontroller.PptviewDocument.create_thumbnails')
self.pptview_document_stop_presentation_patcher = patch(
@ -141,46 +144,45 @@ class TestPptviewDocument(TestCase):
'openlp.plugins.presentations.lib.pptviewcontroller.PresentationDocument._setup')
self.screen_list_patcher = patch('openlp.plugins.presentations.lib.pptviewcontroller.ScreenList')
self.rect_patcher = MagicMock()
self.mock_os = self.os_patcher.start()
self.mock_os_isdir = self.os_isdir_patcher.start()
self.mock_pptview_document_create_thumbnails = self.pptview_document_create_thumbnails_patcher.start()
self.mock_pptview_document_stop_presentation = self.pptview_document_stop_presentation_patcher.start()
self.mock_presentation_document_get_temp_folder = self.presentation_document_get_temp_folder_patcher.start()
self.mock_presentation_document_setup = self.presentation_document_setup_patcher.start()
self.mock_rect = self.rect_patcher.start()
self.mock_screen_list = self.screen_list_patcher.start()
self.mock_controller = MagicMock()
self.mock_presentation = MagicMock()
self.mock_presentation_document_get_temp_folder.return_value = 'temp folder'
self.temp_folder = mkdtemp()
self.mock_presentation_document_get_temp_folder.return_value = self.temp_folder
def tearDown(self):
"""
Stop the patches
"""
self.os_patcher.stop()
self.os_isdir_patcher.stop()
self.pptview_document_create_thumbnails_patcher.stop()
self.pptview_document_stop_presentation_patcher.stop()
self.presentation_document_get_temp_folder_patcher.stop()
self.presentation_document_setup_patcher.stop()
self.rect_patcher.stop()
self.screen_list_patcher.stop()
shutil.rmtree(self.temp_folder)
def load_presentation_succesfull_test(self):
"""
Test the PptviewDocument.load_presentation() method when the PPT is successfully opened
"""
# GIVEN: A reset mocked_os
self.mock_os.reset()
self.mock_os_isdir.reset()
# WHEN: The temporary directory exists and OpenPPT returns successfully (not -1)
self.mock_os.path.isdir.return_value = True
self.mock_os_isdir.return_value = True
self.mock_controller.process.OpenPPT.return_value = 0
instance = PptviewDocument(self.mock_controller, self.mock_presentation)
instance.file_path = 'test\path.ppt'
if os.name == 'nt':
if is_win():
result = instance.load_presentation()
# THEN: PptviewDocument.load_presentation should return True
@ -191,17 +193,78 @@ class TestPptviewDocument(TestCase):
Test the PptviewDocument.load_presentation() method when the temporary directory does not exist and the PPT is
not successfully opened
"""
# GIVEN: A reset mocked_os
self.mock_os.reset()
# GIVEN: A reset mock_os_isdir
self.mock_os_isdir.reset()
# WHEN: The temporary directory does not exist and OpenPPT returns unsuccessfully (-1)
self.mock_os.path.isdir.return_value = False
self.mock_controller.process.OpenPPT.return_value = -1
instance = PptviewDocument(self.mock_controller, self.mock_presentation)
instance.file_path = 'test\path.ppt'
if os.name == 'nt':
result = instance.load_presentation()
with patch('openlp.plugins.presentations.lib.pptviewcontroller.os.makedirs') as mock_makedirs:
self.mock_os_isdir.return_value = False
self.mock_controller.process.OpenPPT.return_value = -1
instance = PptviewDocument(self.mock_controller, self.mock_presentation)
instance.file_path = 'test\path.ppt'
if is_win():
result = instance.load_presentation()
# THEN: The temporary directory should be created and PptviewDocument.load_presentation should return False
self.mock_os.makedirs.assert_called_once_with('temp folder')
self.assertFalse(result)
# THEN: The temp folder should be created and PptviewDocument.load_presentation should return False
mock_makedirs.assert_called_once_with(self.temp_folder)
self.assertFalse(result)
def create_titles_and_notes_test(self):
"""
Test PowerpointController.create_titles_and_notes
"""
# GIVEN: mocked PresentationController.save_titles_and_notes and a pptx file
doc = PptviewDocument(self.mock_controller, self.mock_presentation)
doc.file_path = os.path.join(TEST_RESOURCES_PATH, 'presentations', 'test.pptx')
doc.save_titles_and_notes = MagicMock()
# WHEN reading the titles and notes
doc.create_titles_and_notes()
# THEN save_titles_and_notes should have been called once with empty arrays
doc.save_titles_and_notes.assert_called_once_with(['Test 1\n', '\n', 'Test 2\n', 'Test 4\n', 'Test 3\n'],
['Notes for slide 1', 'Inserted', 'Notes for slide 2',
'Notes \nfor slide 4', 'Notes for slide 3'])
def create_titles_and_notes_nonexistent_file_test(self):
"""
Test PowerpointController.create_titles_and_notes with nonexistent file
"""
# GIVEN: mocked PresentationController.save_titles_and_notes and an nonexistent file
with patch('builtins.open') as mocked_open, \
patch('openlp.plugins.presentations.lib.pptviewcontroller.os.path.exists') as mocked_exists, \
patch('openlp.plugins.presentations.lib.presentationcontroller.check_directory_exists') as \
mocked_dir_exists:
mocked_exists.return_value = False
mocked_dir_exists.return_value = False
doc = PptviewDocument(self.mock_controller, self.mock_presentation)
doc.file_path = 'Idontexist.pptx'
doc.save_titles_and_notes = MagicMock()
# WHEN: Reading the titles and notes
doc.create_titles_and_notes()
# THEN: File existens should have been checked, and not have been opened.
doc.save_titles_and_notes.assert_called_once_with(None, None)
mocked_exists.assert_any_call('Idontexist.pptx')
self.assertEqual(mocked_open.call_count, 0, 'There should be no calls to open a file.')
def create_titles_and_notes_invalid_file_test(self):
"""
Test PowerpointController.create_titles_and_notes with invalid file
"""
# GIVEN: mocked PresentationController.save_titles_and_notes and an invalid file
with patch('builtins.open') as mocked_open, \
patch('openlp.plugins.presentations.lib.pptviewcontroller.zipfile.is_zipfile') as mocked_is_zf:
mocked_is_zf.return_value = False
mocked_open.filesize = 10
doc = PptviewDocument(self.mock_controller, self.mock_presentation)
doc.file_path = os.path.join(TEST_RESOURCES_PATH, 'presentations', 'test.ppt')
doc.save_titles_and_notes = MagicMock()
# WHEN: reading the titles and notes
doc.create_titles_and_notes()
# THEN:
doc.save_titles_and_notes.assert_called_once_with(None, None)
self.assertEqual(mocked_is_zf.call_count, 1, 'is_zipfile should have been called once')

View File

@ -1,158 +1,166 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2014 Raoul Snyman #
# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan #
# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, #
# Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
This module contains tests for the Presentation Controller.
"""
from unittest import TestCase
from openlp.plugins.presentations.lib.presentationcontroller import PresentationController, PresentationDocument
from tests.functional import MagicMock, patch
class TestPresentationController(TestCase):
"""
Test the PresentationController.
"""
# TODO: Items left to test
# PresentationController
# __init__
# enabled
# is_available
# check_available
# start_process
# kill
# add_document
# remove_doc
# close_presentation
# _get_plugin_manager
def constructor_test(self):
"""
Test the Constructor
"""
# GIVEN: No presentation controller
controller = None
# WHEN: The presentation controller object is created
mock_plugin = MagicMock()
mock_plugin.settings_section = ''
controller = PresentationController(plugin=mock_plugin)
# THEN: The name of the presentation controller should be correct
self.assertEqual('PresentationController', controller.name,
'The name of the presentation controller should be correct')
class TestPresentationDocument(TestCase):
"""
Test the PresentationDocument Class
"""
# TODO: Items left to test
# PresentationDocument
# __init__
# load_presentation
# presentation_deleted
# get_file_name
# get_thumbnail_folder
# get_temp_folder
# check_thumbnails
# close_presentation
# is_active
# is_loaded
# blank_screen
# unblank_screen
# is_blank
# stop_presentation
# start_presentation
# get_slide_number
# get_slide_count
# goto_slide
# next_step
# previous_step
# convert_thumbnail
# get_thumbnail_path
# poll_slidenumber
# get_slide_text
# get_slide_notes
def setUp(self):
"""
Set up the patches and mocks need for all tests.
"""
self.check_directory_exists_patcher = \
patch('openlp.plugins.presentations.lib.presentationcontroller.check_directory_exists')
self.get_thumbnail_folder_patcher = \
patch('openlp.plugins.presentations.lib.presentationcontroller.PresentationDocument.get_thumbnail_folder')
self._setup_patcher = \
patch('openlp.plugins.presentations.lib.presentationcontroller.PresentationDocument._setup')
self.mock_check_directory_exists = self.check_directory_exists_patcher.start()
self.mock_get_thumbnail_folder = self.get_thumbnail_folder_patcher.start()
self.mock_setup = self._setup_patcher.start()
self.mock_controller = MagicMock()
self.mock_get_thumbnail_folder.return_value = 'returned/path/'
def tearDown(self):
"""
Stop the patches
"""
self.check_directory_exists_patcher.stop()
self.get_thumbnail_folder_patcher.stop()
self._setup_patcher.stop()
def initialise_presentation_document_test(self):
"""
Test the PresentationDocument __init__ method when initialising the PresentationDocument Class
"""
# GIVEN: A reset mock_setup and mocked controller
self.mock_setup.reset()
# WHEN: Creating an instance of PresentationDocument
PresentationDocument(self.mock_controller, 'Name')
# THEN: PresentationDocument.__init__ should have been called with the correct arguments
self.mock_setup.assert_called_once_with('Name')
def presentation_document_setup_test(self):
"""
Test the PresentationDocument _setup method when initialising the PresentationDocument Class
"""
self._setup_patcher.stop()
# GIVEN: A mocked controller, patched check_directory_exists_patcher and patched get_thumbnail_folder method
# WHEN: Creating an instance of PresentationDocument
PresentationDocument(self.mock_controller, 'Name')
# THEN: check_directory_exists should have been called with the correct arguments
self.mock_check_directory_exists.assert_called_once_with('returned/path/')
self._setup_patcher.start()
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2014 Raoul Snyman #
# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan #
# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, #
# Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
Functional tests to test the PresentationController and PresentationDocument
classes and related methods.
"""
from unittest import TestCase
import os
from openlp.plugins.presentations.lib.presentationcontroller import PresentationController, PresentationDocument
from tests.functional import MagicMock, patch, mock_open
FOLDER_TO_PATCH = 'openlp.plugins.presentations.lib.presentationcontroller.PresentationDocument.get_thumbnail_folder'
class TestPresentationController(TestCase):
"""
Test the PresentationController.
"""
def setUp(self):
mocked_plugin = MagicMock()
mocked_plugin.settings_section = 'presentations'
self.presentation = PresentationController(mocked_plugin)
self.document = PresentationDocument(self.presentation, '')
def constructor_test(self):
"""
Test the Constructor
"""
# GIVEN: A mocked plugin
# WHEN: The PresentationController is created
# THEN: The name of the presentation controller should be correct
self.assertEqual('PresentationController', self.presentation.name,
'The name of the presentation controller should be correct')
def save_titles_and_notes_test(self):
"""
Test PresentationDocument.save_titles_and_notes method with two valid lists
"""
# GIVEN: two lists of length==2 and a mocked open and get_thumbnail_folder
mocked_open = mock_open()
with patch('builtins.open', mocked_open), patch(FOLDER_TO_PATCH) as mocked_get_thumbnail_folder:
titles = ['uno', 'dos']
notes = ['one', 'two']
# WHEN: calling save_titles_and_notes
mocked_get_thumbnail_folder.return_value = 'test'
self.document.save_titles_and_notes(titles, notes)
# THEN: the last call to open should have been for slideNotes2.txt
mocked_open.assert_any_call(os.path.join('test', 'titles.txt'), mode='w')
mocked_open.assert_any_call(os.path.join('test', 'slideNotes1.txt'), mode='w')
mocked_open.assert_any_call(os.path.join('test', 'slideNotes2.txt'), mode='w')
self.assertEqual(mocked_open.call_count, 3, 'There should be exactly three files opened')
mocked_open().writelines.assert_called_once_with(['uno', 'dos'])
mocked_open().write.assert_called_any('one')
mocked_open().write.assert_called_any('two')
def save_titles_and_notes_with_None_test(self):
"""
Test PresentationDocument.save_titles_and_notes method with no data
"""
# GIVEN: None and an empty list and a mocked open and get_thumbnail_folder
with patch('builtins.open') as mocked_open, patch(FOLDER_TO_PATCH) as mocked_get_thumbnail_folder:
titles = None
notes = None
# WHEN: calling save_titles_and_notes
mocked_get_thumbnail_folder.return_value = 'test'
self.document.save_titles_and_notes(titles, notes)
# THEN: No file should have been created
self.assertEqual(mocked_open.call_count, 0, 'No file should be created')
def get_titles_and_notes_test(self):
"""
Test PresentationDocument.get_titles_and_notes method
"""
# GIVEN: A mocked open, get_thumbnail_folder and exists
with patch('builtins.open', mock_open(read_data='uno\ndos\n')) as mocked_open, \
patch(FOLDER_TO_PATCH) as mocked_get_thumbnail_folder, \
patch('openlp.plugins.presentations.lib.presentationcontroller.os.path.exists') as mocked_exists:
mocked_get_thumbnail_folder.return_value = 'test'
mocked_exists.return_value = True
# WHEN: calling get_titles_and_notes
result_titles, result_notes = self.document.get_titles_and_notes()
# THEN: it should return two items for the titles and two empty strings for the notes
self.assertIs(type(result_titles), list, 'result_titles should be of type list')
self.assertEqual(len(result_titles), 2, 'There should be two items in the titles')
self.assertIs(type(result_notes), list, 'result_notes should be of type list')
self.assertEqual(len(result_notes), 2, 'There should be two items in the notes')
self.assertEqual(mocked_open.call_count, 3, 'Three files should be opened')
mocked_open.assert_any_call(os.path.join('test', 'titles.txt'))
mocked_open.assert_any_call(os.path.join('test', 'slideNotes1.txt'))
mocked_open.assert_any_call(os.path.join('test', 'slideNotes2.txt'))
self.assertEqual(mocked_exists.call_count, 3, 'Three files should have been checked')
def get_titles_and_notes_with_file_not_found_test(self):
"""
Test PresentationDocument.get_titles_and_notes method with file not found
"""
# GIVEN: A mocked open, get_thumbnail_folder and exists
with patch('builtins.open') as mocked_open, \
patch(FOLDER_TO_PATCH) as mocked_get_thumbnail_folder, \
patch('openlp.plugins.presentations.lib.presentationcontroller.os.path.exists') as mocked_exists:
mocked_get_thumbnail_folder.return_value = 'test'
mocked_exists.return_value = False
# WHEN: calling get_titles_and_notes
result_titles, result_notes = self.document.get_titles_and_notes()
# THEN: it should return two empty lists
self.assertIs(type(result_titles), list, 'result_titles should be of type list')
self.assertEqual(len(result_titles), 0, 'there be no titles')
self.assertIs(type(result_notes), list, 'result_notes should be a list')
self.assertEqual(len(result_notes), 0, 'but the list should be empty')
self.assertEqual(mocked_open.call_count, 0, 'No calls to open files')
self.assertEqual(mocked_exists.call_count, 1, 'There should be one call to file exists')
def get_titles_and_notes_with_file_error_test(self):
"""
Test PresentationDocument.get_titles_and_notes method with file errors
"""
# GIVEN: A mocked open, get_thumbnail_folder and exists
with patch('builtins.open') as mocked_open, \
patch(FOLDER_TO_PATCH) as mocked_get_thumbnail_folder, \
patch('openlp.plugins.presentations.lib.presentationcontroller.os.path.exists') as mocked_exists:
mocked_get_thumbnail_folder.return_value = 'test'
mocked_exists.return_value = True
mocked_open.side_effect = IOError()
# WHEN: calling get_titles_and_notes
result_titles, result_notes = self.document.get_titles_and_notes()
# THEN: it should return two empty lists
self.assertIs(type(result_titles), list, 'result_titles should be a list')

View File

@ -48,7 +48,8 @@ __default_settings__ = {
'remotes/user id': 'openlp',
'remotes/password': 'password',
'remotes/authentication enabled': False,
'remotes/ip address': '0.0.0.0'
'remotes/ip address': '0.0.0.0',
'remotes/thumbnails': True
}
ZERO_URL = '0.0.0.0'
TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..', 'resources'))

View File

@ -30,10 +30,12 @@
This module contains tests for the lib submodule of the Remotes plugin.
"""
import os
import urllib.request
from unittest import TestCase
from openlp.core.common import Settings, Registry
from openlp.plugins.remotes.lib.httpserver import HttpRouter
from urllib.parse import urlparse
from tests.functional import MagicMock, patch, mock_open
from tests.helpers.testmixin import TestMixin
@ -186,3 +188,86 @@ class TestRouter(TestCase, TestMixin):
self.router.send_response.assert_called_once_with(200)
self.router.send_header.assert_called_once_with('Content-type', 'text/html')
self.assertEqual(self.router.end_headers.call_count, 1, 'end_headers called once')
def serve_thumbnail_without_params_test(self):
"""
Test the serve_thumbnail routine without params
"""
self.router.send_response = MagicMock()
self.router.send_header = MagicMock()
self.router.end_headers = MagicMock()
self.router.wfile = MagicMock()
self.router.serve_thumbnail()
self.router.send_response.assert_called_once_with(404)
self.assertEqual(self.router.send_response.call_count, 1, 'Send response called once')
self.assertEqual(self.router.end_headers.call_count, 1, 'end_headers called once')
def serve_thumbnail_with_invalid_params_test(self):
"""
Test the serve_thumbnail routine with invalid params
"""
# GIVEN: Mocked send_header, send_response, end_headers and wfile
self.router.send_response = MagicMock()
self.router.send_header = MagicMock()
self.router.end_headers = MagicMock()
self.router.wfile = MagicMock()
# WHEN: pass a bad controller
self.router.serve_thumbnail('badcontroller', 'tecnologia 1.pptx/slide1.png')
# THEN: a 404 should be returned
self.assertEqual(len(self.router.send_header.mock_calls), 1, 'One header')
self.assertEqual(len(self.router.send_response.mock_calls), 1, 'One response')
self.assertEqual(len(self.router.wfile.mock_calls), 1, 'Once call to write to the socket')
self.router.send_response.assert_called_once_with(404)
# WHEN: pass a bad filename
self.router.send_response.reset_mock()
self.router.serve_thumbnail('presentations', 'tecnologia 1.pptx/badfilename.png')
# THEN: return a 404
self.router.send_response.assert_called_once_with(404)
# WHEN: a dangerous URL is passed
self.router.send_response.reset_mock()
self.router.serve_thumbnail('presentations', '../tecnologia 1.pptx/slide1.png')
# THEN: return a 404
self.router.send_response.assert_called_once_with(404)
def serve_thumbnail_with_valid_params_test(self):
"""
Test the serve_thumbnail routine with valid params
"""
# GIVEN: Mocked send_header, send_response, end_headers and wfile
self.router.send_response = MagicMock()
self.router.send_header = MagicMock()
self.router.end_headers = MagicMock()
self.router.wfile = MagicMock()
mocked_image_manager = MagicMock()
Registry.create()
Registry().register('image_manager', mocked_image_manager)
file_name = 'another%20test/slide1.png'
full_path = os.path.normpath(os.path.join('thumbnails', file_name))
width = 120
height = 90
with patch('openlp.core.lib.os.path.exists') as mocked_exists, \
patch('builtins.open', mock_open(read_data='123')), \
patch('openlp.plugins.remotes.lib.httprouter.AppLocation') as mocked_location, \
patch('openlp.plugins.remotes.lib.httprouter.image_to_byte') as mocked_image_to_byte:
mocked_exists.return_value = True
mocked_image_to_byte.return_value = '123'
mocked_location.get_section_data_path.return_value = ''
# WHEN: pass good controller and filename
result = self.router.serve_thumbnail('presentations', '{0}x{1}'.format(width, height), file_name)
# THEN: a file should be returned
self.assertEqual(self.router.send_header.call_count, 1, 'One header')
self.assertEqual(self.router.send_response.call_count, 1, 'Send response called once')
self.assertEqual(self.router.end_headers.call_count, 1, 'end_headers called once')
mocked_exists.assert_called_with(urllib.parse.unquote(full_path))
self.assertEqual(mocked_image_to_byte.call_count, 1, 'Called once')
mocked_image_manager.assert_called_any(os.path.normpath('thumbnails\\another test'),
'slide1.png', None, '120x90')
mocked_image_manager.assert_called_any(os.path.normpath('thumbnails\\another test'), 'slide1.png', '120x90')

View File

@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2014 Raoul Snyman #
# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan #
# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, #
# Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################

View File

@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2014 Raoul Snyman #
# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan #
# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, #
# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. #
# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, #
# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, #
# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, #
# Frode Woldsund, Martin Zibricky, Patrick Zimmermann #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it #
# under the terms of the GNU General Public License as published by the Free #
# Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
# more details. #
# #
# You should have received a copy of the GNU General Public License along #
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################

View File

@ -60,6 +60,7 @@ class TestMediaClipSelectorForm(TestCase, TestMixin):
# Mock VLC so we don't actually use it
self.vlc_patcher = patch('openlp.plugins.media.forms.mediaclipselectorform.vlc')
self.vlc_patcher.start()
Registry().register('application', self.app)
# Mock the media item
self.mock_media_item = MagicMock()
# create form to test
@ -67,6 +68,9 @@ class TestMediaClipSelectorForm(TestCase, TestMixin):
mock_media_state_wait = MagicMock()
mock_media_state_wait.return_value = True
self.form.media_state_wait = mock_media_state_wait
self.form.application.set_busy_cursor = MagicMock()
self.form.application.set_normal_cursor = MagicMock()
self.form.find_optical_devices = MagicMock()
def tearDown(self):
"""
@ -155,3 +159,21 @@ class TestMediaClipSelectorForm(TestCase, TestMixin):
self.form.audio_tracks_combobox.itemData.assert_any_call(0)
self.form.audio_tracks_combobox.itemData.assert_any_call(1)
self.form.subtitle_tracks_combobox.itemData.assert_any_call(0)
def click_save_button_test(self):
"""
Test that the correct function is called when save is clicked, and that it behaves as expected.
"""
# GIVEN: Mocked methods.
with patch('openlp.plugins.media.forms.mediaclipselectorform.critical_error_message_box') as \
mocked_critical_error_message_box,\
patch('PyQt4.QtGui.QDialog.exec_') as mocked_exec:
self.form.exec_()
# WHEN: The save button is clicked with a NoneType in start_time_ms or end_time_ms
self.form.accept()
# THEN: we should get an error message
mocked_critical_error_message_box.assert_called_with('DVD not loaded correctly',
'The DVD was not loaded correctly, '
'please re-load and try again.')

Binary file not shown.

Binary file not shown.