forked from openlp/openlp
Merge trunk
This commit is contained in:
commit
806bb5973e
@ -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()
|
||||
|
@ -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')
|
||||
|
@ -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):
|
||||
|
@ -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')
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
@ -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'):
|
||||
|
@ -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)
|
||||
|
@ -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'))
|
||||
|
@ -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)
|
||||
|
@ -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 = []
|
||||
|
@ -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)
|
||||
|
@ -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:
|
||||
|
@ -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):
|
||||
"""
|
||||
|
@ -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:'))
|
||||
|
@ -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:
|
||||
|
@ -85,7 +85,7 @@ class BibleFormat(object):
|
||||
BibleFormat.CSV,
|
||||
BibleFormat.OpenSong,
|
||||
BibleFormat.WebDownload,
|
||||
BibleFormar.Zefania,
|
||||
BibleFormat.Zefania,
|
||||
]
|
||||
|
||||
|
||||
|
@ -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):
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
BIN
openlp/plugins/presentations/lib/pptviewlib/test.pptx
Normal file
BIN
openlp/plugins/presentations/lib/pptviewlib/test.pptx
Normal file
Binary file not shown.
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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):
|
||||
"""
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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>
|
||||
|
BIN
resources/images/openlp-osx-wizard.png
Normal file
BIN
resources/images/openlp-osx-wizard.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 39 KiB |
@ -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):
|
||||
"""
|
||||
|
@ -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):
|
||||
"""
|
||||
|
@ -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
|
||||
|
@ -51,7 +51,7 @@ class TestTheme(TestCase):
|
||||
"""
|
||||
pass
|
||||
|
||||
def test_new_theme(self):
|
||||
def new_theme_test(self):
|
||||
"""
|
||||
Test the theme creation - basic test
|
||||
"""
|
||||
|
@ -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
|
||||
"""
|
||||
|
@ -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
|
||||
"""
|
||||
|
@ -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
|
||||
"""
|
||||
|
@ -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)
|
||||
|
@ -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):
|
||||
"""
|
||||
|
@ -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
|
@ -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')
|
||||
|
@ -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')
|
||||
|
@ -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')
|
||||
|
@ -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'))
|
||||
|
@ -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')
|
||||
|
@ -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 #
|
||||
###############################################################################
|
28
tests/interfaces/openlp_plugins/media/forms/__init__.py
Normal file
28
tests/interfaces/openlp_plugins/media/forms/__init__.py
Normal 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 #
|
||||
###############################################################################
|
@ -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.')
|
||||
|
BIN
tests/resources/presentations/test.ppt
Normal file
BIN
tests/resources/presentations/test.ppt
Normal file
Binary file not shown.
BIN
tests/resources/presentations/test.pptx
Normal file
BIN
tests/resources/presentations/test.pptx
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user